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