##// END OF EJS Templates
Pager shrotcut replaced by new ville's pager hook
laurent.dufrechou -
Show More
@@ -1,1050 +1,1046 b''
1 1 #!/usr/bin/python
2 2 # -*- coding: iso-8859-15 -*-
3 3 '''
4 4 Provides IPython WX console widget.
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 os
34 34 import locale
35 35 import time
36 36 from ThreadEx import Thread
37 37 from StringIO import StringIO
38 38
39 39 try:
40 40 import IPython
41 41 except Exception,e:
42 42 raise "Error importing IPython (%s)" % str(e)
43 43
44 44 class IterableIPShell(Thread):
45 45 '''
46 46 Create an IPython instance inside a dedicated thread.
47 47 Does not start a blocking event loop, instead allow single iterations.
48 48 This allows embedding in any GUI without blockage.
49 49 The thread is a slave one, in that it doesn't interact directly with the GUI.
50 50 Note Thread class comes from ThreadEx that supports asynchroneous function call
51 51 via raise_exc()
52 52 '''
53 53
54 54 def __init__(self,argv=[],user_ns=None,user_global_ns=None,
55 55 cin=None, cout=None, cerr=None,
56 56 exit_handler=None,time_loop = 0.1):
57 57 '''
58 58 @param argv: Command line options for IPython
59 59 @type argv: list
60 60 @param user_ns: User namespace.
61 61 @type user_ns: dictionary
62 62 @param user_global_ns: User global namespace.
63 63 @type user_global_ns: dictionary.
64 64 @param cin: Console standard input.
65 65 @type cin: IO stream
66 66 @param cout: Console standard output.
67 67 @type cout: IO stream
68 68 @param cerr: Console standard error.
69 69 @type cerr: IO stream
70 70 @param exit_handler: Replacement for builtin exit() function
71 71 @type exit_handler: function
72 72 @param time_loop: Define the sleep time between two thread's loop
73 73 @type int
74 74 '''
75 75 Thread.__init__(self)
76 76
77 77 #first we redefine in/out/error functions of IPython
78 78 if cin:
79 79 IPython.Shell.Term.cin = cin
80 80 if cout:
81 81 IPython.Shell.Term.cout = cout
82 82 if cerr:
83 83 IPython.Shell.Term.cerr = cerr
84 84
85 85 # This is to get rid of the blockage that accurs during
86 86 # IPython.Shell.InteractiveShell.user_setup()
87 87 IPython.iplib.raw_input = lambda x: None
88 88
89 89 self._term = IPython.genutils.IOTerm(cin=cin, cout=cout, cerr=cerr)
90 90
91 91 excepthook = sys.excepthook
92 92 self._IP = IPython.Shell.make_IPython(
93 93 argv,user_ns=user_ns,
94 94 user_global_ns=user_global_ns,
95 95 embedded=True,
96 96 shell_class=IPython.Shell.InteractiveShell)
97 97
98 98 #we replace IPython default encoding by wx locale encoding
99 99 loc = locale.getpreferredencoding()
100 100 if loc:
101 101 self._IP.stdin_encoding = loc
102 102 #we replace the ipython default pager by our pager
103 #FIXME: add a pager callback to IPython
104 IPython.OInspect.page = self._pager
105 IPython.genutils.page = self._pager
106 IPython.iplib.page = self._pager
107 IPython.Magic.page = self._pager
103 self._IP.set_hook('show_in_pager',self._pager)
108 104
109 105 #we replace the ipython default shell command caller by our shell handler
110 106 self._IP.set_hook('shell_hook',self._shell)
111 107
112 108 #we replace the ipython default input command caller by our method
113 109 IPython.iplib.raw_input_original = self._raw_input
114 110 #we replace the ipython default exit command by our method
115 111 self._IP.exit = self._setAskExit
116 112
117 113 sys.excepthook = excepthook
118 114
119 115 self._iter_more = 0
120 116 self._history_level = 0
121 117 self._complete_sep = re.compile('[\s\{\}\[\]\(\)]')
122 118 self._prompt = str(self._IP.outputcache.prompt1).strip()
123 119
124 120 #thread working vars
125 121 self._terminate = False
126 122 self._time_loop = time_loop
127 123 self._has_doc = False
128 124 self._do_execute = False
129 125 self._line_to_execute = ''
130 126 self._doc_text = None
131 127 self._ask_exit = False
132 128
133 129 #----------------------- Thread management section ----------------------
134 130 def run (self):
135 131 """
136 132 Thread main loop
137 133 The thread will run until self._terminate will be set to True via shutdown() function
138 134 Command processing can be interrupted with Instance.raise_exc(KeyboardInterrupt) call in the
139 135 GUI thread.
140 136 """
141 137 while(not self._terminate):
142 138 try:
143 139 if self._do_execute:
144 140 self._doc_text = None
145 141 self._execute()
146 142 self._do_execute = False
147 143
148 144 except KeyboardInterrupt:
149 145 pass
150 146
151 147 time.sleep(self._time_loop)
152 148
153 149 def shutdown(self):
154 150 """
155 151 Shutdown the tread
156 152 """
157 153 self._terminate = True
158 154
159 155 def doExecute(self,line):
160 156 """
161 157 Tell the thread to process the 'line' command
162 158 """
163 159 self._do_execute = True
164 160 self._line_to_execute = line
165 161
166 162 def isExecuteDone(self):
167 163 """
168 164 Returns the processing state
169 165 """
170 166 return not self._do_execute
171 167
172 168 #----------------------- IPython management section ----------------------
173 169 def getAskExit(self):
174 170 '''
175 171 returns the _ask_exit variable that can be checked by GUI to see if
176 172 IPython request an exit handling
177 173 '''
178 174 return self._ask_exit
179 175
180 176 def clearAskExit(self):
181 177 '''
182 178 clear the _ask_exit var when GUI as handled the request.
183 179 '''
184 180 self._ask_exit = False
185 181
186 182 def getDocText(self):
187 183 """
188 184 Returns the output of the processing that need to be paged (if any)
189 185
190 186 @return: The std output string.
191 187 @rtype: string
192 188 """
193 189 return self._doc_text
194 190
195 191 def getBanner(self):
196 192 """
197 193 Returns the IPython banner for useful info on IPython instance
198 194
199 195 @return: The banner string.
200 196 @rtype: string
201 197 """
202 198 return self._IP.BANNER
203 199
204 200 def getPromptCount(self):
205 201 """
206 202 Returns the prompt number.
207 203 Each time a user execute a line in the IPython shell the prompt count is increased
208 204
209 205 @return: The prompt number
210 206 @rtype: int
211 207 """
212 208 return self._IP.outputcache.prompt_count
213 209
214 210 def getPrompt(self):
215 211 """
216 212 Returns current prompt inside IPython instance
217 213 (Can be In [...]: ot ...:)
218 214
219 215 @return: The current prompt.
220 216 @rtype: string
221 217 """
222 218 return self._prompt
223 219
224 220 def getIndentation(self):
225 221 """
226 222 Returns the current indentation level
227 223 Usefull to put the caret at the good start position if we want to do autoindentation.
228 224
229 225 @return: The indentation level.
230 226 @rtype: int
231 227 """
232 228 return self._IP.indent_current_nsp
233 229
234 230 def updateNamespace(self, ns_dict):
235 231 '''
236 232 Add the current dictionary to the shell namespace.
237 233
238 234 @param ns_dict: A dictionary of symbol-values.
239 235 @type ns_dict: dictionary
240 236 '''
241 237 self._IP.user_ns.update(ns_dict)
242 238
243 239 def complete(self, line):
244 240 '''
245 241 Returns an auto completed line and/or posibilities for completion.
246 242
247 243 @param line: Given line so far.
248 244 @type line: string
249 245
250 246 @return: Line completed as for as possible,
251 247 and possible further completions.
252 248 @rtype: tuple
253 249 '''
254 250 split_line = self._complete_sep.split(line)
255 251 possibilities = self._IP.complete(split_line[-1])
256 252 if possibilities:
257 253
258 254 def _commonPrefix(str1, str2):
259 255 '''
260 256 Reduction function. returns common prefix of two given strings.
261 257
262 258 @param str1: First string.
263 259 @type str1: string
264 260 @param str2: Second string
265 261 @type str2: string
266 262
267 263 @return: Common prefix to both strings.
268 264 @rtype: string
269 265 '''
270 266 for i in range(len(str1)):
271 267 if not str2.startswith(str1[:i+1]):
272 268 return str1[:i]
273 269 return str1
274 270 common_prefix = reduce(_commonPrefix, possibilities)
275 271 completed = line[:-len(split_line[-1])]+common_prefix
276 272 else:
277 273 completed = line
278 274 return completed, possibilities
279 275
280 276 def historyBack(self):
281 277 '''
282 278 Provides one history command back.
283 279
284 280 @return: The command string.
285 281 @rtype: string
286 282 '''
287 283 history = ''
288 284 #the below while loop is used to suppress empty history lines
289 285 while((history == '' or history == '\n') and self._history_level >0):
290 286 if self._history_level>=1:
291 287 self._history_level -= 1
292 288 history = self._getHistory()
293 289 return history
294 290
295 291 def historyForward(self):
296 292 '''
297 293 Provides one history command forward.
298 294
299 295 @return: The command string.
300 296 @rtype: string
301 297 '''
302 298 history = ''
303 299 #the below while loop is used to suppress empty history lines
304 300 while((history == '' or history == '\n') and self._history_level <= self._getHistoryMaxIndex()):
305 301 if self._history_level < self._getHistoryMaxIndex():
306 302 self._history_level += 1
307 303 history = self._getHistory()
308 304 else:
309 305 if self._history_level == self._getHistoryMaxIndex():
310 306 history = self._getHistory()
311 307 self._history_level += 1
312 308 else:
313 309 history = ''
314 310 return history
315 311
316 312 def initHistoryIndex(self):
317 313 '''
318 314 set history to last command entered
319 315 '''
320 316 self._history_level = self._getHistoryMaxIndex()+1
321 317
322 318 #----------------------- IPython PRIVATE management section ----------------------
323 319 def _setAskExit(self):
324 320 '''
325 321 set the _ask_exit variable that can be cjhecked by GUI to see if
326 322 IPython request an exit handling
327 323 '''
328 324 self._ask_exit = True
329 325
330 326 def _getHistoryMaxIndex(self):
331 327 '''
332 328 returns the max length of the history buffer
333 329
334 330 @return: history length
335 331 @rtype: int
336 332 '''
337 333 return len(self._IP.input_hist_raw)-1
338 334
339 335 def _getHistory(self):
340 336 '''
341 337 Get's the command string of the current history level.
342 338
343 339 @return: Historic command string.
344 340 @rtype: string
345 341 '''
346 342 rv = self._IP.input_hist_raw[self._history_level].strip('\n')
347 343 return rv
348 344
349 def _pager(self,text,start=0,screen_lines=0,pager_cmd = None):
345 def _pager(self,IP,text):
350 346 '''
351 347 This function is used as a callback replacment to IPython pager function
352 348
353 349 It puts the 'text' value inside the self._doc_text string that can be retrived via getDocText
354 350 function.
355 351 '''
356 352 self._doc_text = text
357 353
358 354 def _raw_input(self, prompt=''):
359 355 '''
360 356 Custom raw_input() replacement. Get's current line from console buffer.
361 357
362 358 @param prompt: Prompt to print. Here for compatability as replacement.
363 359 @type prompt: string
364 360
365 361 @return: The current command line text.
366 362 @rtype: string
367 363 '''
368 364 return self._line_to_execute
369 365
370 366 def _execute(self):
371 367 '''
372 368 Executes the current line provided by the shell object.
373 369 '''
374 370 orig_stdout = sys.stdout
375 371 sys.stdout = IPython.Shell.Term.cout
376 372
377 373 try:
378 374 line = self._IP.raw_input(None, self._iter_more)
379 375 if self._IP.autoindent:
380 376 self._IP.readline_startup_hook(None)
381 377
382 378 except KeyboardInterrupt:
383 379 self._IP.write('\nKeyboardInterrupt\n')
384 380 self._IP.resetbuffer()
385 381 # keep cache in sync with the prompt counter:
386 382 self._IP.outputcache.prompt_count -= 1
387 383
388 384 if self._IP.autoindent:
389 385 self._IP.indent_current_nsp = 0
390 386 self._iter_more = 0
391 387 except:
392 388 self._IP.showtraceback()
393 389 else:
394 390 self._iter_more = self._IP.push(line)
395 391 if (self._IP.SyntaxTB.last_syntax_error and
396 392 self._IP.rc.autoedit_syntax):
397 393 self._IP.edit_syntax_error()
398 394 if self._iter_more:
399 395 self._prompt = str(self._IP.outputcache.prompt2).strip()
400 396 if self._IP.autoindent:
401 397 self._IP.readline_startup_hook(self._IP.pre_readline)
402 398 else:
403 399 self._prompt = str(self._IP.outputcache.prompt1).strip()
404 400 self._IP.indent_current_nsp = 0 #we set indentation to 0
405 401 sys.stdout = orig_stdout
406 402
407 403 def _shell(self, ip, cmd):
408 404 '''
409 405 Replacement method to allow shell commands without them blocking.
410 406
411 407 @param ip: Ipython instance, same as self._IP
412 408 @type cmd: Ipython instance
413 409 @param cmd: Shell command to execute.
414 410 @type cmd: string
415 411 '''
416 412 stdin, stdout = os.popen4(cmd)
417 413 result = stdout.read().decode('cp437').encode(locale.getpreferredencoding())
418 414 #we use print command because the shell command is called inside IPython instance and thus is
419 415 #redirected to thread cout
420 416 #"\x01\x1b[1;36m\x02" <-- add colour to the text...
421 417 print "\x01\x1b[1;36m\x02"+result
422 418 stdout.close()
423 419 stdin.close()
424 420
425 421 class WxConsoleView(stc.StyledTextCtrl):
426 422 '''
427 423 Specialized styled text control view for console-like workflow.
428 424 We use here a scintilla frontend thus it can be reused in any GUI taht supports
429 425 scintilla with less work.
430 426
431 427 @cvar ANSI_COLORS_BLACK: Mapping of terminal colors to X11 names.(with Black background)
432 428 @type ANSI_COLORS_BLACK: dictionary
433 429
434 430 @cvar ANSI_COLORS_WHITE: Mapping of terminal colors to X11 names.(with White background)
435 431 @type ANSI_COLORS_WHITE: dictionary
436 432
437 433 @ivar color_pat: Regex of terminal color pattern
438 434 @type color_pat: _sre.SRE_Pattern
439 435 '''
440 436 ANSI_STYLES_BLACK ={'0;30': [0,'WHITE'], '0;31': [1,'RED'],
441 437 '0;32': [2,'GREEN'], '0;33': [3,'BROWN'],
442 438 '0;34': [4,'BLUE'], '0;35': [5,'PURPLE'],
443 439 '0;36': [6,'CYAN'], '0;37': [7,'LIGHT GREY'],
444 440 '1;30': [8,'DARK GREY'], '1;31': [9,'RED'],
445 441 '1;32': [10,'SEA GREEN'], '1;33': [11,'YELLOW'],
446 442 '1;34': [12,'LIGHT BLUE'], '1;35': [13,'MEDIUM VIOLET RED'],
447 443 '1;36': [14,'LIGHT STEEL BLUE'], '1;37': [15,'YELLOW']}
448 444
449 445 ANSI_STYLES_WHITE ={'0;30': [0,'BLACK'], '0;31': [1,'RED'],
450 446 '0;32': [2,'GREEN'], '0;33': [3,'BROWN'],
451 447 '0;34': [4,'BLUE'], '0;35': [5,'PURPLE'],
452 448 '0;36': [6,'CYAN'], '0;37': [7,'LIGHT GREY'],
453 449 '1;30': [8,'DARK GREY'], '1;31': [9,'RED'],
454 450 '1;32': [10,'SEA GREEN'], '1;33': [11,'YELLOW'],
455 451 '1;34': [12,'LIGHT BLUE'], '1;35': [13,'MEDIUM VIOLET RED'],
456 452 '1;36': [14,'LIGHT STEEL BLUE'], '1;37': [15,'YELLOW']}
457 453
458 454 def __init__(self,parent,prompt,intro="",background_color="BLACK",pos=wx.DefaultPosition, ID = -1, size=wx.DefaultSize,
459 455 style=0):
460 456 '''
461 457 Initialize console view.
462 458
463 459 @param parent: Parent widget
464 460 @param prompt: User specified prompt
465 461 @type intro: string
466 462 @param intro: User specified startup introduction string
467 463 @type intro: string
468 464 @param background_color: Can be BLACK or WHITE
469 465 @type background_color: string
470 466 @param other: init param of styledTextControl (can be used as-is)
471 467 '''
472 468 stc.StyledTextCtrl.__init__(self, parent, ID, pos, size, style)
473 469
474 470 ####### Scintilla configuration ##################################################
475 471
476 472 # Ctrl + B or Ctrl + N can be used to zoomin/zoomout the text inside the widget
477 473 self.CmdKeyAssign(ord('B'), stc.STC_SCMOD_CTRL, stc.STC_CMD_ZOOMIN)
478 474 self.CmdKeyAssign(ord('N'), stc.STC_SCMOD_CTRL, stc.STC_CMD_ZOOMOUT)
479 475
480 476 #we define platform specific fonts
481 477 if wx.Platform == '__WXMSW__':
482 478 faces = { 'times': 'Times New Roman',
483 479 'mono' : 'Courier New',
484 480 'helv' : 'Arial',
485 481 'other': 'Comic Sans MS',
486 482 'size' : 10,
487 483 'size2': 8,
488 484 }
489 485 elif wx.Platform == '__WXMAC__':
490 486 faces = { 'times': 'Times New Roman',
491 487 'mono' : 'Monaco',
492 488 'helv' : 'Arial',
493 489 'other': 'Comic Sans MS',
494 490 'size' : 10,
495 491 'size2': 8,
496 492 }
497 493 else:
498 494 faces = { 'times': 'Times',
499 495 'mono' : 'Courier',
500 496 'helv' : 'Helvetica',
501 497 'other': 'new century schoolbook',
502 498 'size' : 10,
503 499 'size2': 8,
504 500 }
505 501
506 502 #We draw a line at position 80
507 503 self.SetEdgeMode(stc.STC_EDGE_LINE)
508 504 self.SetEdgeColumn(80)
509 505 self.SetEdgeColour(wx.LIGHT_GREY)
510 506
511 507 #self.SetViewWhiteSpace(True)
512 508 #self.SetViewEOL(True)
513 509 self.SetEOLMode(stc.STC_EOL_CRLF)
514 510 #self.SetWrapMode(stc.STC_WRAP_CHAR)
515 511 #self.SetWrapMode(stc.STC_WRAP_WORD)
516 512 self.SetBufferedDraw(True)
517 513 #self.SetUseAntiAliasing(True)
518 514 self.SetLayoutCache(stc.STC_CACHE_PAGE)
519 515
520 516 self.EnsureCaretVisible()
521 517
522 518 self.SetMargins(3,3) #text is moved away from border with 3px
523 519 # Suppressing Scintilla margins
524 520 self.SetMarginWidth(0,0)
525 521 self.SetMarginWidth(1,0)
526 522 self.SetMarginWidth(2,0)
527 523
528 524 # make some styles
529 525 if background_color != "BLACK":
530 526 self.background_color = "WHITE"
531 527 self.SetCaretForeground("BLACK")
532 528 self.ANSI_STYLES = self.ANSI_STYLES_WHITE
533 529 else:
534 530 self.background_color = background_color
535 531 self.SetCaretForeground("WHITE")
536 532 self.ANSI_STYLES = self.ANSI_STYLES_BLACK
537 533
538 534 self.StyleSetSpec(stc.STC_STYLE_DEFAULT, "fore:%s,back:%s,size:%d,face:%s" % (self.ANSI_STYLES['0;30'][1],
539 535 self.background_color,
540 536 faces['size'], faces['mono']))
541 537 self.StyleClearAll()
542 538 self.StyleSetSpec(stc.STC_STYLE_BRACELIGHT, "fore:#FF0000,back:#0000FF,bold")
543 539 self.StyleSetSpec(stc.STC_STYLE_BRACEBAD, "fore:#000000,back:#FF0000,bold")
544 540
545 541 for style in self.ANSI_STYLES.values():
546 542 self.StyleSetSpec(style[0], "bold,fore:%s" % style[1])
547 543
548 544 #######################################################################
549 545
550 546 self.indent = 0
551 547 self.prompt_count = 0
552 548 self.color_pat = re.compile('\x01?\x1b\[(.*?)m\x02?')
553 549
554 550 self.write(intro)
555 551 self.setPrompt(prompt)
556 552 self.showPrompt()
557 553
558 554 self.Bind(wx.EVT_KEY_DOWN, self._onKeypress, self)
559 555 #self.Bind(stc.EVT_STC_UPDATEUI, self.OnUpdateUI)
560 556
561 557 def write(self, text):
562 558 '''
563 559 Write given text to buffer.
564 560
565 561 @param text: Text to append.
566 562 @type text: string
567 563 '''
568 564 segments = self.color_pat.split(text)
569 565 segment = segments.pop(0)
570 566 self.StartStyling(self.getCurrentLineEnd(),0xFF)
571 567 self.AppendText(segment)
572 568
573 569 if segments:
574 570 ansi_tags = self.color_pat.findall(text)
575 571
576 572 for tag in ansi_tags:
577 573 i = segments.index(tag)
578 574 self.StartStyling(self.getCurrentLineEnd(),0xFF)
579 575 self.AppendText(segments[i+1])
580 576
581 577 if tag != '0':
582 578 self.SetStyling(len(segments[i+1]),self.ANSI_STYLES[tag][0])
583 579
584 580 segments.pop(i)
585 581
586 582 self.moveCursor(self.getCurrentLineEnd())
587 583
588 584 def getPromptLen(self):
589 585 '''
590 586 Return the length of current prompt
591 587 '''
592 588 return len(str(self.prompt_count)) + 7
593 589
594 590 def setPrompt(self,prompt):
595 591 self.prompt = prompt
596 592
597 593 def setIndentation(self,indentation):
598 594 self.indent = indentation
599 595
600 596 def setPromptCount(self,count):
601 597 self.prompt_count = count
602 598
603 599 def showPrompt(self):
604 600 '''
605 601 Prints prompt at start of line.
606 602
607 603 @param prompt: Prompt to print.
608 604 @type prompt: string
609 605 '''
610 606 self.write(self.prompt)
611 607 #now we update the position of end of prompt
612 608 self.current_start = self.getCurrentLineEnd()
613 609
614 610 autoindent = self.indent*' '
615 611 autoindent = autoindent.replace(' ','\t')
616 612 self.write(autoindent)
617 613
618 614 def changeLine(self, text):
619 615 '''
620 616 Replace currently entered command line with given text.
621 617
622 618 @param text: Text to use as replacement.
623 619 @type text: string
624 620 '''
625 621 self.SetSelection(self.getCurrentPromptStart(),self.getCurrentLineEnd())
626 622 self.ReplaceSelection(text)
627 623 self.moveCursor(self.getCurrentLineEnd())
628 624
629 625 def getCurrentPromptStart(self):
630 626 return self.current_start
631 627
632 628 def getCurrentLineStart(self):
633 629 return self.GotoLine(self.LineFromPosition(self.GetCurrentPos()))
634 630
635 631 def getCurrentLineEnd(self):
636 632 return self.GetLength()
637 633
638 634 def getCurrentLine(self):
639 635 '''
640 636 Get text in current command line.
641 637
642 638 @return: Text of current command line.
643 639 @rtype: string
644 640 '''
645 641 return self.GetTextRange(self.getCurrentPromptStart(),
646 642 self.getCurrentLineEnd())
647 643
648 644 def showReturned(self, text):
649 645 '''
650 646 Show returned text from last command and print new prompt.
651 647
652 648 @param text: Text to show.
653 649 @type text: string
654 650 '''
655 651 self.write('\n'+text)
656 652 if text:
657 653 self.write('\n')
658 654 self.showPrompt()
659 655
660 656 def moveCursorOnNewValidKey(self):
661 657 #If cursor is at wrong position put it at last line...
662 658 if self.GetCurrentPos() < self.getCurrentPromptStart():
663 659 self.GotoPos(self.getCurrentPromptStart())
664 660
665 661 def removeFromTo(self,from_pos,to_pos):
666 662 if from_pos < to_pos:
667 663 self.SetSelection(from_pos,to_pos)
668 664 self.DeleteBack()
669 665
670 666 def removeCurrentLine(self):
671 667 self.LineDelete()
672 668
673 669 def moveCursor(self,position):
674 670 self.GotoPos(position)
675 671
676 672 def getCursorPos(self):
677 673 return self.GetCurrentPos()
678 674
679 675 def selectFromTo(self,from_pos,to_pos):
680 676 self.SetSelectionStart(from_pos)
681 677 self.SetSelectionEnd(to_pos)
682 678
683 679 def writeHistory(self,history):
684 680 self.removeFromTo(self.getCurrentPromptStart(),self.getCurrentLineEnd())
685 681 self.changeLine(history)
686 682
687 683 def writeCompletion(self, possibilities):
688 684 max_len = len(max(possibilities,key=len))
689 685 max_symbol =' '*max_len
690 686
691 687 #now we check how much symbol we can put on a line...
692 688 cursor_pos = self.getCursorPos()
693 689 test_buffer = max_symbol + ' '*4
694 690 current_lines = self.GetLineCount()
695 691
696 692 allowed_symbols = 80/len(test_buffer)
697 693 if allowed_symbols == 0:
698 694 allowed_symbols = 1
699 695
700 696 pos = 1
701 697 buf = ''
702 698 for symbol in possibilities:
703 699 #buf += symbol+'\n'#*spaces)
704 700 if pos<allowed_symbols:
705 701 spaces = max_len - len(symbol) + 4
706 702 buf += symbol+' '*spaces
707 703 pos += 1
708 704 else:
709 705 buf+=symbol+'\n'
710 706 pos = 1
711 707 self.write(buf)
712 708
713 709 def _onKeypress(self, event, skip=True):
714 710 '''
715 711 Key press callback used for correcting behavior for console-like
716 712 interfaces. For example 'home' should go to prompt, not to begining of
717 713 line.
718 714
719 715 @param widget: Widget that key press accored in.
720 716 @type widget: gtk.Widget
721 717 @param event: Event object
722 718 @type event: gtk.gdk.Event
723 719
724 720 @return: Return True if event as been catched.
725 721 @rtype: boolean
726 722 '''
727 723
728 724 if event.GetKeyCode() == wx.WXK_HOME:
729 725 if event.Modifiers == wx.MOD_NONE:
730 726 self.moveCursorOnNewValidKey()
731 727 self.moveCursor(self.getCurrentPromptStart())
732 728 return True
733 729 elif event.Modifiers == wx.MOD_SHIFT:
734 730 self.moveCursorOnNewValidKey()
735 731 self.selectFromTo(self.getCurrentPromptStart(),self.getCursorPos())
736 732 return True
737 733 else:
738 734 return False
739 735
740 736 elif event.GetKeyCode() == wx.WXK_LEFT:
741 737 if event.Modifiers == wx.MOD_NONE:
742 738 self.moveCursorOnNewValidKey()
743 739
744 740 self.moveCursor(self.getCursorPos()-1)
745 741 if self.getCursorPos() < self.getCurrentPromptStart():
746 742 self.moveCursor(self.getCurrentPromptStart())
747 743 return True
748 744
749 745 elif event.GetKeyCode() == wx.WXK_BACK:
750 746 self.moveCursorOnNewValidKey()
751 747 if self.getCursorPos() > self.getCurrentPromptStart():
752 748 self.removeFromTo(self.getCursorPos()-1,self.getCursorPos())
753 749 return True
754 750
755 751 if skip:
756 752 if event.GetKeyCode() not in [wx.WXK_PAGEUP,wx.WXK_PAGEDOWN] and event.Modifiers == wx.MOD_NONE:
757 753 self.moveCursorOnNewValidKey()
758 754
759 755 event.Skip()
760 756 return True
761 757 return False
762 758
763 759 def OnUpdateUI(self, evt):
764 760 # check for matching braces
765 761 braceAtCaret = -1
766 762 braceOpposite = -1
767 763 charBefore = None
768 764 caretPos = self.GetCurrentPos()
769 765
770 766 if caretPos > 0:
771 767 charBefore = self.GetCharAt(caretPos - 1)
772 768 styleBefore = self.GetStyleAt(caretPos - 1)
773 769
774 770 # check before
775 771 if charBefore and chr(charBefore) in "[]{}()" and styleBefore == stc.STC_P_OPERATOR:
776 772 braceAtCaret = caretPos - 1
777 773
778 774 # check after
779 775 if braceAtCaret < 0:
780 776 charAfter = self.GetCharAt(caretPos)
781 777 styleAfter = self.GetStyleAt(caretPos)
782 778
783 779 if charAfter and chr(charAfter) in "[]{}()" and styleAfter == stc.STC_P_OPERATOR:
784 780 braceAtCaret = caretPos
785 781
786 782 if braceAtCaret >= 0:
787 783 braceOpposite = self.BraceMatch(braceAtCaret)
788 784
789 785 if braceAtCaret != -1 and braceOpposite == -1:
790 786 self.BraceBadLight(braceAtCaret)
791 787 else:
792 788 self.BraceHighlight(braceAtCaret, braceOpposite)
793 789 #pt = self.PointFromPosition(braceOpposite)
794 790 #self.Refresh(True, wxRect(pt.x, pt.y, 5,5))
795 791 #print pt
796 792 #self.Refresh(False)
797 793
798 794 class WxIPythonViewPanel(wx.Panel):
799 795 '''
800 796 This is wx.Panel that embbed the IPython Thread and the wx.StyledTextControl
801 797 If you want to port this to any other GUI toolkit, just replace the WxConsoleView
802 798 by YOURGUIConsoleView and make YOURGUIIPythonView derivate from whatever container you want.
803 799 I've choosed to derivate from a wx.Panel because it seems to be ore usefull
804 800 Any idea to make it more 'genric' welcomed.
805 801 '''
806 802 def __init__(self,parent,exit_handler=None,intro=None,background_color="BLACK"):
807 803 '''
808 804 Initialize.
809 805 Instanciate an IPython thread.
810 806 Instanciate a WxConsoleView.
811 807 Redirect I/O to console.
812 808 '''
813 809 wx.Panel.__init__(self,parent,-1)
814 810
815 811 ### IPython thread instanciation ###
816 812 self.cout = StringIO()
817 813 self.IP = IterableIPShell(cout=self.cout,cerr=self.cout,
818 814 exit_handler = exit_handler,
819 815 time_loop = 0.1)
820 816 self.IP.start()
821 817
822 818 ### IPython wx console view instanciation ###
823 819 #If user didn't defined an intro text, we create one for him
824 820 #If you really wnat an empty intrp just call wxIPythonViewPanel with intro=''
825 821 if intro == None:
826 822 welcome_text = "Welcome to WxIPython Shell.\n\n"
827 823 welcome_text+= self.IP.getBanner()
828 824 welcome_text+= "!command -> Execute command in shell\n"
829 825 welcome_text+= "TAB -> Autocompletion\n"
830 826
831 827 self.text_ctrl = WxConsoleView(self,
832 828 self.IP.getPrompt(),
833 829 intro=welcome_text,
834 830 background_color=background_color)
835 831
836 832 self.text_ctrl.Bind(wx.EVT_KEY_DOWN, self.keyPress, self.text_ctrl)
837 833
838 834 ### making the layout of the panel ###
839 835 sizer = wx.BoxSizer(wx.VERTICAL)
840 836 sizer.Add(self.text_ctrl, 1, wx.EXPAND)
841 837 self.SetAutoLayout(True)
842 838 sizer.Fit(self)
843 839 sizer.SetSizeHints(self)
844 840 self.SetSizer(sizer)
845 841 #and we focus on the widget :)
846 842 self.SetFocus()
847 843
848 844 ### below are the thread communication variable ###
849 845 # the IPython thread is managed via unidirectional communication.
850 846 # It's a thread slave that can't interact by itself with the GUI.
851 847 # When the GUI event loop is done runStateMachine() is called and the thread sate is then
852 848 # managed.
853 849
854 850 #Initialize the state machine #kept for information
855 851 #self.states = ['IDLE',
856 852 # 'DO_EXECUTE_LINE',
857 853 # 'WAIT_END_OF_EXECUTION',
858 854 # 'SHOW_DOC',
859 855 # 'SHOW_PROMPT']
860 856
861 857 self.cur_state = 'IDLE'
862 858 self.pager_state = 'DONE'
863 859 #wx.CallAfter(self.runStateMachine)
864 860
865 861 # This creates a new Event class and a EVT binder function
866 862 (self.AskExitEvent, EVT_ASK_EXIT) = wx.lib.newevent.NewEvent()
867 863
868 864 self.Bind(wx.EVT_IDLE, self.runStateMachine)
869 865 self.Bind(EVT_ASK_EXIT, exit_handler)
870 866
871 867 def __del__(self):
872 868 self.IP.shutdown()
873 869 self.IP.join()
874 870 WxConsoleView.__del__()
875 871
876 872 #---------------------------- IPython Thread Management ---------------------------------------
877 873 def runStateMachine(self,event):
878 874 #print >>self.sys_stdout,"state:",self.cur_state
879 875 self.updateStatusTracker(self.cur_state)
880 876
881 877 if self.cur_state == 'DO_EXECUTE_LINE':
882 878 #print >>self.sys_stdout,"command:",self.getCurrentLine()
883 879 self.IP.doExecute(self.text_ctrl.getCurrentLine().replace('\t',' '*4))
884 880 self.updateHistoryTracker(self.text_ctrl.getCurrentLine())
885 881 self.cur_state = 'WAIT_END_OF_EXECUTION'
886 882
887 883 if self.cur_state == 'WAIT_END_OF_EXECUTION':
888 884 if self.IP.isExecuteDone():
889 885 self.doc = self.IP.getDocText()
890 886 if self.IP.getAskExit():
891 887 evt = self.AskExitEvent()
892 888 wx.PostEvent(self, evt)
893 889 self.IP.clearAskExit()
894 890 if self.doc:
895 891 self.pager_state = 'INIT'
896 892 self.cur_state = 'SHOW_DOC'
897 893 else:
898 894 self.cur_state = 'SHOW_PROMPT'
899 895
900 896 if self.cur_state == 'SHOW_PROMPT':
901 897 self.text_ctrl.setPrompt(self.IP.getPrompt())
902 898 self.text_ctrl.setIndentation(self.IP.getIndentation())
903 899 self.text_ctrl.setPromptCount(self.IP.getPromptCount())
904 900 rv = self.cout.getvalue()
905 901 if rv: rv = rv.strip('\n')
906 902 self.text_ctrl.showReturned(rv)
907 903 self.cout.truncate(0)
908 904 self.IP.initHistoryIndex()
909 905 self.cur_state = 'IDLE'
910 906
911 907 if self.cur_state == 'SHOW_DOC':
912 908 self.pager(self.doc)
913 909 if self.pager_state == 'DONE':
914 910 self.cur_state = 'SHOW_PROMPT'
915 911
916 912 event.Skip()
917 913
918 914 #---------------------------- IPython pager ---------------------------------------
919 915 def pager(self,text):#,start=0,screen_lines=0,pager_cmd = None):
920 916 if self.pager_state == 'WAITING':
921 917 #print >>self.sys_stdout,"PAGER waiting"
922 918 return
923 919
924 920 if self.pager_state == 'INIT':
925 921 #print >>self.sys_stdout,"PAGER state:",self.pager_state
926 922 self.pager_lines = text[7:].split('\n')
927 923 self.pager_nb_lines = len(self.pager_lines)
928 924 self.pager_index = 0
929 925 self.pager_do_remove = False
930 926 self.text_ctrl.write('\n')
931 927 self.pager_state = 'PROCESS_LINES'
932 928
933 929 if self.pager_state == 'PROCESS_LINES':
934 930 #print >>self.sys_stdout,"PAGER state:",self.pager_state
935 931 if self.pager_do_remove == True:
936 932 self.text_ctrl.removeCurrentLine()
937 933 self.pager_do_remove = False
938 934
939 935 if self.pager_nb_lines > 10:
940 936 #print >>self.sys_stdout,"PAGER processing 10 lines"
941 937 if self.pager_index > 0:
942 938 self.text_ctrl.write(">\x01\x1b[1;36m\x02"+self.pager_lines[self.pager_index]+'\n')
943 939 else:
944 940 self.text_ctrl.write("\x01\x1b[1;36m\x02 "+self.pager_lines[self.pager_index]+'\n')
945 941
946 942 for line in self.pager_lines[self.pager_index+1:self.pager_index+9]:
947 943 self.text_ctrl.write("\x01\x1b[1;36m\x02 "+line+'\n')
948 944 self.pager_index += 10
949 945 self.pager_nb_lines -= 10
950 946 self.text_ctrl.write("--- Push Enter to continue or 'Q' to quit---")
951 947 self.pager_do_remove = True
952 948 self.pager_state = 'WAITING'
953 949 return
954 950 else:
955 951 #print >>self.sys_stdout,"PAGER processing last lines"
956 952 if self.pager_nb_lines > 0:
957 953 if self.pager_index > 0:
958 954 self.text_ctrl.write(">\x01\x1b[1;36m\x02"+self.pager_lines[self.pager_index]+'\n')
959 955 else:
960 956 self.text_ctrl.write("\x01\x1b[1;36m\x02 "+self.pager_lines[self.pager_index]+'\n')
961 957
962 958 self.pager_index += 1
963 959 self.pager_nb_lines -= 1
964 960 if self.pager_nb_lines > 0:
965 961 for line in self.pager_lines[self.pager_index:]:
966 962 self.text_ctrl.write("\x01\x1b[1;36m\x02 "+line+'\n')
967 963 self.pager_nb_lines = 0
968 964 self.pager_state = 'DONE'
969 965
970 966 #---------------------------- Key Handler --------------------------------------------
971 967 def keyPress(self, event):
972 968 '''
973 969 Key press callback with plenty of shell goodness, like history,
974 970 autocompletions, etc.
975 971 '''
976 972
977 973 if event.GetKeyCode() == ord('C'):
978 974 if event.Modifiers == wx.MOD_CONTROL:
979 975 if self.cur_state == 'WAIT_END_OF_EXECUTION':
980 976 #we raise an exception inside the IPython thread container
981 977 self.IP.raise_exc(KeyboardInterrupt)
982 978 return
983 979
984 980 if event.KeyCode == wx.WXK_RETURN:
985 981 if self.cur_state == 'IDLE':
986 982 #we change the state ot the state machine
987 983 self.cur_state = 'DO_EXECUTE_LINE'
988 984 return
989 985 if self.pager_state == 'WAITING':
990 986 self.pager_state = 'PROCESS_LINES'
991 987 return
992 988
993 989 if event.GetKeyCode() in [ord('q'),ord('Q')]:
994 990 if self.pager_state == 'WAITING':
995 991 self.pager_state = 'DONE'
996 992 return
997 993
998 994 #scroll_position = self.text_ctrl.GetScrollPos(wx.VERTICAL)
999 995 if self.cur_state == 'IDLE':
1000 996 if event.KeyCode == wx.WXK_UP:
1001 997 history = self.IP.historyBack()
1002 998 self.text_ctrl.writeHistory(history)
1003 999 return
1004 1000 if event.KeyCode == wx.WXK_DOWN:
1005 1001 history = self.IP.historyForward()
1006 1002 self.text_ctrl.writeHistory(history)
1007 1003 return
1008 1004 if event.KeyCode == wx.WXK_TAB:
1009 1005 #if line empty we disable tab completion
1010 1006 if not self.text_ctrl.getCurrentLine().strip():
1011 1007 self.text_ctrl.write('\t')
1012 1008 return
1013 1009 completed, possibilities = self.IP.complete(self.text_ctrl.getCurrentLine())
1014 1010 if len(possibilities) > 1:
1015 1011 cur_slice = self.text_ctrl.getCurrentLine()
1016 1012 self.text_ctrl.write('\n')
1017 1013 self.text_ctrl.writeCompletion(possibilities)
1018 1014 self.text_ctrl.write('\n')
1019 1015
1020 1016 self.text_ctrl.showPrompt()
1021 1017 self.text_ctrl.write(cur_slice)
1022 1018 self.text_ctrl.changeLine(completed or cur_slice)
1023 1019
1024 1020 return
1025 1021 event.Skip()
1026 1022
1027 1023 #---------------------------- Hook Section --------------------------------------------
1028 1024 def updateHistoryTracker(self,command_line):
1029 1025 '''
1030 1026 Default history tracker (does nothing)
1031 1027 '''
1032 1028 pass
1033 1029
1034 1030 def setHistoryTrackerHook(self,func):
1035 1031 '''
1036 1032 Define a new history tracker
1037 1033 '''
1038 1034 self.updateHistoryTracker = func
1039 1035 def updateStatusTracker(self,status):
1040 1036 '''
1041 1037 Default status tracker (does nothing)
1042 1038 '''
1043 1039 pass
1044 1040
1045 1041 def setStatusTrackerHook(self,func):
1046 1042 '''
1047 1043 Define a new status tracker
1048 1044 '''
1049 1045 self.updateStatusTracker = func
1050 1046
General Comments 0
You need to be logged in to leave comments. Login now