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