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