##// END OF EJS Templates
Little bug correcton from last commit...
ldufrechou -
Show More
@@ -1,874 +1,874 b''
1 1 #!/usr/bin/python
2 2 # -*- coding: iso-8859-15 -*-
3 3 '''
4 4 Provides IPython WX console widgets.
5 5
6 6 @author: Laurent Dufrechou
7 7 laurent.dufrechou _at_ gmail.com
8 8 This WX widget is based on the original work of Eitan Isaacson
9 9 that provided the console for the GTK toolkit.
10 10
11 11 Original work from:
12 12 @author: Eitan Isaacson
13 13 @organization: IBM Corporation
14 14 @copyright: Copyright (c) 2007 IBM Corporation
15 15 @license: BSD
16 16
17 17 All rights reserved. This program and the accompanying materials are made
18 18 available under the terms of the BSD which accompanies this distribution, and
19 19 is available at U{http://www.opensource.org/licenses/bsd-license.php}
20 20 '''
21 21
22 22 __version__ = 0.8
23 23 __author__ = "Laurent Dufrechou"
24 24 __email__ = "laurent.dufrechou _at_ gmail.com"
25 25 __license__ = "BSD"
26 26
27 27 import wx
28 28 import wx.stc as stc
29 29
30 30 import re
31 31 import sys
32 32 import locale
33 33 from StringIO import StringIO
34 34 try:
35 35 import IPython
36 36 except Exception,e:
37 37 raise "Error importing IPython (%s)" % str(e)
38 38
39 39 from ipshell_nonblocking import NonBlockingIPShell
40 40
41 41 class WxNonBlockingIPShell(NonBlockingIPShell):
42 42 '''
43 43 An NonBlockingIPShell Thread that is WX dependent.
44 44 '''
45 45 def __init__(self, parent,
46 46 argv=[],user_ns={},user_global_ns=None,
47 47 cin=None, cout=None, cerr=None,
48 48 ask_exit_handler=None):
49 49
50 50 NonBlockingIPShell.__init__(self,argv,user_ns,user_global_ns,
51 51 cin, cout, cerr,
52 52 ask_exit_handler)
53 53
54 54 self.parent = parent
55 55
56 56 self.ask_exit_callback = ask_exit_handler
57 57 self._IP.exit = self._askExit
58 58
59 59 def addGUIShortcut(self,text,func):
60 60 wx.CallAfter(self.parent.add_button_handler,
61 61 button_info={ 'text':text,
62 62 'func':self.parent.doExecuteLine(func)})
63 63
64 64 def _askExit(self):
65 65 wx.CallAfter(self.ask_exit_callback, ())
66 66
67 67 def _afterExecute(self):
68 68 wx.CallAfter(self.parent.evtStateExecuteDone, ())
69 69
70 70
71 71 class WxConsoleView(stc.StyledTextCtrl):
72 72 '''
73 73 Specialized styled text control view for console-like workflow.
74 74 We use here a scintilla frontend thus it can be reused in any GUI that
75 75 supports scintilla with less work.
76 76
77 77 @cvar ANSI_COLORS_BLACK: Mapping of terminal colors to X11 names.
78 78 (with Black background)
79 79 @type ANSI_COLORS_BLACK: dictionary
80 80
81 81 @cvar ANSI_COLORS_WHITE: Mapping of terminal colors to X11 names.
82 82 (with White background)
83 83 @type ANSI_COLORS_WHITE: dictionary
84 84
85 85 @ivar color_pat: Regex of terminal color pattern
86 86 @type color_pat: _sre.SRE_Pattern
87 87 '''
88 88 ANSI_STYLES_BLACK={'0;30': [0,'WHITE'], '0;31': [1,'RED'],
89 89 '0;32': [2,'GREEN'], '0;33': [3,'BROWN'],
90 90 '0;34': [4,'BLUE'], '0;35': [5,'PURPLE'],
91 91 '0;36': [6,'CYAN'], '0;37': [7,'LIGHT GREY'],
92 92 '1;30': [8,'DARK GREY'], '1;31': [9,'RED'],
93 93 '1;32': [10,'SEA GREEN'], '1;33': [11,'YELLOW'],
94 94 '1;34': [12,'LIGHT BLUE'], '1;35':
95 95 [13,'MEDIUM VIOLET RED'],
96 96 '1;36': [14,'LIGHT STEEL BLUE'],'1;37': [15,'YELLOW']}
97 97
98 98 ANSI_STYLES_WHITE={'0;30': [0,'BLACK'], '0;31': [1,'RED'],
99 99 '0;32': [2,'GREEN'], '0;33': [3,'BROWN'],
100 100 '0;34': [4,'BLUE'], '0;35': [5,'PURPLE'],
101 101 '0;36': [6,'CYAN'], '0;37': [7,'LIGHT GREY'],
102 102 '1;30': [8,'DARK GREY'], '1;31': [9,'RED'],
103 103 '1;32': [10,'SEA GREEN'], '1;33': [11,'YELLOW'],
104 104 '1;34': [12,'LIGHT BLUE'], '1;35':
105 105 [13,'MEDIUM VIOLET RED'],
106 106 '1;36': [14,'LIGHT STEEL BLUE'],'1;37': [15,'YELLOW']}
107 107
108 108 def __init__(self,parent,prompt,intro="",background_color="BLACK",
109 109 pos=wx.DefaultPosition, ID = -1, size=wx.DefaultSize,
110 110 style=0, autocomplete_mode = 'IPYTHON'):
111 111 '''
112 112 Initialize console view.
113 113
114 114 @param parent: Parent widget
115 115 @param prompt: User specified prompt
116 116 @type intro: string
117 117 @param intro: User specified startup introduction string
118 118 @type intro: string
119 119 @param background_color: Can be BLACK or WHITE
120 120 @type background_color: string
121 121 @param other: init param of styledTextControl (can be used as-is)
122 122 @param autocomplete_mode: Can be 'IPYTHON' or 'STC'
123 123 'IPYTHON' show autocompletion the ipython way
124 124 'STC" show it scintilla text control way
125 125 '''
126 126 stc.StyledTextCtrl.__init__(self, parent, ID, pos, size, style)
127 127
128 128 ####### Scintilla configuration ###################################
129 129
130 130 # Ctrl + B or Ctrl + N can be used to zoomin/zoomout the text inside
131 131 # the widget
132 132 self.CmdKeyAssign(ord('B'), stc.STC_SCMOD_CTRL, stc.STC_CMD_ZOOMIN)
133 133 self.CmdKeyAssign(ord('N'), stc.STC_SCMOD_CTRL, stc.STC_CMD_ZOOMOUT)
134 134
135 135 #We draw a line at position 80
136 136 self.SetEdgeMode(stc.STC_EDGE_LINE)
137 137 self.SetEdgeColumn(80)
138 138 self.SetEdgeColour(wx.LIGHT_GREY)
139 139
140 140 #self.SetViewWhiteSpace(True)
141 141 #self.SetViewEOL(True)
142 142 self.SetEOLMode(stc.STC_EOL_CRLF)
143 143 #self.SetWrapMode(stc.STC_WRAP_CHAR)
144 144 #self.SetWrapMode(stc.STC_WRAP_WORD)
145 145 self.SetBufferedDraw(True)
146 146 #self.SetUseAntiAliasing(True)
147 147 self.SetLayoutCache(stc.STC_CACHE_PAGE)
148 148 self.SetUndoCollection(False)
149 149
150 150 self.EnsureCaretVisible()
151 151
152 152 self.SetMargins(3,3) #text is moved away from border with 3px
153 153 # Suppressing Scintilla margins
154 154 self.SetMarginWidth(0,0)
155 155 self.SetMarginWidth(1,0)
156 156 self.SetMarginWidth(2,0)
157 157
158 158 self.background_color = background_color
159 159 self.buildStyles()
160 160
161 161 self.indent = 0
162 162 self.prompt_count = 0
163 163 self.color_pat = re.compile('\x01?\x1b\[(.*?)m\x02?')
164 164
165 165 self.write(intro)
166 166 self.setPrompt(prompt)
167 167 self.showPrompt()
168 168
169 169 self.autocomplete_mode = autocomplete_mode
170 170
171 171 self.Bind(wx.EVT_KEY_DOWN, self._onKeypress)
172 172
173 173 def buildStyles(self):
174 174 #we define platform specific fonts
175 175 if wx.Platform == '__WXMSW__':
176 176 faces = { 'times': 'Times New Roman',
177 177 'mono' : 'Courier New',
178 178 'helv' : 'Arial',
179 179 'other': 'Comic Sans MS',
180 180 'size' : 10,
181 181 'size2': 8,
182 182 }
183 183 elif wx.Platform == '__WXMAC__':
184 184 faces = { 'times': 'Times New Roman',
185 185 'mono' : 'Monaco',
186 186 'helv' : 'Arial',
187 187 'other': 'Comic Sans MS',
188 188 'size' : 10,
189 189 'size2': 8,
190 190 }
191 191 else:
192 192 faces = { 'times': 'Times',
193 193 'mono' : 'Courier',
194 194 'helv' : 'Helvetica',
195 195 'other': 'new century schoolbook',
196 196 'size' : 10,
197 197 'size2': 8,
198 198 }
199 199
200 200 # make some styles
201 201 if self.background_color != "BLACK":
202 202 self.background_color = "WHITE"
203 203 self.SetCaretForeground("BLACK")
204 204 self.ANSI_STYLES = self.ANSI_STYLES_WHITE
205 205 else:
206 206 self.SetCaretForeground("WHITE")
207 207 self.ANSI_STYLES = self.ANSI_STYLES_BLACK
208 208
209 209 self.StyleSetSpec(stc.STC_STYLE_DEFAULT,
210 210 "fore:%s,back:%s,size:%d,face:%s"
211 211 % (self.ANSI_STYLES['0;30'][1],
212 212 self.background_color,
213 213 faces['size'], faces['mono']))
214 214 self.StyleClearAll()
215 215 self.StyleSetSpec(stc.STC_STYLE_BRACELIGHT,
216 216 "fore:#FF0000,back:#0000FF,bold")
217 217 self.StyleSetSpec(stc.STC_STYLE_BRACEBAD,
218 218 "fore:#000000,back:#FF0000,bold")
219 219
220 220 for style in self.ANSI_STYLES.values():
221 221 self.StyleSetSpec(style[0], "bold,fore:%s" % style[1])
222 222
223 223 #######################################################################
224 224
225 225 def setBackgroundColor(self,color):
226 226 self.background_color = color
227 227 self.buildStyles()
228 228
229 229 def getBackgroundColor(self,color):
230 230 return self.background_color
231 231
232 def asyncWrite(self, evt):
232 def asyncWrite(self, text):
233 233 '''
234 234 Write given text to buffer in an asynchroneous way.
235 235 It is used from another thread to be able to acces the GUI.
236 236 @param text: Text to append
237 237 @type text: string
238 238 '''
239 239 try:
240 240 #print >>sys.__stdout__,'entering'
241 241 wx.MutexGuiEnter()
242 242 #print >>sys.__stdout__,'locking the GUI'
243 243
244 244 #be sure not to be interrutpted before the MutexGuiLeave!
245 245 self.write(text)
246 246
247 247 #print >>sys.__stdout__,'done'
248 248
249 249 except KeyboardInterrupt:
250 250 #print >>sys.__stdout__,'got keyboard interrupt'
251 251 wx.MutexGuiLeave()
252 252 #print >>sys.__stdout__,'interrupt unlock the GUI'
253 253 raise KeyboardInterrupt
254 254 wx.MutexGuiLeave()
255 255 #print >>sys.__stdout__,'normal unlock the GUI'
256 256
257 257
258 258 def write(self, text):
259 259 '''
260 260 Write given text to buffer.
261 261
262 262 @param text: Text to append.
263 263 @type text: string
264 264 '''
265 265 segments = self.color_pat.split(text)
266 266 segment = segments.pop(0)
267 267 self.StartStyling(self.getCurrentLineEnd(),0xFF)
268 268 self.AppendText(segment)
269 269
270 270 if segments:
271 271 ansi_tags = self.color_pat.findall(text)
272 272
273 273 for tag in ansi_tags:
274 274 i = segments.index(tag)
275 275 self.StartStyling(self.getCurrentLineEnd(),0xFF)
276 276 self.AppendText(segments[i+1])
277 277
278 278 if tag != '0':
279 279 self.SetStyling(len(segments[i+1]),self.ANSI_STYLES[tag][0])
280 280
281 281 segments.pop(i)
282 282
283 283 self.moveCursor(self.getCurrentLineEnd())
284 284
285 285 def getPromptLen(self):
286 286 '''
287 287 Return the length of current prompt
288 288 '''
289 289 return len(str(self.prompt_count)) + 7
290 290
291 291 def setPrompt(self,prompt):
292 292 self.prompt = prompt
293 293
294 294 def setIndentation(self,indentation):
295 295 self.indent = indentation
296 296
297 297 def setPromptCount(self,count):
298 298 self.prompt_count = count
299 299
300 300 def showPrompt(self):
301 301 '''
302 302 Prints prompt at start of line.
303 303
304 304 @param prompt: Prompt to print.
305 305 @type prompt: string
306 306 '''
307 307 self.write(self.prompt)
308 308 #now we update the position of end of prompt
309 309 self.current_start = self.getCurrentLineEnd()
310 310
311 311 autoindent = self.indent*' '
312 312 autoindent = autoindent.replace(' ','\t')
313 313 self.write(autoindent)
314 314
315 315 def changeLine(self, text):
316 316 '''
317 317 Replace currently entered command line with given text.
318 318
319 319 @param text: Text to use as replacement.
320 320 @type text: string
321 321 '''
322 322 self.SetSelection(self.getCurrentPromptStart(),self.getCurrentLineEnd())
323 323 self.ReplaceSelection(text)
324 324 self.moveCursor(self.getCurrentLineEnd())
325 325
326 326 def getCurrentPromptStart(self):
327 327 return self.current_start
328 328
329 329 def getCurrentLineStart(self):
330 330 return self.GotoLine(self.LineFromPosition(self.GetCurrentPos()))
331 331
332 332 def getCurrentLineEnd(self):
333 333 return self.GetLength()
334 334
335 335 def getCurrentLine(self):
336 336 '''
337 337 Get text in current command line.
338 338
339 339 @return: Text of current command line.
340 340 @rtype: string
341 341 '''
342 342 return self.GetTextRange(self.getCurrentPromptStart(),
343 343 self.getCurrentLineEnd())
344 344
345 345 def moveCursorOnNewValidKey(self):
346 346 #If cursor is at wrong position put it at last line...
347 347 if self.GetCurrentPos() < self.getCurrentPromptStart():
348 348 self.GotoPos(self.getCurrentPromptStart())
349 349
350 350 def removeFromTo(self,from_pos,to_pos):
351 351 if from_pos < to_pos:
352 352 self.SetSelection(from_pos,to_pos)
353 353 self.DeleteBack()
354 354
355 355 def removeCurrentLine(self):
356 356 self.LineDelete()
357 357
358 358 def moveCursor(self,position):
359 359 self.GotoPos(position)
360 360
361 361 def getCursorPos(self):
362 362 return self.GetCurrentPos()
363 363
364 364 def selectFromTo(self,from_pos,to_pos):
365 365 self.SetSelectionStart(from_pos)
366 366 self.SetSelectionEnd(to_pos)
367 367
368 368 def writeHistory(self,history):
369 369 self.removeFromTo(self.getCurrentPromptStart(),self.getCurrentLineEnd())
370 370 self.changeLine(history)
371 371
372 372 def setCompletionMethod(self, completion):
373 373 if completion in ['IPYTHON','STC']:
374 374 self.autocomplete_mode = completion
375 375 else:
376 376 raise AttributeError
377 377
378 378 def getCompletionMethod(self, completion):
379 379 return self.autocomplete_mode
380 380
381 381 def writeCompletion(self, possibilities):
382 382 if self.autocomplete_mode == 'IPYTHON':
383 383 max_len = len(max(possibilities,key=len))
384 384 max_symbol =' '*max_len
385 385
386 386 #now we check how much symbol we can put on a line...
387 387 cursor_pos = self.getCursorPos()
388 388 test_buffer = max_symbol + ' '*4
389 389 current_lines = self.GetLineCount()
390 390
391 391 allowed_symbols = 80/len(test_buffer)
392 392 if allowed_symbols == 0:
393 393 allowed_symbols = 1
394 394
395 395 pos = 1
396 396 buf = ''
397 397 for symbol in possibilities:
398 398 #buf += symbol+'\n'#*spaces)
399 399 if pos<allowed_symbols:
400 400 spaces = max_len - len(symbol) + 4
401 401 buf += symbol+' '*spaces
402 402 pos += 1
403 403 else:
404 404 buf+=symbol+'\n'
405 405 pos = 1
406 406 self.write(buf)
407 407 else:
408 408 possibilities.sort() # Python sorts are case sensitive
409 409 self.AutoCompSetIgnoreCase(False)
410 410 self.AutoCompSetAutoHide(False)
411 411 #let compute the length ot last word
412 412 splitter = [' ','(','[','{']
413 413 last_word = self.getCurrentLine()
414 414 for breaker in splitter:
415 415 last_word = last_word.split(breaker)[-1]
416 416 self.AutoCompShow(len(last_word), " ".join(possibilities))
417 417
418 418 def _onKeypress(self, event, skip=True):
419 419 '''
420 420 Key press callback used for correcting behavior for console-like
421 421 interfaces. For example 'home' should go to prompt, not to begining of
422 422 line.
423 423
424 424 @param widget: Widget that key press accored in.
425 425 @type widget: gtk.Widget
426 426 @param event: Event object
427 427 @type event: gtk.gdk.Event
428 428
429 429 @return: Return True if event as been catched.
430 430 @rtype: boolean
431 431 '''
432 432
433 433 if not self.AutoCompActive():
434 434 if event.GetKeyCode() == wx.WXK_HOME:
435 435 if event.Modifiers == wx.MOD_NONE:
436 436 self.moveCursorOnNewValidKey()
437 437 self.moveCursor(self.getCurrentPromptStart())
438 438 return True
439 439 elif event.Modifiers == wx.MOD_SHIFT:
440 440 self.moveCursorOnNewValidKey()
441 441 self.selectFromTo(self.getCurrentPromptStart(),self.getCursorPos())
442 442 return True
443 443 else:
444 444 return False
445 445
446 446 elif event.GetKeyCode() == wx.WXK_LEFT:
447 447 if event.Modifiers == wx.MOD_NONE:
448 448 self.moveCursorOnNewValidKey()
449 449
450 450 self.moveCursor(self.getCursorPos()-1)
451 451 if self.getCursorPos() < self.getCurrentPromptStart():
452 452 self.moveCursor(self.getCurrentPromptStart())
453 453 return True
454 454
455 455 elif event.GetKeyCode() == wx.WXK_BACK:
456 456 self.moveCursorOnNewValidKey()
457 457 if self.getCursorPos() > self.getCurrentPromptStart():
458 458 event.Skip()
459 459 return True
460 460
461 461 if skip:
462 462 if event.GetKeyCode() not in [wx.WXK_PAGEUP,wx.WXK_PAGEDOWN] and event.Modifiers == wx.MOD_NONE:
463 463 self.moveCursorOnNewValidKey()
464 464
465 465 event.Skip()
466 466 return True
467 467 return False
468 468 else:
469 469 event.Skip()
470 470
471 471 def OnUpdateUI(self, evt):
472 472 # check for matching braces
473 473 braceAtCaret = -1
474 474 braceOpposite = -1
475 475 charBefore = None
476 476 caretPos = self.GetCurrentPos()
477 477
478 478 if caretPos > 0:
479 479 charBefore = self.GetCharAt(caretPos - 1)
480 480 styleBefore = self.GetStyleAt(caretPos - 1)
481 481
482 482 # check before
483 483 if charBefore and chr(charBefore) in "[]{}()" and styleBefore == stc.STC_P_OPERATOR:
484 484 braceAtCaret = caretPos - 1
485 485
486 486 # check after
487 487 if braceAtCaret < 0:
488 488 charAfter = self.GetCharAt(caretPos)
489 489 styleAfter = self.GetStyleAt(caretPos)
490 490
491 491 if charAfter and chr(charAfter) in "[]{}()" and styleAfter == stc.STC_P_OPERATOR:
492 492 braceAtCaret = caretPos
493 493
494 494 if braceAtCaret >= 0:
495 495 braceOpposite = self.BraceMatch(braceAtCaret)
496 496
497 497 if braceAtCaret != -1 and braceOpposite == -1:
498 498 self.BraceBadLight(braceAtCaret)
499 499 else:
500 500 self.BraceHighlight(braceAtCaret, braceOpposite)
501 501 #pt = self.PointFromPosition(braceOpposite)
502 502 #self.Refresh(True, wxRect(pt.x, pt.y, 5,5))
503 503 #print pt
504 504 #self.Refresh(False)
505 505
506 506 class IPShellWidget(wx.Panel):
507 507 '''
508 508 This is wx.Panel that embbed the IPython Thread and the wx.StyledTextControl
509 509 If you want to port this to any other GUI toolkit, just replace the
510 510 WxConsoleView by YOURGUIConsoleView and make YOURGUIIPythonView derivate
511 511 from whatever container you want. I've choosed to derivate from a wx.Panel
512 512 because it seems to be more useful
513 513 Any idea to make it more 'generic' welcomed.
514 514 '''
515 515
516 516 def __init__(self, parent, intro=None,
517 517 background_color="BLACK", add_button_handler=None,
518 518 wx_ip_shell=None, user_ns={},user_global_ns=None,
519 519 ):
520 520 '''
521 521 Initialize.
522 522 Instanciate an IPython thread.
523 523 Instanciate a WxConsoleView.
524 524 Redirect I/O to console.
525 525 '''
526 526 wx.Panel.__init__(self,parent,wx.ID_ANY)
527 527
528 528 self.parent = parent
529 529 ### IPython non blocking shell instanciation ###
530 530 self.cout = StringIO()
531 531 self.add_button_handler = add_button_handler
532 532
533 533 if wx_ip_shell is not None:
534 534 self.IP = wx_ip_shell
535 535 else:
536 536 self.IP = WxNonBlockingIPShell(self,
537 537 cout = self.cout, cerr = self.cout,
538 538 ask_exit_handler = self.askExitCallback)
539 539
540 540 ### IPython wx console view instanciation ###
541 541 #If user didn't defined an intro text, we create one for him
542 542 #If you really wnat an empty intro just call wxIPythonViewPanel
543 543 #with intro=''
544 544 if intro is None:
545 545 welcome_text = "Welcome to WxIPython Shell.\n\n"
546 546 welcome_text+= self.IP.getBanner()
547 547 welcome_text+= "!command -> Execute command in shell\n"
548 548 welcome_text+= "TAB -> Autocompletion\n"
549 549 else:
550 550 welcome_text = intro
551 551
552 552 self.text_ctrl = WxConsoleView(self,
553 553 self.IP.getPrompt(),
554 554 intro=welcome_text,
555 555 background_color=background_color)
556 556
557 557 self.cout.write = self.text_ctrl.asyncWrite
558 558
559 559 option_text = wx.StaticText(self, -1, "Options:")
560 560 self.completion_option = wx.CheckBox(self, -1, "Scintilla Completion")
561 561 #self.completion_option.SetValue(False)
562 562 self.background_option = wx.CheckBox(self, -1, "White Background")
563 563 #self.background_option.SetValue(False)
564 564
565 565 self.options={'completion':{'value':'IPYTHON',
566 566 'checkbox':self.completion_option,'STC':True,'IPYTHON':False,
567 567 'setfunc':self.text_ctrl.setCompletionMethod},
568 568 'background_color':{'value':'BLACK',
569 569 'checkbox':self.background_option,'WHITE':True,'BLACK':False,
570 570 'setfunc':self.text_ctrl.setBackgroundColor},
571 571 }
572 572 self.reloadOptions(self.options)
573 573
574 574 self.text_ctrl.Bind(wx.EVT_KEY_DOWN, self.keyPress)
575 575 self.completion_option.Bind(wx.EVT_CHECKBOX, self.evtCheckOptionCompletion)
576 576 self.background_option.Bind(wx.EVT_CHECKBOX, self.evtCheckOptionBackgroundColor)
577 577
578 578 ### making the layout of the panel ###
579 579 sizer = wx.BoxSizer(wx.VERTICAL)
580 580 sizer.Add(self.text_ctrl, 1, wx.EXPAND)
581 581 option_sizer = wx.BoxSizer(wx.HORIZONTAL)
582 582 sizer.Add(option_sizer, 0)
583 583 option_sizer.AddMany([(10, 20),
584 584 (option_text, 0, wx.ALIGN_CENTER_VERTICAL),
585 585 (5, 5),
586 586 (self.completion_option, 0, wx.ALIGN_CENTER_VERTICAL),
587 587 (8, 8),
588 588 (self.background_option, 0, wx.ALIGN_CENTER_VERTICAL)
589 589 ])
590 590 self.SetAutoLayout(True)
591 591 sizer.Fit(self)
592 592 sizer.SetSizeHints(self)
593 593 self.SetSizer(sizer)
594 594 #and we focus on the widget :)
595 595 self.SetFocus()
596 596
597 597 #widget state management (for key handling different cases)
598 598 self.setCurrentState('IDLE')
599 599 self.pager_state = 'DONE'
600 600 self.raw_input_current_line = 0
601 601
602 602 def askExitCallback(self, event):
603 603 self.askExitHandler(event)
604 604
605 605 #---------------------- IPython Thread Management ------------------------
606 606 def stateDoExecuteLine(self):
607 607 lines=self.text_ctrl.getCurrentLine()
608 608 self.text_ctrl.write('\n')
609 609 lines_to_execute = lines.replace('\t',' '*4)
610 610 lines_to_execute = lines_to_execute.replace('\r','')
611 611 self.IP.doExecute(lines_to_execute.encode('cp1252'))
612 612 self.updateHistoryTracker(lines)
613 613 self.setCurrentState('WAIT_END_OF_EXECUTION')
614 614
615 615 def evtStateExecuteDone(self,evt):
616 616 self.doc = self.IP.getDocText()
617 617 self.help = self.IP.getHelpText()
618 618 if self.doc:
619 619 self.pager_lines = self.doc[7:].split('\n')
620 620 self.pager_state = 'INIT'
621 621 self.setCurrentState('SHOW_DOC')
622 622 self.pager(self.doc)
623 623 elif self.help:
624 624 self.pager_lines = self.help.split('\n')
625 625 self.pager_state = 'INIT'
626 626 self.setCurrentState('SHOW_DOC')
627 627 self.pager(self.help)
628 628 else:
629 629 self.stateShowPrompt()
630 630
631 631 def stateShowPrompt(self):
632 632 self.setCurrentState('SHOW_PROMPT')
633 633 self.text_ctrl.setPrompt(self.IP.getPrompt())
634 634 self.text_ctrl.setIndentation(self.IP.getIndentation())
635 635 self.text_ctrl.setPromptCount(self.IP.getPromptCount())
636 636 self.text_ctrl.showPrompt()
637 637 self.IP.initHistoryIndex()
638 638 self.setCurrentState('IDLE')
639 639
640 640 def setCurrentState(self, state):
641 641 self.cur_state = state
642 642 self.updateStatusTracker(self.cur_state)
643 643
644 644 def pager(self,text):
645 645
646 646 if self.pager_state == 'INIT':
647 647 #print >>sys.__stdout__,"PAGER state:",self.pager_state
648 648 self.pager_nb_lines = len(self.pager_lines)
649 649 self.pager_index = 0
650 650 self.pager_do_remove = False
651 651 self.text_ctrl.write('\n')
652 652 self.pager_state = 'PROCESS_LINES'
653 653
654 654 if self.pager_state == 'PROCESS_LINES':
655 655 #print >>sys.__stdout__,"PAGER state:",self.pager_state
656 656 if self.pager_do_remove == True:
657 657 self.text_ctrl.removeCurrentLine()
658 658 self.pager_do_remove = False
659 659
660 660 if self.pager_nb_lines > 10:
661 661 #print >>sys.__stdout__,"PAGER processing 10 lines"
662 662 if self.pager_index > 0:
663 663 self.text_ctrl.write(">\x01\x1b[1;36m\x02"+self.pager_lines[self.pager_index]+'\n')
664 664 else:
665 665 self.text_ctrl.write("\x01\x1b[1;36m\x02 "+self.pager_lines[self.pager_index]+'\n')
666 666
667 667 for line in self.pager_lines[self.pager_index+1:self.pager_index+9]:
668 668 self.text_ctrl.write("\x01\x1b[1;36m\x02 "+line+'\n')
669 669 self.pager_index += 10
670 670 self.pager_nb_lines -= 10
671 671 self.text_ctrl.write("--- Push Enter to continue or 'Q' to quit---")
672 672 self.pager_do_remove = True
673 673 self.pager_state = 'WAITING'
674 674 return
675 675 else:
676 676 #print >>sys.__stdout__,"PAGER processing last lines"
677 677 if self.pager_nb_lines > 0:
678 678 if self.pager_index > 0:
679 679 self.text_ctrl.write(">\x01\x1b[1;36m\x02"+self.pager_lines[self.pager_index]+'\n')
680 680 else:
681 681 self.text_ctrl.write("\x01\x1b[1;36m\x02 "+self.pager_lines[self.pager_index]+'\n')
682 682
683 683 self.pager_index += 1
684 684 self.pager_nb_lines -= 1
685 685 if self.pager_nb_lines > 0:
686 686 for line in self.pager_lines[self.pager_index:]:
687 687 self.text_ctrl.write("\x01\x1b[1;36m\x02 "+line+'\n')
688 688 self.pager_nb_lines = 0
689 689 self.pager_state = 'DONE'
690 690 self.stateShowPrompt()
691 691
692 692 #------------------------ Key Handler ------------------------------------
693 693 def keyPress(self, event):
694 694 '''
695 695 Key press callback with plenty of shell goodness, like history,
696 696 autocompletions, etc.
697 697 '''
698 698 if event.GetKeyCode() == ord('C'):
699 699 if event.Modifiers == wx.MOD_CONTROL or event.Modifiers == wx.MOD_ALT:
700 700 if self.cur_state == 'WAIT_END_OF_EXECUTION':
701 701 #we raise an exception inside the IPython thread container
702 702 self.IP.ce.raise_exc(KeyboardInterrupt)
703 703 return
704 704
705 705 #let this before 'wx.WXK_RETURN' because we have to put 'IDLE'
706 706 #mode if AutoComp has been set as inactive
707 707 if self.cur_state == 'COMPLETING':
708 708 if not self.text_ctrl.AutoCompActive():
709 709 self.cur_state = 'IDLE'
710 710 else:
711 711 event.Skip()
712 712
713 713 if event.KeyCode == wx.WXK_RETURN:
714 714 if self.cur_state == 'IDLE':
715 715 #we change the state ot the state machine
716 716 self.setCurrentState('DO_EXECUTE_LINE')
717 717 self.stateDoExecuteLine()
718 718 return
719 719
720 720 if self.pager_state == 'WAITING':
721 721 self.pager_state = 'PROCESS_LINES'
722 722 self.pager(self.doc)
723 723 return
724 724
725 725 if self.cur_state == 'WAITING_USER_INPUT':
726 726 line=self.text_ctrl.getCurrentLine()
727 727 self.text_ctrl.write('\n')
728 728 self.setCurrentState('WAIT_END_OF_EXECUTION')
729 729 return
730 730
731 731 if event.GetKeyCode() in [ord('q'),ord('Q')]:
732 732 if self.pager_state == 'WAITING':
733 733 self.pager_state = 'DONE'
734 734 self.text_ctrl.write('\n')
735 735 self.stateShowPrompt()
736 736 return
737 737
738 738 if self.cur_state == 'WAITING_USER_INPUT':
739 739 event.Skip()
740 740
741 741 if self.cur_state == 'IDLE':
742 742 if event.KeyCode == wx.WXK_UP:
743 743 history = self.IP.historyBack()
744 744 self.text_ctrl.writeHistory(history)
745 745 return
746 746 if event.KeyCode == wx.WXK_DOWN:
747 747 history = self.IP.historyForward()
748 748 self.text_ctrl.writeHistory(history)
749 749 return
750 750 if event.KeyCode == wx.WXK_TAB:
751 751 #if line empty we disable tab completion
752 752 if not self.text_ctrl.getCurrentLine().strip():
753 753 self.text_ctrl.write('\t')
754 754 return
755 755 completed, possibilities = self.IP.complete(self.text_ctrl.getCurrentLine())
756 756 if len(possibilities) > 1:
757 757 if self.text_ctrl.autocomplete_mode == 'IPYTHON':
758 758 cur_slice = self.text_ctrl.getCurrentLine()
759 759 self.text_ctrl.write('\n')
760 760 self.text_ctrl.writeCompletion(possibilities)
761 761 self.text_ctrl.write('\n')
762 762
763 763 self.text_ctrl.showPrompt()
764 764 self.text_ctrl.write(cur_slice)
765 765 self.text_ctrl.changeLine(completed or cur_slice)
766 766 else:
767 767 self.cur_state = 'COMPLETING'
768 768 self.text_ctrl.writeCompletion(possibilities)
769 769 else:
770 770 self.text_ctrl.changeLine(completed or cur_slice)
771 771 return
772 772 event.Skip()
773 773
774 774 #------------------------ Option Section ---------------------------------
775 775 def evtCheckOptionCompletion(self, event):
776 776 if event.IsChecked():
777 777 self.options['completion']['value']='STC'
778 778 else:
779 779 self.options['completion']['value']='IPYTHON'
780 780 self.text_ctrl.setCompletionMethod(self.options['completion']['value'])
781 781 self.updateOptionTracker('completion',
782 782 self.options['completion']['value'])
783 783 self.text_ctrl.SetFocus()
784 784
785 785 def evtCheckOptionBackgroundColor(self, event):
786 786 if event.IsChecked():
787 787 self.options['background_color']['value']='WHITE'
788 788 else:
789 789 self.options['background_color']['value']='BLACK'
790 790 self.text_ctrl.setBackgroundColor(self.options['background_color']['value'])
791 791 self.updateOptionTracker('background_color',
792 792 self.options['background_color']['value'])
793 793 self.text_ctrl.SetFocus()
794 794
795 795 def getOptions(self):
796 796 return self.options
797 797
798 798 def reloadOptions(self,options):
799 799 self.options = options
800 800 for key in self.options.keys():
801 801 value = self.options[key]['value']
802 802 self.options[key]['checkbox'].SetValue(self.options[key][value])
803 803 self.options[key]['setfunc'](value)
804 804
805 805
806 806 #------------------------ Hook Section -----------------------------------
807 807 def updateOptionTracker(self,name,value):
808 808 '''
809 809 Default history tracker (does nothing)
810 810 '''
811 811 pass
812 812
813 813 def setOptionTrackerHook(self,func):
814 814 '''
815 815 Define a new history tracker
816 816 '''
817 817 self.updateOptionTracker = func
818 818
819 819 def updateHistoryTracker(self,command_line):
820 820 '''
821 821 Default history tracker (does nothing)
822 822 '''
823 823 pass
824 824
825 825 def setHistoryTrackerHook(self,func):
826 826 '''
827 827 Define a new history tracker
828 828 '''
829 829 self.updateHistoryTracker = func
830 830
831 831 def updateStatusTracker(self,status):
832 832 '''
833 833 Default status tracker (does nothing)
834 834 '''
835 835 pass
836 836
837 837 def setStatusTrackerHook(self,func):
838 838 '''
839 839 Define a new status tracker
840 840 '''
841 841 self.updateStatusTracker = func
842 842
843 843 def askExitHandler(self, event):
844 844 '''
845 845 Default exit handler
846 846 '''
847 847 self.text_ctrl.write('\nExit callback has not been set.')
848 848
849 849 def setAskExitHandler(self, func):
850 850 '''
851 851 Define an exit handler
852 852 '''
853 853 self.askExitHandler = func
854 854
855 855 if __name__ == '__main__':
856 856 # Some simple code to test the shell widget.
857 857 class MainWindow(wx.Frame):
858 858 def __init__(self, parent, id, title):
859 859 wx.Frame.__init__(self, parent, id, title, size=(300,250))
860 860 self._sizer = wx.BoxSizer(wx.VERTICAL)
861 861 self.shell = IPShellWidget(self)
862 862 self._sizer.Add(self.shell, 1, wx.EXPAND)
863 863 self.SetSizer(self._sizer)
864 864 self.SetAutoLayout(1)
865 865 self.Show(True)
866 866
867 867 app = wx.PySimpleApp()
868 868 frame = MainWindow(None, wx.ID_ANY, 'Ipython')
869 869 frame.SetSize((780, 460))
870 870 shell = frame.shell
871 871
872 872 app.MainLoop()
873 873
874 874
General Comments 0
You need to be logged in to leave comments. Login now