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