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