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