##// END OF EJS Templates
Catter for empty docstrings (bug introduced 2 checkins ago).
gvaroquaux -
Show More
@@ -1,524 +1,526
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 # The different line markers used to higlight the prompts.
132 132 _markers = dict()
133 133
134 134 #--------------------------------------------------------------------------
135 135 # Public API
136 136 #--------------------------------------------------------------------------
137 137
138 138 def __init__(self, parent, id=wx.ID_ANY, pos=wx.DefaultPosition,
139 139 size=wx.DefaultSize,
140 140 style=wx.CLIP_CHILDREN|wx.WANTS_CHARS,
141 141 *args, **kwds):
142 142 """ Create Shell instance.
143 143 """
144 144 ConsoleWidget.__init__(self, parent, id, pos, size, style)
145 145 PrefilterFrontEnd.__init__(self, **kwds)
146 146
147 147 # Stick in our own raw_input:
148 148 self.ipython0.raw_input = self.raw_input
149 149
150 150 # Marker for complete buffer.
151 151 self.MarkerDefine(_COMPLETE_BUFFER_MARKER, stc.STC_MARK_BACKGROUND,
152 152 background=_COMPLETE_BUFFER_BG)
153 153 # Marker for current input buffer.
154 154 self.MarkerDefine(_INPUT_MARKER, stc.STC_MARK_BACKGROUND,
155 155 background=_INPUT_BUFFER_BG)
156 156 # Marker for tracebacks.
157 157 self.MarkerDefine(_ERROR_MARKER, stc.STC_MARK_BACKGROUND,
158 158 background=_ERROR_BG)
159 159
160 160 # A time for flushing the write buffer
161 161 BUFFER_FLUSH_TIMER_ID = 100
162 162 self._buffer_flush_timer = wx.Timer(self, BUFFER_FLUSH_TIMER_ID)
163 163 wx.EVT_TIMER(self, BUFFER_FLUSH_TIMER_ID, self._buffer_flush)
164 164
165 165 if 'debug' in kwds:
166 166 self.debug = kwds['debug']
167 167 kwds.pop('debug')
168 168
169 169 # Inject self in namespace, for debug
170 170 if self.debug:
171 171 self.shell.user_ns['self'] = self
172 172 # Inject our own raw_input in namespace
173 173 self.shell.user_ns['raw_input'] = self.raw_input
174 174
175 175
176 176 def raw_input(self, prompt):
177 177 """ A replacement from python's raw_input.
178 178 """
179 179 self.new_prompt(prompt)
180 180 self._input_state = 'raw_input'
181 181 if hasattr(self, '_cursor'):
182 182 del self._cursor
183 183 self.SetCursor(wx.StockCursor(wx.CURSOR_CROSS))
184 184 self.__old_on_enter = self._on_enter
185 185 event_loop = wx.EventLoop()
186 186 def my_on_enter():
187 187 event_loop.Exit()
188 188 self._on_enter = my_on_enter
189 189 # XXX: Running a separate event_loop. Ugly.
190 190 event_loop.Run()
191 191 self._on_enter = self.__old_on_enter
192 192 self._input_state = 'buffering'
193 193 self._cursor = wx.BusyCursor()
194 194 return self.input_buffer.rstrip('\n')
195 195
196 196
197 197 def system_call(self, command_string):
198 198 self._input_state = 'subprocess'
199 199 event_loop = wx.EventLoop()
200 200 def _end_system_call():
201 201 self._input_state = 'buffering'
202 202 self._running_process = False
203 203 event_loop.Exit()
204 204
205 205 self._running_process = PipedProcess(command_string,
206 206 out_callback=self.buffered_write,
207 207 end_callback = _end_system_call)
208 208 self._running_process.start()
209 209 # XXX: Running a separate event_loop. Ugly.
210 210 event_loop.Run()
211 211 # Be sure to flush the buffer.
212 212 self._buffer_flush(event=None)
213 213
214 214
215 215 def do_calltip(self):
216 216 """ Analyse current and displays useful calltip for it.
217 217 """
218 218 if self.debug:
219 219 print >>sys.__stdout__, "do_calltip"
220 220 separators = re.compile('[\s\{\}\[\]\(\)\= ,:]')
221 221 symbol = self.input_buffer
222 222 symbol_string = separators.split(symbol)[-1]
223 223 base_symbol_string = symbol_string.split('.')[0]
224 224 if base_symbol_string in self.shell.user_ns:
225 225 symbol = self.shell.user_ns[base_symbol_string]
226 226 elif base_symbol_string in self.shell.user_global_ns:
227 227 symbol = self.shell.user_global_ns[base_symbol_string]
228 228 elif base_symbol_string in __builtin__.__dict__:
229 229 symbol = __builtin__.__dict__[base_symbol_string]
230 230 else:
231 231 return False
232 232 try:
233 233 for name in symbol_string.split('.')[1:] + ['__doc__']:
234 234 symbol = getattr(symbol, name)
235 235 self.AutoCompCancel()
236 # Check that the symbol can indeed be converted to a string:
237 symbol += ''
236 238 wx.CallAfter(self.CallTipShow, self.GetCurrentPos(), symbol)
237 239 except:
238 240 # The retrieve symbol couldn't be converted to a string
239 241 pass
240 242
241 243
242 244 def _popup_completion(self, create=False):
243 245 """ Updates the popup completion menu if it exists. If create is
244 246 true, open the menu.
245 247 """
246 248 if self.debug:
247 249 print >>sys.__stdout__, "_popup_completion"
248 250 line = self.input_buffer
249 251 if (self.AutoCompActive() and line and not line[-1] == '.') \
250 252 or create==True:
251 253 suggestion, completions = self.complete(line)
252 254 offset=0
253 255 if completions:
254 256 complete_sep = re.compile('[\s\{\}\[\]\(\)\= ,:]')
255 257 residual = complete_sep.split(line)[-1]
256 258 offset = len(residual)
257 259 self.pop_completion(completions, offset=offset)
258 260 if self.debug:
259 261 print >>sys.__stdout__, completions
260 262
261 263
262 264 def buffered_write(self, text):
263 265 """ A write method for streams, that caches the stream in order
264 266 to avoid flooding the event loop.
265 267
266 268 This can be called outside of the main loop, in separate
267 269 threads.
268 270 """
269 271 self._out_buffer_lock.acquire()
270 272 self._out_buffer.append(text)
271 273 self._out_buffer_lock.release()
272 274 if not self._buffer_flush_timer.IsRunning():
273 275 wx.CallAfter(self._buffer_flush_timer.Start,
274 276 milliseconds=100, oneShot=True)
275 277
276 278
277 279 #--------------------------------------------------------------------------
278 280 # LineFrontEnd interface
279 281 #--------------------------------------------------------------------------
280 282
281 283 def execute(self, python_string, raw_string=None):
282 284 self._input_state = 'buffering'
283 285 self.CallTipCancel()
284 286 self._cursor = wx.BusyCursor()
285 287 if raw_string is None:
286 288 raw_string = python_string
287 289 end_line = self.current_prompt_line \
288 290 + max(1, len(raw_string.split('\n'))-1)
289 291 for i in range(self.current_prompt_line, end_line):
290 292 if i in self._markers:
291 293 self.MarkerDeleteHandle(self._markers[i])
292 294 self._markers[i] = self.MarkerAdd(i, _COMPLETE_BUFFER_MARKER)
293 295 # Use a callafter to update the display robustly under windows
294 296 def callback():
295 297 self.GotoPos(self.GetLength())
296 298 PrefilterFrontEnd.execute(self, python_string,
297 299 raw_string=raw_string)
298 300 wx.CallAfter(callback)
299 301
300 302 def save_output_hooks(self):
301 303 self.__old_raw_input = __builtin__.raw_input
302 304 PrefilterFrontEnd.save_output_hooks(self)
303 305
304 306 def capture_output(self):
305 307 self.SetLexer(stc.STC_LEX_NULL)
306 308 PrefilterFrontEnd.capture_output(self)
307 309 __builtin__.raw_input = self.raw_input
308 310
309 311
310 312 def release_output(self):
311 313 __builtin__.raw_input = self.__old_raw_input
312 314 PrefilterFrontEnd.release_output(self)
313 315 self.SetLexer(stc.STC_LEX_PYTHON)
314 316
315 317
316 318 def after_execute(self):
317 319 PrefilterFrontEnd.after_execute(self)
318 320 # Clear the wait cursor
319 321 if hasattr(self, '_cursor'):
320 322 del self._cursor
321 323 self.SetCursor(wx.StockCursor(wx.CURSOR_CHAR))
322 324
323 325
324 326 def show_traceback(self):
325 327 start_line = self.GetCurrentLine()
326 328 PrefilterFrontEnd.show_traceback(self)
327 329 self.ProcessEvent(wx.PaintEvent())
328 330 #wx.Yield()
329 331 for i in range(start_line, self.GetCurrentLine()):
330 332 self._markers[i] = self.MarkerAdd(i, _ERROR_MARKER)
331 333
332 334
333 335 #--------------------------------------------------------------------------
334 336 # FrontEndBase interface
335 337 #--------------------------------------------------------------------------
336 338
337 339 def render_error(self, e):
338 340 start_line = self.GetCurrentLine()
339 341 self.write('\n' + e + '\n')
340 342 for i in range(start_line, self.GetCurrentLine()):
341 343 self._markers[i] = self.MarkerAdd(i, _ERROR_MARKER)
342 344
343 345
344 346 #--------------------------------------------------------------------------
345 347 # ConsoleWidget interface
346 348 #--------------------------------------------------------------------------
347 349
348 350 def new_prompt(self, prompt):
349 351 """ Display a new prompt, and start a new input buffer.
350 352 """
351 353 self._input_state = 'readline'
352 354 ConsoleWidget.new_prompt(self, prompt)
353 355 i = self.current_prompt_line
354 356 self._markers[i] = self.MarkerAdd(i, _INPUT_MARKER)
355 357
356 358
357 359 def write(self, *args, **kwargs):
358 360 # Avoid multiple inheritence, be explicit about which
359 361 # parent method class gets called
360 362 ConsoleWidget.write(self, *args, **kwargs)
361 363
362 364
363 365 def _on_key_down(self, event, skip=True):
364 366 """ Capture the character events, let the parent
365 367 widget handle them, and put our logic afterward.
366 368 """
367 369 # FIXME: This method needs to be broken down in smaller ones.
368 370 current_line_number = self.GetCurrentLine()
369 371 if event.KeyCode in (ord('c'), ord('C')) and event.ControlDown():
370 372 # Capture Control-C
371 373 if self._input_state == 'subprocess':
372 374 if self.debug:
373 375 print >>sys.__stderr__, 'Killing running process'
374 376 if hasattr(self._running_process, 'process'):
375 377 self._running_process.process.kill()
376 378 elif self._input_state == 'buffering':
377 379 if self.debug:
378 380 print >>sys.__stderr__, 'Raising KeyboardInterrupt'
379 381 raise KeyboardInterrupt
380 382 # XXX: We need to make really sure we
381 383 # get back to a prompt.
382 384 elif self._input_state == 'subprocess' and (
383 385 ( event.KeyCode<256 and
384 386 not event.ControlDown() )
385 387 or
386 388 ( event.KeyCode in (ord('d'), ord('D')) and
387 389 event.ControlDown())):
388 390 # We are running a process, we redirect keys.
389 391 ConsoleWidget._on_key_down(self, event, skip=skip)
390 392 char = chr(event.KeyCode)
391 393 # Deal with some inconsistency in wx keycodes:
392 394 if char == '\r':
393 395 char = '\n'
394 396 elif not event.ShiftDown():
395 397 char = char.lower()
396 398 if event.ControlDown() and event.KeyCode in (ord('d'), ord('D')):
397 399 char = '\04'
398 400 self._running_process.process.stdin.write(char)
399 401 self._running_process.process.stdin.flush()
400 402 elif event.KeyCode in (ord('('), 57, 53):
401 403 # Calltips
402 404 event.Skip()
403 405 self.do_calltip()
404 406 elif self.AutoCompActive() and not event.KeyCode == ord('\t'):
405 407 event.Skip()
406 408 if event.KeyCode in (wx.WXK_BACK, wx.WXK_DELETE):
407 409 wx.CallAfter(self._popup_completion, create=True)
408 410 elif not event.KeyCode in (wx.WXK_UP, wx.WXK_DOWN, wx.WXK_LEFT,
409 411 wx.WXK_RIGHT, wx.WXK_ESCAPE):
410 412 wx.CallAfter(self._popup_completion)
411 413 else:
412 414 # Up history
413 415 if event.KeyCode == wx.WXK_UP and (
414 416 ( current_line_number == self.current_prompt_line and
415 417 event.Modifiers in (wx.MOD_NONE, wx.MOD_WIN) )
416 418 or event.ControlDown() ):
417 419 new_buffer = self.get_history_previous(
418 420 self.input_buffer)
419 421 if new_buffer is not None:
420 422 self.input_buffer = new_buffer
421 423 if self.GetCurrentLine() > self.current_prompt_line:
422 424 # Go to first line, for seemless history up.
423 425 self.GotoPos(self.current_prompt_pos)
424 426 # Down history
425 427 elif event.KeyCode == wx.WXK_DOWN and (
426 428 ( current_line_number == self.LineCount -1 and
427 429 event.Modifiers in (wx.MOD_NONE, wx.MOD_WIN) )
428 430 or event.ControlDown() ):
429 431 new_buffer = self.get_history_next()
430 432 if new_buffer is not None:
431 433 self.input_buffer = new_buffer
432 434 # Tab-completion
433 435 elif event.KeyCode == ord('\t'):
434 436 current_line, current_line_number = self.CurLine
435 437 if not re.match(r'^\s*$', current_line):
436 438 self.complete_current_input()
437 439 if self.AutoCompActive():
438 440 wx.CallAfter(self._popup_completion, create=True)
439 441 else:
440 442 event.Skip()
441 443 else:
442 444 ConsoleWidget._on_key_down(self, event, skip=skip)
443 445
444 446
445 447 def _on_key_up(self, event, skip=True):
446 448 """ Called when any key is released.
447 449 """
448 450 if event.KeyCode in (59, ord('.')):
449 451 # Intercepting '.'
450 452 event.Skip()
451 453 wx.CallAfter(self._popup_completion, create=True)
452 454 else:
453 455 ConsoleWidget._on_key_up(self, event, skip=skip)
454 456
455 457
456 458 def _on_enter(self):
457 459 """ Called on return key down, in readline input_state.
458 460 """
459 461 if self.debug:
460 462 print >>sys.__stdout__, repr(self.input_buffer)
461 463 PrefilterFrontEnd._on_enter(self)
462 464
463 465
464 466 #--------------------------------------------------------------------------
465 467 # EditWindow API
466 468 #--------------------------------------------------------------------------
467 469
468 470 def OnUpdateUI(self, event):
469 471 """ Override the OnUpdateUI of the EditWindow class, to prevent
470 472 syntax highlighting both for faster redraw, and for more
471 473 consistent look and feel.
472 474 """
473 475 if not self._input_state == 'readline':
474 476 ConsoleWidget.OnUpdateUI(self, event)
475 477
476 478 #--------------------------------------------------------------------------
477 479 # Private API
478 480 #--------------------------------------------------------------------------
479 481
480 482 def _buffer_flush(self, event):
481 483 """ Called by the timer to flush the write buffer.
482 484
483 485 This is always called in the mainloop, by the wx timer.
484 486 """
485 487 self._out_buffer_lock.acquire()
486 488 _out_buffer = self._out_buffer
487 489 self._out_buffer = []
488 490 self._out_buffer_lock.release()
489 491 self.write(''.join(_out_buffer), refresh=False)
490 492
491 493
492 494 def _colorize_input_buffer(self):
493 495 """ Keep the input buffer lines at a bright color.
494 496 """
495 497 if not self._input_state in ('readline', 'raw_input'):
496 498 return
497 499 end_line = self.GetCurrentLine()
498 500 if not sys.platform == 'win32':
499 501 end_line += 1
500 502 for i in range(self.current_prompt_line, end_line):
501 503 if i in self._markers:
502 504 self.MarkerDeleteHandle(self._markers[i])
503 505 self._markers[i] = self.MarkerAdd(i, _INPUT_MARKER)
504 506
505 507
506 508 if __name__ == '__main__':
507 509 class MainWindow(wx.Frame):
508 510 def __init__(self, parent, id, title):
509 511 wx.Frame.__init__(self, parent, id, title, size=(300,250))
510 512 self._sizer = wx.BoxSizer(wx.VERTICAL)
511 513 self.shell = WxController(self)
512 514 self._sizer.Add(self.shell, 1, wx.EXPAND)
513 515 self.SetSizer(self._sizer)
514 516 self.SetAutoLayout(1)
515 517 self.Show(True)
516 518
517 519 app = wx.PySimpleApp()
518 520 frame = MainWindow(None, wx.ID_ANY, 'Ipython')
519 521 frame.shell.SetFocus()
520 522 frame.SetSize((680, 460))
521 523 self = frame.shell
522 524
523 525 app.MainLoop()
524 526
General Comments 0
You need to be logged in to leave comments. Login now