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