##// END OF EJS Templates
BUG: Make the frontend compatible with wxPython 2.6
Gael Varoquaux -
Show More
@@ -1,625 +1,624 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 time
27 27 import sys
28 28 import string
29 29
30 30 LINESEP = '\n'
31 31 if sys.platform == 'win32':
32 32 LINESEP = '\n\r'
33 33
34 34 import re
35 35
36 36 # FIXME: Need to provide an API for non user-generated display on the
37 37 # screen: this should not be editable by the user.
38 38 #-------------------------------------------------------------------------------
39 39 # Constants
40 40 #-------------------------------------------------------------------------------
41 41 _COMPLETE_BUFFER_MARKER = 31
42 42 _ERROR_MARKER = 30
43 43 _INPUT_MARKER = 29
44 44
45 45 _DEFAULT_SIZE = 10
46 46 if sys.platform == 'darwin':
47 47 _DEFAULT_SIZE = 12
48 48
49 49 _DEFAULT_STYLE = {
50 50 #background definition
51 51 'default' : 'size:%d' % _DEFAULT_SIZE,
52 52 'bracegood' : 'fore:#00AA00,back:#000000,bold',
53 53 'bracebad' : 'fore:#FF0000,back:#000000,bold',
54 54
55 55 # Edge column: a number of None
56 56 'edge_column' : -1,
57 57
58 58 # properties for the various Python lexer styles
59 59 'comment' : 'fore:#007F00',
60 60 'number' : 'fore:#007F7F',
61 61 'string' : 'fore:#7F007F,italic',
62 62 'char' : 'fore:#7F007F,italic',
63 63 'keyword' : 'fore:#00007F,bold',
64 64 'triple' : 'fore:#7F0000',
65 65 'tripledouble' : 'fore:#7F0000',
66 66 'class' : 'fore:#0000FF,bold,underline',
67 67 'def' : 'fore:#007F7F,bold',
68 68 'operator' : 'bold',
69 69
70 70 # Default colors
71 71 'trace' : '#FAFAF1', # Nice green
72 72 'stdout' : '#FDFFD3', # Nice yellow
73 73 'stderr' : '#FFF1F1', # Nice red
74 74
75 75 # Default scintilla settings
76 76 'antialiasing' : True,
77 77 'carret_color' : 'BLACK',
78 78 'background_color' :'WHITE',
79 79
80 80 #prompt definition
81 81 'prompt_in1' : \
82 82 '\n\x01\x1b[0;34m\x02In [\x01\x1b[1;34m\x02$number\x01\x1b[0;34m\x02]: \x01\x1b[0m\x02',
83 83
84 84 'prompt_out': \
85 85 '\x01\x1b[0;31m\x02Out[\x01\x1b[1;31m\x02$number\x01\x1b[0;31m\x02]: \x01\x1b[0m\x02',
86 86 }
87 87
88 88 # new style numbers
89 89 _STDOUT_STYLE = 15
90 90 _STDERR_STYLE = 16
91 91 _TRACE_STYLE = 17
92 92
93 93
94 94 # system colors
95 95 #SYS_COLOUR_BACKGROUND = wx.SystemSettings.GetColour(wx.SYS_COLOUR_BACKGROUND)
96 96
97 97 # Translation table from ANSI escape sequences to color.
98 98 ANSI_STYLES = {'0;30': [0, 'BLACK'], '0;31': [1, 'RED'],
99 99 '0;32': [2, 'GREEN'], '0;33': [3, 'BROWN'],
100 100 '0;34': [4, 'BLUE'], '0;35': [5, 'PURPLE'],
101 101 '0;36': [6, 'CYAN'], '0;37': [7, 'LIGHT GREY'],
102 102 '1;30': [8, 'DARK GREY'], '1;31': [9, 'RED'],
103 103 '1;32': [10, 'SEA GREEN'], '1;33': [11, 'YELLOW'],
104 104 '1;34': [12, 'LIGHT BLUE'], '1;35':
105 105 [13, 'MEDIUM VIOLET RED'],
106 106 '1;36': [14, 'LIGHT STEEL BLUE'], '1;37': [15, 'YELLOW']}
107 107
108 108 # XXX: Maybe one day we should factor this code with ColorANSI. Right now
109 109 # ColorANSI is hard to reuse and makes our code more complex.
110 110
111 111 #we define platform specific fonts
112 112 if wx.Platform == '__WXMSW__':
113 113 FACES = { 'times': 'Times New Roman',
114 114 'mono' : 'Courier New',
115 115 'helv' : 'Arial',
116 116 'other': 'Comic Sans MS',
117 117 'size' : 10,
118 118 'size2': 8,
119 119 }
120 120 elif wx.Platform == '__WXMAC__':
121 121 FACES = { 'times': 'Times New Roman',
122 122 'mono' : 'Monaco',
123 123 'helv' : 'Arial',
124 124 'other': 'Comic Sans MS',
125 125 'size' : 10,
126 126 'size2': 8,
127 127 }
128 128 else:
129 129 FACES = { 'times': 'Times',
130 130 'mono' : 'Courier',
131 131 'helv' : 'Helvetica',
132 132 'other': 'new century schoolbook',
133 133 'size' : 10,
134 134 'size2': 8,
135 135 }
136 136
137 137
138 138 #-------------------------------------------------------------------------------
139 139 # The console widget class
140 140 #-------------------------------------------------------------------------------
141 141 class ConsoleWidget(editwindow.EditWindow):
142 142 """ Specialized styled text control view for console-like workflow.
143 143
144 144 This widget is mainly interested in dealing with the prompt and
145 145 keeping the cursor inside the editing line.
146 146 """
147 147
148 148 # This is where the title captured from the ANSI escape sequences are
149 149 # stored.
150 150 title = 'Console'
151 151
152 152 # Last prompt printed
153 153 last_prompt = ''
154 154
155 155 # The buffer being edited.
156 156 def _set_input_buffer(self, string):
157 157 self.SetSelection(self.current_prompt_pos, self.GetLength())
158 158 self.ReplaceSelection(string)
159 159 self.GotoPos(self.GetLength())
160 160
161 161 def _get_input_buffer(self):
162 162 """ Returns the text in current edit buffer.
163 163 """
164 164 input_buffer = self.GetTextRange(self.current_prompt_pos,
165 165 self.GetLength())
166 166 input_buffer = input_buffer.replace(LINESEP, '\n')
167 167 return input_buffer
168 168
169 169 input_buffer = property(_get_input_buffer, _set_input_buffer)
170 170
171 171 style = _DEFAULT_STYLE.copy()
172 172
173 173 # Translation table from ANSI escape sequences to color. Override
174 174 # this to specify your colors.
175 175 ANSI_STYLES = ANSI_STYLES.copy()
176 176
177 177 # Font faces
178 178 faces = FACES.copy()
179 179
180 180 # Store the last time a refresh was done
181 181 _last_refresh_time = 0
182 182
183 183 #--------------------------------------------------------------------------
184 184 # Public API
185 185 #--------------------------------------------------------------------------
186 186
187 187 def __init__(self, parent, id=wx.ID_ANY, pos=wx.DefaultPosition,
188 188 size=wx.DefaultSize, style=wx.WANTS_CHARS, ):
189 189 editwindow.EditWindow.__init__(self, parent, id, pos, size, style)
190 190 self.configure_scintilla()
191 191 # Track if 'enter' key as ever been processed
192 192 # This variable will only be reallowed until key goes up
193 193 self.enter_catched = False
194 194 self.current_prompt_pos = 0
195 195
196 196 self.Bind(wx.EVT_KEY_DOWN, self._on_key_down)
197 197 self.Bind(wx.EVT_KEY_UP, self._on_key_up)
198 198
199 199
200 200 def write(self, text, refresh=True):
201 201 """ Write given text to buffer, while translating the ansi escape
202 202 sequences.
203 203 """
204 204 # XXX: do not put print statements to sys.stdout/sys.stderr in
205 205 # this method, the print statements will call this method, as
206 206 # you will end up with an infinit loop
207 207 title = self.title_pat.split(text)
208 208 if len(title)>1:
209 209 self.title = title[-2]
210 210
211 211 text = self.title_pat.sub('', text)
212 212 segments = self.color_pat.split(text)
213 213 segment = segments.pop(0)
214 214 self.GotoPos(self.GetLength())
215 215 self.StartStyling(self.GetLength(), 0xFF)
216 216 try:
217 217 self.AppendText(segment)
218 218 except UnicodeDecodeError:
219 219 # XXX: Do I really want to skip the exception?
220 220 pass
221 221
222 222 if segments:
223 223 for ansi_tag, text in zip(segments[::2], segments[1::2]):
224 224 self.StartStyling(self.GetLength(), 0xFF)
225 225 try:
226 226 self.AppendText(text)
227 227 except UnicodeDecodeError:
228 228 # XXX: Do I really want to skip the exception?
229 229 pass
230 230
231 231 if ansi_tag not in self.ANSI_STYLES:
232 232 style = 0
233 233 else:
234 234 style = self.ANSI_STYLES[ansi_tag][0]
235 235
236 236 self.SetStyling(len(text), style)
237 237
238 238 self.GotoPos(self.GetLength())
239 239 if refresh:
240 240 current_time = time.time()
241 241 if current_time - self._last_refresh_time > 0.03:
242 242 if sys.platform == 'win32':
243 243 wx.SafeYield()
244 244 else:
245 245 wx.Yield()
246 246 # self.ProcessEvent(wx.PaintEvent())
247 247 self._last_refresh_time = current_time
248 248
249 249
250 250 def new_prompt(self, prompt):
251 251 """ Prints a prompt at start of line, and move the start of the
252 252 current block there.
253 253
254 254 The prompt can be given with ascii escape sequences.
255 255 """
256 256 self.write(prompt, refresh=False)
257 257 # now we update our cursor giving end of prompt
258 258 self.current_prompt_pos = self.GetLength()
259 259 self.current_prompt_line = self.GetCurrentLine()
260 260 self.EnsureCaretVisible()
261 261 self.last_prompt = prompt
262 262
263 263
264 264 def continuation_prompt(self):
265 265 """ Returns the current continuation prompt.
266 266 We need to implement this method here to deal with the
267 267 ascii escape sequences cleaning up.
268 268 """
269 269 # ASCII-less prompt
270 270 ascii_less = ''.join(self.color_pat.split(self.last_prompt)[2::2])
271 271 return "."*(len(ascii_less)-2) + ': '
272 272
273 273
274 274 def scroll_to_bottom(self):
275 275 maxrange = self.GetScrollRange(wx.VERTICAL)
276 276 self.ScrollLines(maxrange)
277 277
278 278
279 279 def pop_completion(self, possibilities, offset=0):
280 280 """ Pops up an autocompletion menu. Offset is the offset
281 281 in characters of the position at which the menu should
282 282 appear, relativ to the cursor.
283 283 """
284 284 self.AutoCompSetIgnoreCase(False)
285 285 self.AutoCompSetAutoHide(False)
286 286 self.AutoCompSetMaxHeight(len(possibilities))
287 287 self.AutoCompShow(offset, " ".join(possibilities))
288 288
289 289
290 290 def get_line_width(self):
291 291 """ Return the width of the line in characters.
292 292 """
293 293 return self.GetSize()[0]/self.GetCharWidth()
294 294
295 295
296 296 def configure_scintilla(self):
297 297 """ Set up all the styling option of the embedded scintilla
298 298 widget.
299 299 """
300 300 p = self.style.copy()
301 301
302 302 # Marker for complete buffer.
303 303 self.MarkerDefine(_COMPLETE_BUFFER_MARKER, stc.STC_MARK_BACKGROUND,
304 304 background=p['trace'])
305 305
306 306 # Marker for current input buffer.
307 307 self.MarkerDefine(_INPUT_MARKER, stc.STC_MARK_BACKGROUND,
308 308 background=p['stdout'])
309 309 # Marker for tracebacks.
310 310 self.MarkerDefine(_ERROR_MARKER, stc.STC_MARK_BACKGROUND,
311 311 background=p['stderr'])
312 312
313 313 self.SetEOLMode(stc.STC_EOL_LF)
314 314
315 315 # Ctrl"+" or Ctrl "-" can be used to zoomin/zoomout the text inside
316 316 # the widget
317 317 self.CmdKeyAssign(ord('+'), stc.STC_SCMOD_CTRL, stc.STC_CMD_ZOOMIN)
318 318 self.CmdKeyAssign(ord('-'), stc.STC_SCMOD_CTRL, stc.STC_CMD_ZOOMOUT)
319 319 # Also allow Ctrl Shift "=" for poor non US keyboard users.
320 320 self.CmdKeyAssign(ord('='), stc.STC_SCMOD_CTRL|stc.STC_SCMOD_SHIFT,
321 321 stc.STC_CMD_ZOOMIN)
322 322
323 323 # Keys: we need to clear some of the keys the that don't play
324 324 # well with a console.
325 325 self.CmdKeyClear(ord('D'), stc.STC_SCMOD_CTRL)
326 326 self.CmdKeyClear(ord('L'), stc.STC_SCMOD_CTRL)
327 327 self.CmdKeyClear(ord('T'), stc.STC_SCMOD_CTRL)
328 328 self.CmdKeyClear(ord('A'), stc.STC_SCMOD_CTRL)
329 329
330 330 self.SetEOLMode(stc.STC_EOL_CRLF)
331 331 self.SetWrapMode(stc.STC_WRAP_CHAR)
332 332 self.SetWrapMode(stc.STC_WRAP_WORD)
333 333 self.SetBufferedDraw(True)
334 334
335 335 self.SetUseAntiAliasing(p['antialiasing'])
336 336
337 337 self.SetLayoutCache(stc.STC_CACHE_PAGE)
338 338 self.SetUndoCollection(False)
339 339 self.SetUseTabs(True)
340 340 self.SetIndent(4)
341 341 self.SetTabWidth(4)
342 342
343 343 # we don't want scintilla's autocompletion to choose
344 344 # automaticaly out of a single choice list, as we pop it up
345 345 # automaticaly
346 346 self.AutoCompSetChooseSingle(False)
347 347 self.AutoCompSetMaxHeight(10)
348 348 # XXX: this doesn't seem to have an effect.
349 349 self.AutoCompSetFillUps('\n')
350 350
351 351 self.SetMargins(3, 3) #text is moved away from border with 3px
352 352 # Suppressing Scintilla margins
353 353 self.SetMarginWidth(0, 0)
354 354 self.SetMarginWidth(1, 0)
355 355 self.SetMarginWidth(2, 0)
356 356
357 357 # Xterm escape sequences
358 358 self.color_pat = re.compile('\x01?\x1b\[(.*?)m\x02?')
359 359 self.title_pat = re.compile('\x1b]0;(.*?)\x07')
360 360
361 361 # styles
362 362
363 363 self.SetCaretForeground(p['carret_color'])
364 364
365 365 background_color = p['background_color']
366 366
367 367 if 'default' in p:
368 368 if 'back' not in p['default']:
369 369 p['default'] += ',back:%s' % background_color
370 370 if 'size' not in p['default']:
371 371 p['default'] += ',size:%s' % self.faces['size']
372 372 if 'face' not in p['default']:
373 373 p['default'] += ',face:%s' % self.faces['mono']
374 374
375 375 self.StyleSetSpec(stc.STC_STYLE_DEFAULT, p['default'])
376 376 else:
377 377 self.StyleSetSpec(stc.STC_STYLE_DEFAULT,
378 378 "fore:%s,back:%s,size:%d,face:%s"
379 379 % (self.ANSI_STYLES['0;30'][1],
380 380 background_color,
381 381 self.faces['size'], self.faces['mono']))
382 382
383 383 self.StyleClearAll()
384 384
385 385 # XXX: two lines below are usefull if not using the lexer
386 386 #for style in self.ANSI_STYLES.values():
387 387 # self.StyleSetSpec(style[0], "bold,fore:%s" % style[1])
388 388
389 389 # prompt definition
390 390 self.prompt_in1 = p['prompt_in1']
391 391 self.prompt_out = p['prompt_out']
392 392
393 393 self.output_prompt_template = string.Template(self.prompt_out)
394 394 self.input_prompt_template = string.Template(self.prompt_in1)
395 395
396 396 self.StyleSetSpec(_STDOUT_STYLE, p['stdout'])
397 397 self.StyleSetSpec(_STDERR_STYLE, p['stderr'])
398 398 self.StyleSetSpec(_TRACE_STYLE, p['trace'])
399 399 self.StyleSetSpec(stc.STC_STYLE_BRACELIGHT, p['bracegood'])
400 400 self.StyleSetSpec(stc.STC_STYLE_BRACEBAD, p['bracebad'])
401 401 self.StyleSetSpec(stc.STC_P_COMMENTLINE, p['comment'])
402 402 self.StyleSetSpec(stc.STC_P_NUMBER, p['number'])
403 403 self.StyleSetSpec(stc.STC_P_STRING, p['string'])
404 404 self.StyleSetSpec(stc.STC_P_CHARACTER, p['char'])
405 405 self.StyleSetSpec(stc.STC_P_WORD, p['keyword'])
406 406 self.StyleSetSpec(stc.STC_P_WORD2, p['keyword'])
407 407 self.StyleSetSpec(stc.STC_P_TRIPLE, p['triple'])
408 408 self.StyleSetSpec(stc.STC_P_TRIPLEDOUBLE, p['tripledouble'])
409 409 self.StyleSetSpec(stc.STC_P_CLASSNAME, p['class'])
410 410 self.StyleSetSpec(stc.STC_P_DEFNAME, p['def'])
411 411 self.StyleSetSpec(stc.STC_P_OPERATOR, p['operator'])
412 412 self.StyleSetSpec(stc.STC_P_COMMENTBLOCK, p['comment'])
413 413
414 414 edge_column = p['edge_column']
415 415 if edge_column is not None and edge_column > 0:
416 416 #we add a vertical line to console widget
417 417 self.SetEdgeMode(stc.STC_EDGE_LINE)
418 418 self.SetEdgeColumn(edge_column)
419 419
420 420
421 421 #--------------------------------------------------------------------------
422 422 # EditWindow API
423 423 #--------------------------------------------------------------------------
424 424
425 425 def OnUpdateUI(self, event):
426 426 """ Override the OnUpdateUI of the EditWindow class, to prevent
427 427 syntax highlighting both for faster redraw, and for more
428 428 consistent look and feel.
429 429 """
430 430
431 431
432 432 #--------------------------------------------------------------------------
433 433 # Private API
434 434 #--------------------------------------------------------------------------
435 435
436 436 def _on_key_down(self, event, skip=True):
437 437 """ Key press callback used for correcting behavior for
438 438 console-like interfaces: the cursor is constraint to be after
439 439 the last prompt.
440 440
441 441 Return True if event as been catched.
442 442 """
443 443 catched = True
444 444 # XXX: Would the right way to do this be to have a
445 445 # dictionary at the instance level associating keys with
446 446 # callbacks? How would we deal with inheritance? And Do the
447 447 # different callbacks share local variables?
448 448
449 449 # Intercept some specific keys.
450 if event.KeyCode == ord('L') and event.ControlDown() :
450 key_code = event.GetKeyCode()
451 if key_code == ord('L') and event.ControlDown() :
451 452 self.scroll_to_bottom()
452 elif event.KeyCode == ord('K') and event.ControlDown() :
453 elif key_code == ord('K') and event.ControlDown() :
453 454 self.input_buffer = ''
454 elif event.KeyCode == ord('A') and event.ControlDown() :
455 elif key_code == ord('A') and event.ControlDown() :
455 456 self.GotoPos(self.GetLength())
456 457 self.SetSelectionStart(self.current_prompt_pos)
457 458 self.SetSelectionEnd(self.GetCurrentPos())
458 459 catched = True
459 elif event.KeyCode == ord('E') and event.ControlDown() :
460 elif key_code == ord('E') and event.ControlDown() :
460 461 self.GotoPos(self.GetLength())
461 462 catched = True
462 elif event.KeyCode == wx.WXK_PAGEUP:
463 elif key_code == wx.WXK_PAGEUP:
463 464 self.ScrollPages(-1)
464 elif event.KeyCode == wx.WXK_PAGEDOWN:
465 elif key_code == wx.WXK_PAGEDOWN:
465 466 self.ScrollPages(1)
466 elif event.KeyCode == wx.WXK_HOME:
467 elif key_code == wx.WXK_HOME:
467 468 self.GotoPos(self.GetLength())
468 elif event.KeyCode == wx.WXK_END:
469 elif key_code == wx.WXK_END:
469 470 self.GotoPos(self.GetLength())
470 elif event.KeyCode == wx.WXK_UP and event.ShiftDown():
471 elif key_code == wx.WXK_UP and event.ShiftDown():
471 472 self.ScrollLines(-1)
472 elif event.KeyCode == wx.WXK_DOWN and event.ShiftDown():
473 elif key_code == wx.WXK_DOWN and event.ShiftDown():
473 474 self.ScrollLines(1)
474 475 else:
475 476 catched = False
476 477
477 478 if self.AutoCompActive():
478 479 event.Skip()
479 480 else:
480 if event.KeyCode in (13, wx.WXK_NUMPAD_ENTER) and \
481 event.Modifiers in (wx.MOD_NONE, wx.MOD_WIN,
482 wx.MOD_SHIFT):
481 if key_code in (13, wx.WXK_NUMPAD_ENTER):
482 # XXX: not catching modifiers, to be wx2.6-compatible
483 483 catched = True
484 484 if not self.enter_catched:
485 485 self.CallTipCancel()
486 if event.Modifiers == wx.MOD_SHIFT:
486 if event.ShiftDown():
487 487 # Try to force execution
488 488 self.GotoPos(self.GetLength())
489 489 self.write('\n' + self.continuation_prompt(),
490 490 refresh=False)
491 491 self._on_enter()
492 492 else:
493 493 self._on_enter()
494 494 self.enter_catched = True
495 495
496 elif event.KeyCode == wx.WXK_HOME:
497 if event.Modifiers in (wx.MOD_NONE, wx.MOD_WIN):
496 elif key_code == wx.WXK_HOME:
497 if not event.ShiftDown():
498 498 self.GotoPos(self.current_prompt_pos)
499 499 catched = True
500
501 elif event.Modifiers == wx.MOD_SHIFT:
500 else:
502 501 # FIXME: This behavior is not ideal: if the selection
503 502 # is already started, it will jump.
504 503 self.SetSelectionStart(self.current_prompt_pos)
505 504 self.SetSelectionEnd(self.GetCurrentPos())
506 505 catched = True
507 506
508 elif event.KeyCode == wx.WXK_UP:
507 elif key_code == wx.WXK_UP:
509 508 if self.GetCurrentLine() > self.current_prompt_line:
510 509 if self.GetCurrentLine() == self.current_prompt_line + 1 \
511 510 and self.GetColumn(self.GetCurrentPos()) < \
512 511 self.GetColumn(self.current_prompt_pos):
513 512 self.GotoPos(self.current_prompt_pos)
514 513 else:
515 514 event.Skip()
516 515 catched = True
517 516
518 elif event.KeyCode in (wx.WXK_LEFT, wx.WXK_BACK):
517 elif key_code in (wx.WXK_LEFT, wx.WXK_BACK):
519 518 if not self._keep_cursor_in_buffer(self.GetCurrentPos() - 1):
520 519 event.Skip()
521 520 catched = True
522 521
523 elif event.KeyCode == wx.WXK_RIGHT:
522 elif key_code == wx.WXK_RIGHT:
524 523 if not self._keep_cursor_in_buffer(self.GetCurrentPos() + 1):
525 524 event.Skip()
526 525 catched = True
527 526
528 527
529 elif event.KeyCode == wx.WXK_DELETE:
528 elif key_code == wx.WXK_DELETE:
530 529 if not self._keep_cursor_in_buffer(self.GetCurrentPos() - 1):
531 530 event.Skip()
532 531 catched = True
533 532
534 533 if skip and not catched:
535 534 # Put the cursor back in the edit region
536 535 if not self._keep_cursor_in_buffer():
537 536 if not (self.GetCurrentPos() == self.GetLength()
538 and event.KeyCode == wx.WXK_DELETE):
537 and key_code == wx.WXK_DELETE):
539 538 event.Skip()
540 539 catched = True
541 540
542 541 return catched
543 542
544 543
545 544 def _on_key_up(self, event, skip=True):
546 545 """ If cursor is outside the editing region, put it back.
547 546 """
548 547 if skip:
549 548 event.Skip()
550 549 self._keep_cursor_in_buffer()
551 550
552 551
553 552 # XXX: I need to avoid the problem of having an empty glass;
554 553 def _keep_cursor_in_buffer(self, pos=None):
555 554 """ Checks if the cursor is where it is allowed to be. If not,
556 555 put it back.
557 556
558 557 Returns
559 558 -------
560 559 cursor_moved: Boolean
561 560 whether or not the cursor was moved by this routine.
562 561
563 562 Notes
564 563 ------
565 564 WARNING: This does proper checks only for horizontal
566 565 movements.
567 566 """
568 567 if pos is None:
569 568 current_pos = self.GetCurrentPos()
570 569 else:
571 570 current_pos = pos
572 571 if current_pos < self.current_prompt_pos:
573 572 self.GotoPos(self.current_prompt_pos)
574 573 return True
575 574 line_num = self.LineFromPosition(current_pos)
576 575 if not current_pos > self.GetLength():
577 576 line_pos = self.GetColumn(current_pos)
578 577 else:
579 578 line_pos = self.GetColumn(self.GetLength())
580 579 line = self.GetLine(line_num)
581 580 # Jump the continuation prompt
582 581 continuation_prompt = self.continuation_prompt()
583 582 if ( line.startswith(continuation_prompt)
584 583 and line_pos < len(continuation_prompt)):
585 584 if line_pos < 2:
586 585 # We are at the beginning of the line, trying to move
587 586 # forward: jump forward.
588 587 self.GotoPos(current_pos + 1 +
589 588 len(continuation_prompt) - line_pos)
590 589 else:
591 590 # Jump back up
592 591 self.GotoPos(self.GetLineEndPosition(line_num-1))
593 592 return True
594 593 elif ( current_pos > self.GetLineEndPosition(line_num)
595 594 and not current_pos == self.GetLength()):
596 595 # Jump to next line
597 596 self.GotoPos(current_pos + 1 +
598 597 len(continuation_prompt))
599 598 return True
600 599
601 600 # We re-allow enter event processing
602 601 self.enter_catched = False
603 602 return False
604 603
605 604
606 605 if __name__ == '__main__':
607 606 # Some simple code to test the console widget.
608 607 class MainWindow(wx.Frame):
609 608 def __init__(self, parent, id, title):
610 609 wx.Frame.__init__(self, parent, id, title, size=(300, 250))
611 610 self._sizer = wx.BoxSizer(wx.VERTICAL)
612 611 self.console_widget = ConsoleWidget(self)
613 612 self._sizer.Add(self.console_widget, 1, wx.EXPAND)
614 613 self.SetSizer(self._sizer)
615 614 self.SetAutoLayout(1)
616 615 self.Show(True)
617 616
618 617 app = wx.PySimpleApp()
619 618 w = MainWindow(None, wx.ID_ANY, 'ConsoleWidget')
620 619 w.SetSize((780, 460))
621 620 w.Show()
622 621
623 622 app.MainLoop()
624 623
625 624
@@ -1,602 +1,602 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 import sys
29 29 from threading import Lock
30 30
31 31 import wx
32 32 from wx import stc
33 33
34 34 # Ipython-specific imports.
35 35 from IPython.frontend.process import PipedProcess
36 36 from console_widget import ConsoleWidget, _COMPLETE_BUFFER_MARKER, \
37 37 _ERROR_MARKER, _INPUT_MARKER
38 38 from IPython.frontend.prefilterfrontend import PrefilterFrontEnd
39 39
40 40 #-------------------------------------------------------------------------------
41 41 # Classes to implement the Wx frontend
42 42 #-------------------------------------------------------------------------------
43 43 class WxController(ConsoleWidget, PrefilterFrontEnd):
44 44 """Classes to provide a Wx frontend to the
45 45 IPython.kernel.core.interpreter.
46 46
47 47 This class inherits from ConsoleWidget, that provides a console-like
48 48 widget to provide a text-rendering widget suitable for a terminal.
49 49 """
50 50
51 51 # Print debug info on what is happening to the console.
52 52 debug = False
53 53
54 54 # The title of the terminal, as captured through the ANSI escape
55 55 # sequences.
56 56 def _set_title(self, title):
57 57 return self.Parent.SetTitle(title)
58 58
59 59 def _get_title(self):
60 60 return self.Parent.GetTitle()
61 61
62 62 title = property(_get_title, _set_title)
63 63
64 64
65 65 # The buffer being edited.
66 66 # We are duplicating the definition here because of multiple
67 67 # inheritence
68 68 def _set_input_buffer(self, string):
69 69 ConsoleWidget._set_input_buffer(self, string)
70 70 self._colorize_input_buffer()
71 71
72 72 def _get_input_buffer(self):
73 73 """ Returns the text in current edit buffer.
74 74 """
75 75 return ConsoleWidget._get_input_buffer(self)
76 76
77 77 input_buffer = property(_get_input_buffer, _set_input_buffer)
78 78
79 79
80 80 #--------------------------------------------------------------------------
81 81 # Private Attributes
82 82 #--------------------------------------------------------------------------
83 83
84 84 # A flag governing the behavior of the input. Can be:
85 85 #
86 86 # 'readline' for readline-like behavior with a prompt
87 87 # and an edit buffer.
88 88 # 'raw_input' similar to readline, but triggered by a raw-input
89 89 # call. Can be used by subclasses to act differently.
90 90 # 'subprocess' for sending the raw input directly to a
91 91 # subprocess.
92 92 # 'buffering' for buffering of the input, that will be used
93 93 # when the input state switches back to another state.
94 94 _input_state = 'readline'
95 95
96 96 # Attribute to store reference to the pipes of a subprocess, if we
97 97 # are running any.
98 98 _running_process = False
99 99
100 100 # A queue for writing fast streams to the screen without flooding the
101 101 # event loop
102 102 _out_buffer = []
103 103
104 104 # A lock to lock the _out_buffer to make sure we don't empty it
105 105 # while it is being swapped
106 106 _out_buffer_lock = Lock()
107 107
108 108 # The different line markers used to higlight the prompts.
109 109 _markers = dict()
110 110
111 111 #--------------------------------------------------------------------------
112 112 # Public API
113 113 #--------------------------------------------------------------------------
114 114
115 115 def __init__(self, parent, id=wx.ID_ANY, pos=wx.DefaultPosition,
116 116 size=wx.DefaultSize,
117 117 style=wx.CLIP_CHILDREN|wx.WANTS_CHARS,
118 118 styledef=None,
119 119 *args, **kwds):
120 120 """ Create Shell instance.
121 121
122 122 Parameters
123 123 -----------
124 124 styledef : dict, optional
125 125 styledef is the dictionary of options used to define the
126 126 style.
127 127 """
128 128 if styledef is not None:
129 129 self.style = styledef
130 130 ConsoleWidget.__init__(self, parent, id, pos, size, style)
131 131 PrefilterFrontEnd.__init__(self, **kwds)
132 132
133 133 # Stick in our own raw_input:
134 134 self.ipython0.raw_input = self.raw_input
135 135
136 136 # A time for flushing the write buffer
137 137 BUFFER_FLUSH_TIMER_ID = 100
138 138 self._buffer_flush_timer = wx.Timer(self, BUFFER_FLUSH_TIMER_ID)
139 139 wx.EVT_TIMER(self, BUFFER_FLUSH_TIMER_ID, self._buffer_flush)
140 140
141 141 if 'debug' in kwds:
142 142 self.debug = kwds['debug']
143 143 kwds.pop('debug')
144 144
145 145 # Inject self in namespace, for debug
146 146 if self.debug:
147 147 self.shell.user_ns['self'] = self
148 148 # Inject our own raw_input in namespace
149 149 self.shell.user_ns['raw_input'] = self.raw_input
150 150
151 151 def raw_input(self, prompt=''):
152 152 """ A replacement from python's raw_input.
153 153 """
154 154 self.new_prompt(prompt)
155 155 self._input_state = 'raw_input'
156 156 if hasattr(self, '_cursor'):
157 157 del self._cursor
158 158 self.SetCursor(wx.StockCursor(wx.CURSOR_CROSS))
159 159 self.__old_on_enter = self._on_enter
160 160 event_loop = wx.EventLoop()
161 161 def my_on_enter():
162 162 event_loop.Exit()
163 163 self._on_enter = my_on_enter
164 164 # XXX: Running a separate event_loop. Ugly.
165 165 event_loop.Run()
166 166 self._on_enter = self.__old_on_enter
167 167 self._input_state = 'buffering'
168 168 self._cursor = wx.BusyCursor()
169 169 return self.input_buffer.rstrip('\n')
170 170
171 171
172 172 def system_call(self, command_string):
173 173 self._input_state = 'subprocess'
174 174 event_loop = wx.EventLoop()
175 175 def _end_system_call():
176 176 self._input_state = 'buffering'
177 177 self._running_process = False
178 178 event_loop.Exit()
179 179
180 180 self._running_process = PipedProcess(command_string,
181 181 out_callback=self.buffered_write,
182 182 end_callback = _end_system_call)
183 183 self._running_process.start()
184 184 # XXX: Running a separate event_loop. Ugly.
185 185 event_loop.Run()
186 186 # Be sure to flush the buffer.
187 187 self._buffer_flush(event=None)
188 188
189 189
190 190 def do_calltip(self):
191 191 """ Analyse current and displays useful calltip for it.
192 192 """
193 193 if self.debug:
194 194 print >>sys.__stdout__, "do_calltip"
195 195 separators = re.compile('[\s\{\}\[\]\(\)\= ,:]')
196 196 symbol = self.input_buffer
197 197 symbol_string = separators.split(symbol)[-1]
198 198 base_symbol_string = symbol_string.split('.')[0]
199 199 if base_symbol_string in self.shell.user_ns:
200 200 symbol = self.shell.user_ns[base_symbol_string]
201 201 elif base_symbol_string in self.shell.user_global_ns:
202 202 symbol = self.shell.user_global_ns[base_symbol_string]
203 203 elif base_symbol_string in __builtin__.__dict__:
204 204 symbol = __builtin__.__dict__[base_symbol_string]
205 205 else:
206 206 return False
207 207 try:
208 208 for name in symbol_string.split('.')[1:] + ['__doc__']:
209 209 symbol = getattr(symbol, name)
210 210 self.AutoCompCancel()
211 211 # Check that the symbol can indeed be converted to a string:
212 212 symbol += ''
213 213 wx.CallAfter(self.CallTipShow, self.GetCurrentPos(), symbol)
214 214 except:
215 215 # The retrieve symbol couldn't be converted to a string
216 216 pass
217 217
218 218
219 219 def _popup_completion(self, create=False):
220 220 """ Updates the popup completion menu if it exists. If create is
221 221 true, open the menu.
222 222 """
223 223 if self.debug:
224 224 print >>sys.__stdout__, "_popup_completion"
225 225 line = self.input_buffer
226 226 if (self.AutoCompActive() and line and not line[-1] == '.') \
227 227 or create==True:
228 228 suggestion, completions = self.complete(line)
229 229 if completions:
230 230 offset = len(self._get_completion_text(line))
231 231 self.pop_completion(completions, offset=offset)
232 232 if self.debug:
233 233 print >>sys.__stdout__, completions
234 234
235 235
236 236 def buffered_write(self, text):
237 237 """ A write method for streams, that caches the stream in order
238 238 to avoid flooding the event loop.
239 239
240 240 This can be called outside of the main loop, in separate
241 241 threads.
242 242 """
243 243 self._out_buffer_lock.acquire()
244 244 self._out_buffer.append(text)
245 245 self._out_buffer_lock.release()
246 246 if not self._buffer_flush_timer.IsRunning():
247 247 wx.CallAfter(self._buffer_flush_timer.Start,
248 248 milliseconds=100, oneShot=True)
249 249
250 250
251 251 def clear_screen(self):
252 252 """ Empty completely the widget.
253 253 """
254 254 self.ClearAll()
255 255 self.new_prompt(self.input_prompt_template.substitute(
256 256 number=(self.last_result['number'] + 1)))
257 257
258 258
259 259 #--------------------------------------------------------------------------
260 260 # LineFrontEnd interface
261 261 #--------------------------------------------------------------------------
262 262
263 263 def execute(self, python_string, raw_string=None):
264 264 self._input_state = 'buffering'
265 265 self.CallTipCancel()
266 266 self._cursor = wx.BusyCursor()
267 267 if raw_string is None:
268 268 raw_string = python_string
269 269 end_line = self.current_prompt_line \
270 270 + max(1, len(raw_string.split('\n'))-1)
271 271 for i in range(self.current_prompt_line, end_line):
272 272 if i in self._markers:
273 273 self.MarkerDeleteHandle(self._markers[i])
274 274 self._markers[i] = self.MarkerAdd(i, _COMPLETE_BUFFER_MARKER)
275 275 # Use a callafter to update the display robustly under windows
276 276 def callback():
277 277 self.GotoPos(self.GetLength())
278 278 PrefilterFrontEnd.execute(self, python_string,
279 279 raw_string=raw_string)
280 280 wx.CallAfter(callback)
281 281
282 282
283 283 def execute_command(self, command, hidden=False):
284 284 """ Execute a command, not only in the model, but also in the
285 285 view.
286 286 """
287 287 # XXX: This method needs to be integrated in the base fronted
288 288 # interface
289 289 if hidden:
290 290 return self.shell.execute(command)
291 291 else:
292 292 # XXX: we are not storing the input buffer previous to the
293 293 # execution, as this forces us to run the execution
294 294 # input_buffer a yield, which is not good.
295 295 ##current_buffer = self.shell.control.input_buffer
296 296 command = command.rstrip()
297 297 if len(command.split('\n')) > 1:
298 298 # The input command is several lines long, we need to
299 299 # force the execution to happen
300 300 command += '\n'
301 301 cleaned_command = self.prefilter_input(command)
302 302 self.input_buffer = command
303 303 # Do not use wx.Yield() (aka GUI.process_events()) to avoid
304 304 # recursive yields.
305 305 self.ProcessEvent(wx.PaintEvent())
306 306 self.write('\n')
307 307 if not self.is_complete(cleaned_command + '\n'):
308 308 self._colorize_input_buffer()
309 309 self.render_error('Incomplete or invalid input')
310 310 self.new_prompt(self.input_prompt_template.substitute(
311 311 number=(self.last_result['number'] + 1)))
312 312 return False
313 313 self._on_enter()
314 314 return True
315 315
316 316
317 317 def save_output_hooks(self):
318 318 self.__old_raw_input = __builtin__.raw_input
319 319 PrefilterFrontEnd.save_output_hooks(self)
320 320
321 321 def capture_output(self):
322 322 self.SetLexer(stc.STC_LEX_NULL)
323 323 PrefilterFrontEnd.capture_output(self)
324 324 __builtin__.raw_input = self.raw_input
325 325
326 326
327 327 def release_output(self):
328 328 __builtin__.raw_input = self.__old_raw_input
329 329 PrefilterFrontEnd.release_output(self)
330 330 self.SetLexer(stc.STC_LEX_PYTHON)
331 331
332 332
333 333 def after_execute(self):
334 334 PrefilterFrontEnd.after_execute(self)
335 335 # Clear the wait cursor
336 336 if hasattr(self, '_cursor'):
337 337 del self._cursor
338 338 self.SetCursor(wx.StockCursor(wx.CURSOR_CHAR))
339 339
340 340
341 341 def show_traceback(self):
342 342 start_line = self.GetCurrentLine()
343 343 PrefilterFrontEnd.show_traceback(self)
344 344 self.ProcessEvent(wx.PaintEvent())
345 345 #wx.Yield()
346 346 for i in range(start_line, self.GetCurrentLine()):
347 347 self._markers[i] = self.MarkerAdd(i, _ERROR_MARKER)
348 348
349 349
350 350 #--------------------------------------------------------------------------
351 351 # FrontEndBase interface
352 352 #--------------------------------------------------------------------------
353 353
354 354 def render_error(self, e):
355 355 start_line = self.GetCurrentLine()
356 356 self.write('\n' + e + '\n')
357 357 for i in range(start_line, self.GetCurrentLine()):
358 358 self._markers[i] = self.MarkerAdd(i, _ERROR_MARKER)
359 359
360 360
361 361 #--------------------------------------------------------------------------
362 362 # ConsoleWidget interface
363 363 #--------------------------------------------------------------------------
364 364
365 365 def new_prompt(self, prompt):
366 366 """ Display a new prompt, and start a new input buffer.
367 367 """
368 368 self._input_state = 'readline'
369 369 ConsoleWidget.new_prompt(self, prompt)
370 370 i = self.current_prompt_line
371 371 self._markers[i] = self.MarkerAdd(i, _INPUT_MARKER)
372 372
373 373
374 374 def continuation_prompt(self, *args, **kwargs):
375 375 # Avoid multiple inheritence, be explicit about which
376 376 # parent method class gets called
377 377 return ConsoleWidget.continuation_prompt(self, *args, **kwargs)
378 378
379 379
380 380 def write(self, *args, **kwargs):
381 381 # Avoid multiple inheritence, be explicit about which
382 382 # parent method class gets called
383 383 return ConsoleWidget.write(self, *args, **kwargs)
384 384
385 385
386 386 def _on_key_down(self, event, skip=True):
387 387 """ Capture the character events, let the parent
388 388 widget handle them, and put our logic afterward.
389 389 """
390 390 # FIXME: This method needs to be broken down in smaller ones.
391 391 current_line_num = self.GetCurrentLine()
392 if event.KeyCode in (ord('c'), ord('C')) and event.ControlDown():
392 key_code = event.GetKeyCode()
393 if key_code in (ord('c'), ord('C')) and event.ControlDown():
393 394 # Capture Control-C
394 395 if self._input_state == 'subprocess':
395 396 if self.debug:
396 397 print >>sys.__stderr__, 'Killing running process'
397 398 if hasattr(self._running_process, 'process'):
398 399 self._running_process.process.kill()
399 400 elif self._input_state == 'buffering':
400 401 if self.debug:
401 402 print >>sys.__stderr__, 'Raising KeyboardInterrupt'
402 403 raise KeyboardInterrupt
403 404 # XXX: We need to make really sure we
404 405 # get back to a prompt.
405 406 elif self._input_state == 'subprocess' and (
406 ( event.KeyCode<256 and
407 not event.ControlDown() )
407 ( key_code <256 and not event.ControlDown() )
408 408 or
409 ( event.KeyCode in (ord('d'), ord('D')) and
409 ( key_code in (ord('d'), ord('D')) and
410 410 event.ControlDown())):
411 411 # We are running a process, we redirect keys.
412 412 ConsoleWidget._on_key_down(self, event, skip=skip)
413 char = chr(event.KeyCode)
413 char = chr(key_code)
414 414 # Deal with some inconsistency in wx keycodes:
415 415 if char == '\r':
416 416 char = '\n'
417 417 elif not event.ShiftDown():
418 418 char = char.lower()
419 if event.ControlDown() and event.KeyCode in (ord('d'), ord('D')):
419 if event.ControlDown() and key_code in (ord('d'), ord('D')):
420 420 char = '\04'
421 421 self._running_process.process.stdin.write(char)
422 422 self._running_process.process.stdin.flush()
423 elif event.KeyCode in (ord('('), 57, 53):
423 elif key_code in (ord('('), 57, 53):
424 424 # Calltips
425 425 event.Skip()
426 426 self.do_calltip()
427 elif self.AutoCompActive() and not event.KeyCode == ord('\t'):
427 elif self.AutoCompActive() and not key_code == ord('\t'):
428 428 event.Skip()
429 if event.KeyCode in (wx.WXK_BACK, wx.WXK_DELETE):
429 if key_code in (wx.WXK_BACK, wx.WXK_DELETE):
430 430 wx.CallAfter(self._popup_completion, create=True)
431 elif not event.KeyCode in (wx.WXK_UP, wx.WXK_DOWN, wx.WXK_LEFT,
431 elif not key_code in (wx.WXK_UP, wx.WXK_DOWN, wx.WXK_LEFT,
432 432 wx.WXK_RIGHT, wx.WXK_ESCAPE):
433 433 wx.CallAfter(self._popup_completion)
434 434 else:
435 435 # Up history
436 if event.KeyCode == wx.WXK_UP and (
437 ( current_line_num == self.current_prompt_line and
438 event.Modifiers in (wx.MOD_NONE, wx.MOD_WIN) )
439 or event.ControlDown() ):
436 if key_code == wx.WXK_UP and (
437 event.ControlDown() or
438 current_line_num == self.current_prompt_line
439 ):
440 440 new_buffer = self.get_history_previous(
441 441 self.input_buffer)
442 442 if new_buffer is not None:
443 443 self.input_buffer = new_buffer
444 444 if self.GetCurrentLine() > self.current_prompt_line:
445 445 # Go to first line, for seemless history up.
446 446 self.GotoPos(self.current_prompt_pos)
447 447 # Down history
448 448 elif event.KeyCode == wx.WXK_DOWN and (
449 ( current_line_num == self.LineCount -1 and
450 event.Modifiers in (wx.MOD_NONE, wx.MOD_WIN) )
451 or event.ControlDown() ):
449 event.ControlDown() or
450 current_line_num == self.LineCount -1
451 ):
452 452 new_buffer = self.get_history_next()
453 453 if new_buffer is not None:
454 454 self.input_buffer = new_buffer
455 455 # Tab-completion
456 456 elif event.KeyCode == ord('\t'):
457 457 current_line, current_line_num = self.CurLine
458 458 if not re.match(r'^%s\s*$' % self.continuation_prompt(),
459 459 current_line):
460 460 self.complete_current_input()
461 461 if self.AutoCompActive():
462 462 wx.CallAfter(self._popup_completion, create=True)
463 463 else:
464 464 event.Skip()
465 465 elif event.KeyCode == wx.WXK_BACK:
466 466 # If characters where erased, check if we have to
467 467 # remove a line.
468 468 # XXX: What about DEL?
469 469 # FIXME: This logics should be in ConsoleWidget, as it is
470 470 # independant of IPython
471 471 current_line, _ = self.CurLine
472 472 current_pos = self.GetCurrentPos()
473 473 current_line_num = self.LineFromPosition(current_pos)
474 474 current_col = self.GetColumn(current_pos)
475 475 len_prompt = len(self.continuation_prompt())
476 476 if ( current_line.startswith(self.continuation_prompt())
477 477 and current_col == len_prompt):
478 478 new_lines = []
479 479 for line_num, line in enumerate(
480 480 self.input_buffer.split('\n')):
481 481 if (line_num + self.current_prompt_line ==
482 482 current_line_num):
483 483 new_lines.append(line[len_prompt:])
484 484 else:
485 485 new_lines.append('\n'+line)
486 486 # The first character is '\n', due to the above
487 487 # code:
488 488 self.input_buffer = ''.join(new_lines)[1:]
489 489 self.GotoPos(current_pos - 1 - len_prompt)
490 490 else:
491 491 ConsoleWidget._on_key_down(self, event, skip=skip)
492 492 else:
493 493 ConsoleWidget._on_key_down(self, event, skip=skip)
494 494
495 495
496 496
497 497 def _on_key_up(self, event, skip=True):
498 498 """ Called when any key is released.
499 499 """
500 500 if event.KeyCode in (59, ord('.')):
501 501 # Intercepting '.'
502 502 event.Skip()
503 503 wx.CallAfter(self._popup_completion, create=True)
504 504 else:
505 505 ConsoleWidget._on_key_up(self, event, skip=skip)
506 506 # Make sure the continuation_prompts are always followed by a
507 507 # whitespace
508 508 new_lines = []
509 509 if self._input_state == 'readline':
510 510 position = self.GetCurrentPos()
511 511 continuation_prompt = self.continuation_prompt()[:-1]
512 512 for line in self.input_buffer.split('\n'):
513 513 if not line == continuation_prompt:
514 514 new_lines.append(line)
515 515 self.input_buffer = '\n'.join(new_lines)
516 516 self.GotoPos(position)
517 517
518 518
519 519 def _on_enter(self):
520 520 """ Called on return key down, in readline input_state.
521 521 """
522 522 last_line_num = self.LineFromPosition(self.GetLength())
523 523 current_line_num = self.LineFromPosition(self.GetCurrentPos())
524 524 new_line_pos = (last_line_num - current_line_num)
525 525 if self.debug:
526 526 print >>sys.__stdout__, repr(self.input_buffer)
527 527 self.write('\n', refresh=False)
528 528 # Under windows scintilla seems to be doing funny
529 529 # stuff to the line returns here, but the getter for
530 530 # input_buffer filters this out.
531 531 if sys.platform == 'win32':
532 532 self.input_buffer = self.input_buffer
533 533 old_prompt_num = self.current_prompt_pos
534 534 has_executed = PrefilterFrontEnd._on_enter(self,
535 535 new_line_pos=new_line_pos)
536 536 if old_prompt_num == self.current_prompt_pos:
537 537 # No execution has happened
538 538 self.GotoPos(self.GetLineEndPosition(current_line_num + 1))
539 539 return has_executed
540 540
541 541
542 542 #--------------------------------------------------------------------------
543 543 # EditWindow API
544 544 #--------------------------------------------------------------------------
545 545
546 546 def OnUpdateUI(self, event):
547 547 """ Override the OnUpdateUI of the EditWindow class, to prevent
548 548 syntax highlighting both for faster redraw, and for more
549 549 consistent look and feel.
550 550 """
551 551 if not self._input_state == 'readline':
552 552 ConsoleWidget.OnUpdateUI(self, event)
553 553
554 554 #--------------------------------------------------------------------------
555 555 # Private API
556 556 #--------------------------------------------------------------------------
557 557
558 558 def _buffer_flush(self, event):
559 559 """ Called by the timer to flush the write buffer.
560 560
561 561 This is always called in the mainloop, by the wx timer.
562 562 """
563 563 self._out_buffer_lock.acquire()
564 564 _out_buffer = self._out_buffer
565 565 self._out_buffer = []
566 566 self._out_buffer_lock.release()
567 567 self.write(''.join(_out_buffer), refresh=False)
568 568
569 569
570 570 def _colorize_input_buffer(self):
571 571 """ Keep the input buffer lines at a bright color.
572 572 """
573 573 if not self._input_state in ('readline', 'raw_input'):
574 574 return
575 575 end_line = self.GetCurrentLine()
576 576 if not sys.platform == 'win32':
577 577 end_line += 1
578 578 for i in range(self.current_prompt_line, end_line):
579 579 if i in self._markers:
580 580 self.MarkerDeleteHandle(self._markers[i])
581 581 self._markers[i] = self.MarkerAdd(i, _INPUT_MARKER)
582 582
583 583
584 584 if __name__ == '__main__':
585 585 class MainWindow(wx.Frame):
586 586 def __init__(self, parent, id, title):
587 587 wx.Frame.__init__(self, parent, id, title, size=(300,250))
588 588 self._sizer = wx.BoxSizer(wx.VERTICAL)
589 589 self.shell = WxController(self)
590 590 self._sizer.Add(self.shell, 1, wx.EXPAND)
591 591 self.SetSizer(self._sizer)
592 592 self.SetAutoLayout(1)
593 593 self.Show(True)
594 594
595 595 app = wx.PySimpleApp()
596 596 frame = MainWindow(None, wx.ID_ANY, 'Ipython')
597 597 frame.shell.SetFocus()
598 598 frame.SetSize((680, 460))
599 599 self = frame.shell
600 600
601 601 app.MainLoop()
602 602
General Comments 0
You need to be logged in to leave comments. Login now