##// END OF EJS Templates
[gui/wx] MacOSX out of bound cursor patch from cody precod, now usable under MACOSX
ldufrechou -
Show More
@@ -1,813 +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 self.Bind(wx.EVT_KEY_DOWN, self._onKeypress, self)
221 self.Bind(wx.EVT_KEY_DOWN, self._onKeypress)
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 400 self.AutoCompSetAutoHide(False)
401 401 #let compute the length ot last word
402 402 splitter = [' ','(','[','{']
403 403 last_word = self.getCurrentLine()
404 404 for breaker in splitter:
405 405 last_word = last_word.split(breaker)[-1]
406 406 self.AutoCompShow(len(last_word), " ".join(possibilities))
407 407
408 408 def _onKeypress(self, event, skip=True):
409 409 '''
410 410 Key press callback used for correcting behavior for console-like
411 411 interfaces. For example 'home' should go to prompt, not to begining of
412 412 line.
413 413
414 414 @param widget: Widget that key press accored in.
415 415 @type widget: gtk.Widget
416 416 @param event: Event object
417 417 @type event: gtk.gdk.Event
418 418
419 419 @return: Return True if event as been catched.
420 420 @rtype: boolean
421 421 '''
422 422
423 423 if not self.AutoCompActive():
424 424 if event.GetKeyCode() == wx.WXK_HOME:
425 425 if event.Modifiers == wx.MOD_NONE:
426 426 self.moveCursorOnNewValidKey()
427 427 self.moveCursor(self.getCurrentPromptStart())
428 428 return True
429 429 elif event.Modifiers == wx.MOD_SHIFT:
430 430 self.moveCursorOnNewValidKey()
431 431 self.selectFromTo(self.getCurrentPromptStart(),self.getCursorPos())
432 432 return True
433 433 else:
434 434 return False
435 435
436 436 elif event.GetKeyCode() == wx.WXK_LEFT:
437 437 if event.Modifiers == wx.MOD_NONE:
438 438 self.moveCursorOnNewValidKey()
439 439
440 440 self.moveCursor(self.getCursorPos()-1)
441 441 if self.getCursorPos() < self.getCurrentPromptStart():
442 442 self.moveCursor(self.getCurrentPromptStart())
443 443 return True
444 444
445 445 elif event.GetKeyCode() == wx.WXK_BACK:
446 446 self.moveCursorOnNewValidKey()
447 447 if self.getCursorPos() > self.getCurrentPromptStart():
448 448 event.Skip()
449 449 return True
450 450
451 451 if skip:
452 452 if event.GetKeyCode() not in [wx.WXK_PAGEUP,wx.WXK_PAGEDOWN] and event.Modifiers == wx.MOD_NONE:
453 453 self.moveCursorOnNewValidKey()
454 454
455 455 event.Skip()
456 456 return True
457 457 return False
458 458 else:
459 459 event.Skip()
460 460
461 461 def OnUpdateUI(self, evt):
462 462 # check for matching braces
463 463 braceAtCaret = -1
464 464 braceOpposite = -1
465 465 charBefore = None
466 466 caretPos = self.GetCurrentPos()
467 467
468 468 if caretPos > 0:
469 469 charBefore = self.GetCharAt(caretPos - 1)
470 470 styleBefore = self.GetStyleAt(caretPos - 1)
471 471
472 472 # check before
473 473 if charBefore and chr(charBefore) in "[]{}()" and styleBefore == stc.STC_P_OPERATOR:
474 474 braceAtCaret = caretPos - 1
475 475
476 476 # check after
477 477 if braceAtCaret < 0:
478 478 charAfter = self.GetCharAt(caretPos)
479 479 styleAfter = self.GetStyleAt(caretPos)
480 480
481 481 if charAfter and chr(charAfter) in "[]{}()" and styleAfter == stc.STC_P_OPERATOR:
482 482 braceAtCaret = caretPos
483 483
484 484 if braceAtCaret >= 0:
485 485 braceOpposite = self.BraceMatch(braceAtCaret)
486 486
487 487 if braceAtCaret != -1 and braceOpposite == -1:
488 488 self.BraceBadLight(braceAtCaret)
489 489 else:
490 490 self.BraceHighlight(braceAtCaret, braceOpposite)
491 491 #pt = self.PointFromPosition(braceOpposite)
492 492 #self.Refresh(True, wxRect(pt.x, pt.y, 5,5))
493 493 #print pt
494 494 #self.Refresh(False)
495 495
496 496 class IPShellWidget(wx.Panel):
497 497 '''
498 498 This is wx.Panel that embbed the IPython Thread and the wx.StyledTextControl
499 499 If you want to port this to any other GUI toolkit, just replace the
500 500 WxConsoleView by YOURGUIConsoleView and make YOURGUIIPythonView derivate
501 501 from whatever container you want. I've choosed to derivate from a wx.Panel
502 502 because it seems to be more useful
503 503 Any idea to make it more 'generic' welcomed.
504 504 '''
505 505
506 506 def __init__(self, parent, intro=None,
507 507 background_color="BLACK", add_button_handler=None,
508 508 wx_ip_shell=None, user_ns={},user_global_ns=None,
509 509 ):
510 510 '''
511 511 Initialize.
512 512 Instanciate an IPython thread.
513 513 Instanciate a WxConsoleView.
514 514 Redirect I/O to console.
515 515 '''
516 516 wx.Panel.__init__(self,parent,wx.ID_ANY)
517 517
518 518 ### IPython non blocking shell instanciation ###
519 519 self.cout = StringIO()
520 520 self.add_button_handler = add_button_handler
521 521
522 522 if wx_ip_shell is not None:
523 523 self.IP = wx_ip_shell
524 524 else:
525 525 self.IP = WxNonBlockingIPShell(self,
526 526 cout = self.cout, cerr = self.cout,
527 527 ask_exit_handler = self.askExitCallback)
528 528
529 529 ### IPython wx console view instanciation ###
530 530 #If user didn't defined an intro text, we create one for him
531 531 #If you really wnat an empty intrp just call wxIPythonViewPanel
532 532 #with intro=''
533 533 if intro is None:
534 534 welcome_text = "Welcome to WxIPython Shell.\n\n"
535 535 welcome_text+= self.IP.getBanner()
536 536 welcome_text+= "!command -> Execute command in shell\n"
537 537 welcome_text+= "TAB -> Autocompletion\n"
538 538 else:
539 539 welcome_text = intro
540 540
541 541 self.text_ctrl = WxConsoleView(self,
542 542 self.IP.getPrompt(),
543 543 intro=welcome_text,
544 544 background_color=background_color)
545 545
546 546 self.cout.write = self.text_ctrl.asyncWrite
547 547
548 548 self.completion_option = wx.CheckBox(self, -1, "Scintilla completion")
549 549 self.completion_option.SetValue(False)
550 550 option_text = wx.StaticText(self,-1,'Options:')
551 551
552 552 self.text_ctrl.Bind(wx.EVT_KEY_DOWN, self.keyPress)
553 553 self.Bind(wx.EVT_CHECKBOX, self.evtCheckOptionCompletion, self.completion_option)
554 554
555 555 ### making the layout of the panel ###
556 556 sizer = wx.BoxSizer(wx.VERTICAL)
557 557 sizer.Add(self.text_ctrl, 1, wx.EXPAND)
558 558 option_sizer = wx.BoxSizer(wx.HORIZONTAL)
559 559 sizer.Add(option_sizer,0)
560 560 option_sizer.AddMany([(10,15),
561 561 option_text,
562 562 (20,15),
563 563 self.completion_option
564 564 ])
565 565 self.SetAutoLayout(True)
566 566 sizer.Fit(self)
567 567 sizer.SetSizeHints(self)
568 568 self.SetSizer(sizer)
569 569 #and we focus on the widget :)
570 570 self.SetFocus()
571 571
572 572 #widget state management (for key handling different cases)
573 573 self.setCurrentState('IDLE')
574 574 self.pager_state = 'DONE'
575 575 self.raw_input_current_line = 0
576 576
577 577 def askExitCallback(self, event):
578 578 self.askExitHandler(event)
579 579
580 580 #---------------------- IPython Thread Management ------------------------
581 581 def stateDoExecuteLine(self):
582 582 lines=self.text_ctrl.getCurrentLine()
583 583 self.text_ctrl.write('\n')
584 584 lines_to_execute = lines.replace('\t',' '*4)
585 585 lines_to_execute = lines_to_execute.replace('\r','')
586 586 self.IP.doExecute(lines_to_execute.encode('cp1252'))
587 587 self.updateHistoryTracker(lines)
588 588 self.setCurrentState('WAIT_END_OF_EXECUTION')
589 589
590 590 def evtStateExecuteDone(self,evt):
591 591 self.doc = self.IP.getDocText()
592 592 self.help = self.IP.getHelpText()
593 593 if self.doc:
594 594 self.pager_lines = self.doc[7:].split('\n')
595 595 self.pager_state = 'INIT'
596 596 self.setCurrentState('SHOW_DOC')
597 597 self.pager(self.doc)
598 598 elif self.help:
599 599 self.pager_lines = self.help.split('\n')
600 600 self.pager_state = 'INIT'
601 601 self.setCurrentState('SHOW_DOC')
602 602 self.pager(self.help)
603 603 else:
604 604 self.stateShowPrompt()
605 605
606 606 def stateShowPrompt(self):
607 607 self.setCurrentState('SHOW_PROMPT')
608 608 self.text_ctrl.setPrompt(self.IP.getPrompt())
609 609 self.text_ctrl.setIndentation(self.IP.getIndentation())
610 610 self.text_ctrl.setPromptCount(self.IP.getPromptCount())
611 611 self.text_ctrl.showPrompt()
612 612 self.IP.initHistoryIndex()
613 613 self.setCurrentState('IDLE')
614 614
615 615 def setCurrentState(self, state):
616 616 self.cur_state = state
617 617 self.updateStatusTracker(self.cur_state)
618 618
619 619 def pager(self,text):
620 620
621 621 if self.pager_state == 'INIT':
622 622 #print >>sys.__stdout__,"PAGER state:",self.pager_state
623 623 self.pager_nb_lines = len(self.pager_lines)
624 624 self.pager_index = 0
625 625 self.pager_do_remove = False
626 626 self.text_ctrl.write('\n')
627 627 self.pager_state = 'PROCESS_LINES'
628 628
629 629 if self.pager_state == 'PROCESS_LINES':
630 630 #print >>sys.__stdout__,"PAGER state:",self.pager_state
631 631 if self.pager_do_remove == True:
632 632 self.text_ctrl.removeCurrentLine()
633 633 self.pager_do_remove = False
634 634
635 635 if self.pager_nb_lines > 10:
636 636 #print >>sys.__stdout__,"PAGER processing 10 lines"
637 637 if self.pager_index > 0:
638 638 self.text_ctrl.write(">\x01\x1b[1;36m\x02"+self.pager_lines[self.pager_index]+'\n')
639 639 else:
640 640 self.text_ctrl.write("\x01\x1b[1;36m\x02 "+self.pager_lines[self.pager_index]+'\n')
641 641
642 642 for line in self.pager_lines[self.pager_index+1:self.pager_index+9]:
643 643 self.text_ctrl.write("\x01\x1b[1;36m\x02 "+line+'\n')
644 644 self.pager_index += 10
645 645 self.pager_nb_lines -= 10
646 646 self.text_ctrl.write("--- Push Enter to continue or 'Q' to quit---")
647 647 self.pager_do_remove = True
648 648 self.pager_state = 'WAITING'
649 649 return
650 650 else:
651 651 #print >>sys.__stdout__,"PAGER processing last lines"
652 652 if self.pager_nb_lines > 0:
653 653 if self.pager_index > 0:
654 654 self.text_ctrl.write(">\x01\x1b[1;36m\x02"+self.pager_lines[self.pager_index]+'\n')
655 655 else:
656 656 self.text_ctrl.write("\x01\x1b[1;36m\x02 "+self.pager_lines[self.pager_index]+'\n')
657 657
658 658 self.pager_index += 1
659 659 self.pager_nb_lines -= 1
660 660 if self.pager_nb_lines > 0:
661 661 for line in self.pager_lines[self.pager_index:]:
662 662 self.text_ctrl.write("\x01\x1b[1;36m\x02 "+line+'\n')
663 663 self.pager_nb_lines = 0
664 664 self.pager_state = 'DONE'
665 665 self.stateShowPrompt()
666 666
667 667 #------------------------ Key Handler ------------------------------------
668 668 def keyPress(self, event):
669 669 '''
670 670 Key press callback with plenty of shell goodness, like history,
671 671 autocompletions, etc.
672 672 '''
673 673 if event.GetKeyCode() == ord('C'):
674 674 if event.Modifiers == wx.MOD_CONTROL or event.Modifiers == wx.MOD_ALT:
675 675 if self.cur_state == 'WAIT_END_OF_EXECUTION':
676 676 #we raise an exception inside the IPython thread container
677 677 self.IP.ce.raise_exc(KeyboardInterrupt)
678 678 return
679 679
680 680 #let this before 'wx.WXK_RETURN' because we have to put 'IDLE'
681 681 #mode if AutoComp has been set as inactive
682 682 if self.cur_state == 'COMPLETING':
683 683 if not self.text_ctrl.AutoCompActive():
684 684 self.cur_state = 'IDLE'
685 685 else:
686 686 event.Skip()
687 687
688 688 if event.KeyCode == wx.WXK_RETURN:
689 689 if self.cur_state == 'IDLE':
690 690 #we change the state ot the state machine
691 691 self.setCurrentState('DO_EXECUTE_LINE')
692 692 self.stateDoExecuteLine()
693 693 return
694 694
695 695 if self.pager_state == 'WAITING':
696 696 self.pager_state = 'PROCESS_LINES'
697 697 self.pager(self.doc)
698 698 return
699 699
700 700 if self.cur_state == 'WAITING_USER_INPUT':
701 701 line=self.text_ctrl.getCurrentLine()
702 702 self.text_ctrl.write('\n')
703 703 self.setCurrentState('WAIT_END_OF_EXECUTION')
704 704 return
705 705
706 706 if event.GetKeyCode() in [ord('q'),ord('Q')]:
707 707 if self.pager_state == 'WAITING':
708 708 self.pager_state = 'DONE'
709 709 self.text_ctrl.write('\n')
710 710 self.stateShowPrompt()
711 711 return
712 712
713 713 if self.cur_state == 'WAITING_USER_INPUT':
714 714 event.Skip()
715 715
716 716 if self.cur_state == 'IDLE':
717 717 if event.KeyCode == wx.WXK_UP:
718 718 history = self.IP.historyBack()
719 719 self.text_ctrl.writeHistory(history)
720 720 return
721 721 if event.KeyCode == wx.WXK_DOWN:
722 722 history = self.IP.historyForward()
723 723 self.text_ctrl.writeHistory(history)
724 724 return
725 725 if event.KeyCode == wx.WXK_TAB:
726 726 #if line empty we disable tab completion
727 727 if not self.text_ctrl.getCurrentLine().strip():
728 728 self.text_ctrl.write('\t')
729 729 return
730 730 completed, possibilities = self.IP.complete(self.text_ctrl.getCurrentLine())
731 731 if len(possibilities) > 1:
732 732 if self.text_ctrl.autocomplete_mode == 'IPYTHON':
733 733 cur_slice = self.text_ctrl.getCurrentLine()
734 734 self.text_ctrl.write('\n')
735 735 self.text_ctrl.writeCompletion(possibilities)
736 736 self.text_ctrl.write('\n')
737 737
738 738 self.text_ctrl.showPrompt()
739 739 self.text_ctrl.write(cur_slice)
740 740 self.text_ctrl.changeLine(completed or cur_slice)
741 741 else:
742 742 self.cur_state = 'COMPLETING'
743 743 self.text_ctrl.writeCompletion(possibilities)
744 744 else:
745 745 self.text_ctrl.changeLine(completed or cur_slice)
746 746 return
747 747 event.Skip()
748 748
749 749 #------------------------ Option Section ---------------------------------
750 750 def evtCheckOptionCompletion(self, event):
751 751 if event.IsChecked():
752 752 self.text_ctrl.setCompletionMethod('STC')
753 753 else:
754 754 self.text_ctrl.setCompletionMethod('IPYTHON')
755 755 self.text_ctrl.SetFocus()
756 756
757 757 #------------------------ Hook Section -----------------------------------
758 758 def updateHistoryTracker(self,command_line):
759 759 '''
760 760 Default history tracker (does nothing)
761 761 '''
762 762 pass
763 763
764 764 def setHistoryTrackerHook(self,func):
765 765 '''
766 766 Define a new history tracker
767 767 '''
768 768 self.updateHistoryTracker = func
769 769
770 770 def updateStatusTracker(self,status):
771 771 '''
772 772 Default status tracker (does nothing)
773 773 '''
774 774 pass
775 775
776 776 def setStatusTrackerHook(self,func):
777 777 '''
778 778 Define a new status tracker
779 779 '''
780 780 self.updateStatusTracker = func
781 781
782 782 def askExitHandler(self, event):
783 783 '''
784 784 Default exit handler
785 785 '''
786 786 self.text_ctrl.write('\nExit callback has not been set.')
787 787
788 788 def setAskExitHandler(self, func):
789 789 '''
790 790 Define an exit handler
791 791 '''
792 792 self.askExitHandler = func
793 793
794 794 if __name__ == '__main__':
795 795 # Some simple code to test the shell widget.
796 796 class MainWindow(wx.Frame):
797 797 def __init__(self, parent, id, title):
798 798 wx.Frame.__init__(self, parent, id, title, size=(300,250))
799 799 self._sizer = wx.BoxSizer(wx.VERTICAL)
800 800 self.shell = IPShellWidget(self)
801 801 self._sizer.Add(self.shell, 1, wx.EXPAND)
802 802 self.SetSizer(self._sizer)
803 803 self.SetAutoLayout(1)
804 804 self.Show(True)
805 805
806 806 app = wx.PySimpleApp()
807 807 frame = MainWindow(None, wx.ID_ANY, 'Ipython')
808 808 frame.SetSize((780, 460))
809 809 shell = frame.shell
810 810
811 811 app.MainLoop()
812 812
813 813
General Comments 0
You need to be logged in to leave comments. Login now