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