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