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