##// END OF EJS Templates
some more little cleanup and a bug correction with prompt that comes with last commit
laurent.dufrechou@gmail.com -
Show More
@@ -1,533 +1,537 b''
1 1 #!/usr/bin/python
2 2 # -*- coding: iso-8859-15 -*-
3 3 '''
4 4 Provides IPython remote instance.
5 5
6 6 @author: Laurent Dufrechou
7 7 laurent.dufrechou _at_ gmail.com
8 8 @license: BSD
9 9
10 10 All rights reserved. This program and the accompanying materials are made
11 11 available under the terms of the BSD which accompanies this distribution, and
12 12 is available at U{http://www.opensource.org/licenses/bsd-license.php}
13 13 '''
14 14
15 15 __version__ = 0.9
16 16 __author__ = "Laurent Dufrechou"
17 17 __email__ = "laurent.dufrechou _at_ gmail.com"
18 18 __license__ = "BSD"
19 19
20 20 import re
21 21 import sys
22 22 import os
23 23 import locale
24 24 from thread_ex import ThreadEx
25 25
26 26 try:
27 27 import IPython
28 28 except Exception,e:
29 29 print "Error importing IPython (%s)" % str(e)
30 30 raise Exception, e
31 31
32 32 ##############################################################################
33 33 class _Helper(object):
34 34 """Redefine the built-in 'help'.
35 35 This is a wrapper around pydoc.help (with a twist).
36 36 """
37 37
38 38 def __init__(self, pager):
39 39 self._pager = pager
40 40
41 41 def __repr__(self):
42 42 return "Type help() for interactive help, " \
43 43 "or help(object) for help about object."
44 44
45 45 def __call__(self, *args, **kwds):
46 46 class DummyWriter(object):
47 47 '''Dumy class to handle help output'''
48 48 def __init__(self, pager):
49 49 self._pager = pager
50 50
51 51 def write(self, data):
52 52 '''hook to fill self._pager'''
53 53 self._pager(data)
54 54
55 55 import pydoc
56 56 pydoc.help.output = DummyWriter(self._pager)
57 57 pydoc.help.interact = lambda :1
58 58
59 59 return pydoc.help(*args, **kwds)
60 60
61 61
62 62 ##############################################################################
63 63 class _CodeExecutor(ThreadEx):
64 64 ''' Thread that execute ipython code '''
65 65 def __init__(self, instance):
66 66 ThreadEx.__init__(self)
67 67 self.instance = instance
68 68
69 69 def run(self):
70 70 '''Thread main loop'''
71 71 try:
72 72 self.instance._doc_text = None
73 73 self.instance._help_text = None
74 74 self.instance._execute()
75 75 # used for uper class to generate event after execution
76 76 self.instance._after_execute()
77 77
78 78 except KeyboardInterrupt:
79 79 pass
80 80
81 81
82 82 ##############################################################################
83 83 class NonBlockingIPShell(object):
84 84 '''
85 85 Create an IPython instance, running the commands in a separate,
86 86 non-blocking thread.
87 87 This allows embedding in any GUI without blockage.
88 88
89 89 Note: The ThreadEx class supports asynchroneous function call
90 90 via raise_exc()
91 91 '''
92 92
93 93 def __init__(self, argv=[], user_ns={}, user_global_ns=None,
94 94 cin=None, cout=None, cerr=None,
95 95 ask_exit_handler=None):
96 96 '''
97 97 @param argv: Command line options for IPython
98 98 @type argv: list
99 99 @param user_ns: User namespace.
100 100 @type user_ns: dictionary
101 101 @param user_global_ns: User global namespace.
102 102 @type user_global_ns: dictionary.
103 103 @param cin: Console standard input.
104 104 @type cin: IO stream
105 105 @param cout: Console standard output.
106 106 @type cout: IO stream
107 107 @param cerr: Console standard error.
108 108 @type cerr: IO stream
109 109 @param exit_handler: Replacement for builtin exit() function
110 110 @type exit_handler: function
111 111 @param time_loop: Define the sleep time between two thread's loop
112 112 @type int
113 113 '''
114 114 #ipython0 initialisation
115 115 self._IP = None
116 116 self.init_ipython0(argv, user_ns, user_global_ns,
117 117 cin, cout, cerr,
118 118 ask_exit_handler)
119 119
120 120 #vars used by _execute
121 121 self._iter_more = 0
122 122 self._history_level = 0
123 123 self._complete_sep = re.compile('[\s\{\}\[\]\(\)\=]')
124 124 self._prompt = str(self._IP.outputcache.prompt1).strip()
125 125
126 126 #thread working vars
127 127 self._line_to_execute = ''
128 128 self._threading = True
129 129
130 130 #vars that will be checked by GUI loop to handle thread states...
131 131 #will be replaced later by PostEvent GUI funtions...
132 132 self._doc_text = None
133 133 self._help_text = None
134 134 self._add_button = None
135 135
136 136 def init_ipython0(self, argv=[], user_ns={}, user_global_ns=None,
137 137 cin=None, cout=None, cerr=None,
138 138 ask_exit_handler=None):
139 139 ''' Initialize an ipython0 instance '''
140 140
141 141 #first we redefine in/out/error functions of IPython
142 142 #BUG: we've got a limitation form ipython0 there
143 143 #only one instance can be instanciated else tehre will be
144 144 #cin/cout/cerr clash...
145 145 if cin:
146 146 IPython.genutils.Term.cin = cin
147 147 if cout:
148 148 IPython.genutils.Term.cout = cout
149 149 if cerr:
150 150 IPython.genutils.Term.cerr = cerr
151 151
152 152 excepthook = sys.excepthook
153 153
154 154 #Hack to save sys.displayhook, because ipython seems to overwrite it...
155 155 self.sys_displayhook_ori = sys.displayhook
156 156
157 157 self._IP = IPython.Shell.make_IPython(
158 158 argv,user_ns=user_ns,
159 159 user_global_ns=user_global_ns,
160 160 embedded=True,
161 161 shell_class=IPython.Shell.InteractiveShell)
162 162
163 163 #we save ipython0 displayhook and we restore sys.displayhook
164 164 self.displayhook = sys.displayhook
165 165 sys.displayhook = self.sys_displayhook_ori
166 166
167 167 #we replace IPython default encoding by wx locale encoding
168 168 loc = locale.getpreferredencoding()
169 169 if loc:
170 170 self._IP.stdin_encoding = loc
171 171 #we replace the ipython default pager by our pager
172 172 self._IP.set_hook('show_in_pager', self._pager)
173 173
174 #we replace the ipython default shell command caller by our shell handler
174 #we replace the ipython default shell command caller
175 #by our shell handler
175 176 self._IP.set_hook('shell_hook', self._shell)
176 177
177 178 #we replace the ipython default input command caller by our method
178 179 IPython.iplib.raw_input_original = self._raw_input
179 180 #we replace the ipython default exit command by our method
180 181 self._IP.exit = ask_exit_handler
181 182 #we replace the help command
182 183 self._IP.user_ns['help'] = _Helper(self._pager_help)
183 184
184 185 #we disable cpase magic... until we found a way to use it properly.
185 186 #import IPython.ipapi
186 187 ip = IPython.ipapi.get()
187 188 def bypass_magic(self, arg):
188 189 print '%this magic is currently disabled.'
189 190 ip.expose_magic('cpaste', bypass_magic)
190 191
191 192 def reset_magic(self, arg):
192 193 """Resets the namespace by removing all names defined by the user.
193 194
194 195 Input/Output history are left around in case you need them."""
195 196
196 197 ans = True ##todo find away to ask the user...
197 198 ##seems hard to do it cleanly...
198 199 if not ans:
199 200 print 'Nothing done.'
200 201 return
201 202 user_ns = self.shell.user_ns
202 203 for i in self.magic_who_ls():
203 204 del(user_ns[i])
204 205
205 206 # Also flush the private list of module references kept for script
206 207 # execution protection
207 208 self.shell._user_main_modules[:] = []
208 209
209 210 ip.expose_magic('reset', reset_magic)
210 211
211 212 sys.excepthook = excepthook
212 213
213 #----------------------- Thread management section ----------------------
214 #----------------------- Thread management section ----------------------
214 215 def do_execute(self, line):
215 216 """
216 217 Tell the thread to process the 'line' command
217 218 """
218 219
219 220 self._line_to_execute = line
220 221
221 222 if self._threading:
222 #we launch the ipython line execution in a thread to make it interruptible
223 #with include it in self namespace to be able to call ce.raise_exc(KeyboardInterrupt)
223 #we launch the ipython line execution in a thread to make it
224 #interruptible with include it in self namespace to be able
225 #to call ce.raise_exc(KeyboardInterrupt)
224 226 self.ce = _CodeExecutor(self)
225 227 self.ce.start()
226 228 else:
227 229 try:
228 230 self._doc_text = None
229 231 self._help_text = None
230 232 self._execute()
231 233 # used for uper class to generate event after execution
232 234 self._after_execute()
233 235
234 236 except KeyboardInterrupt:
235 237 pass
236 238
237 #----------------------- IPython management section ----------------------
239 #----------------------- IPython management section ----------------------
238 240 def get_threading(self):
239 241 """
240 242 Returns threading status, is set to True, then each command sent to
241 243 the interpreter will be executed in a separated thread allowing,
242 244 for example, breaking a long running commands.
243 245 Disallowing it, permits better compatibilty with instance that is embedding
244 246 IPython instance.
245 247
246 248 @return: Execution method
247 249 @rtype: bool
248 250 """
249 251 return self._threading
250 252
251 253 def set_threading(self, state):
252 254 """
253 255 Sets threading state, if set to True, then each command sent to
254 256 the interpreter will be executed in a separated thread allowing,
255 257 for example, breaking a long running commands.
256 258 Disallowing it, permits better compatibilty with instance that is embedding
257 259 IPython instance.
258 260
259 261 @param state: Sets threading state
260 262 @type bool
261 263 """
262 264 self._threading = state
263 265
264 266 def get_doc_text(self):
265 267 """
266 268 Returns the output of the processing that need to be paged (if any)
267 269
268 270 @return: The std output string.
269 271 @rtype: string
270 272 """
271 273 return self._doc_text
272 274
273 275 def get_help_text(self):
274 276 """
275 277 Returns the output of the processing that need to be paged via help pager(if any)
276 278
277 279 @return: The std output string.
278 280 @rtype: string
279 281 """
280 282 return self._help_text
281 283
282 284 def get_banner(self):
283 285 """
284 286 Returns the IPython banner for useful info on IPython instance
285 287
286 288 @return: The banner string.
287 289 @rtype: string
288 290 """
289 291 return self._IP.BANNER
290 292
291 293 def get_prompt_count(self):
292 294 """
293 295 Returns the prompt number.
294 296 Each time a user execute a line in the IPython shell the prompt count is increased
295 297
296 298 @return: The prompt number
297 299 @rtype: int
298 300 """
299 301 return self._IP.outputcache.prompt_count
300 302
301 303 def get_prompt(self):
302 304 """
303 305 Returns current prompt inside IPython instance
304 306 (Can be In [...]: ot ...:)
305 307
306 308 @return: The current prompt.
307 309 @rtype: string
308 310 """
309 311 return self._prompt
310 312
311 313 def get_indentation(self):
312 314 """
313 315 Returns the current indentation level
314 316 Usefull to put the caret at the good start position if we want to do autoindentation.
315 317
316 318 @return: The indentation level.
317 319 @rtype: int
318 320 """
319 321 return self._IP.indent_current_nsp
320 322
321 323 def update_namespace(self, ns_dict):
322 324 '''
323 325 Add the current dictionary to the shell namespace.
324 326
325 327 @param ns_dict: A dictionary of symbol-values.
326 328 @type ns_dict: dictionary
327 329 '''
328 330 self._IP.user_ns.update(ns_dict)
329 331
330 332 def complete(self, line):
331 333 '''
332 334 Returns an auto completed line and/or posibilities for completion.
333 335
334 336 @param line: Given line so far.
335 337 @type line: string
336 338
337 339 @return: Line completed as for as possible,
338 340 and possible further completions.
339 341 @rtype: tuple
340 342 '''
341 343 split_line = self._complete_sep.split(line)
342 344 possibilities = self._IP.complete(split_line[-1])
343 345 if possibilities:
344 346
345 347 def _common_prefix(str1, str2):
346 348 '''
347 349 Reduction function. returns common prefix of two given strings.
348 350
349 351 @param str1: First string.
350 352 @type str1: string
351 353 @param str2: Second string
352 354 @type str2: string
353 355
354 356 @return: Common prefix to both strings.
355 357 @rtype: string
356 358 '''
357 359 for i in range(len(str1)):
358 360 if not str2.startswith(str1[:i+1]):
359 361 return str1[:i]
360 362 return str1
361 363 common_prefix = reduce(_common_prefix, possibilities)
362 364 completed = line[:-len(split_line[-1])]+common_prefix
363 365 else:
364 366 completed = line
365 367 return completed, possibilities
366 368
367 369 def history_back(self):
368 370 '''
369 371 Provides one history command back.
370 372
371 373 @return: The command string.
372 374 @rtype: string
373 375 '''
374 376 history = ''
375 377 #the below while loop is used to suppress empty history lines
376 378 while((history == '' or history == '\n') and self._history_level >0):
377 379 if self._history_level >= 1:
378 380 self._history_level -= 1
379 381 history = self._get_history()
380 382 return history
381 383
382 384 def history_forward(self):
383 385 '''
384 386 Provides one history command forward.
385 387
386 388 @return: The command string.
387 389 @rtype: string
388 390 '''
389 391 history = ''
390 392 #the below while loop is used to suppress empty history lines
391 393 while((history == '' or history == '\n') \
392 394 and self._history_level <= self._get_history_max_index()):
393 395 if self._history_level < self._get_history_max_index():
394 396 self._history_level += 1
395 397 history = self._get_history()
396 398 else:
397 399 if self._history_level == self._get_history_max_index():
398 400 history = self._get_history()
399 401 self._history_level += 1
400 402 else:
401 403 history = ''
402 404 return history
403 405
404 406 def init_history_index(self):
405 407 '''
406 408 set history to last command entered
407 409 '''
408 410 self._history_level = self._get_history_max_index()+1
409 411
410 412 #----------------------- IPython PRIVATE management section --------------
411 413 def _after_execute(self):
412 414 '''
413 415 Can be redefined to generate post event after excution is done
414 416 '''
415 417 pass
416 418
417 419 #def _ask_exit(self):
418 420 # '''
419 421 # Can be redefined to generate post event to exit the Ipython shell
420 422 # '''
421 423 # pass
422 424
423 425 def _get_history_max_index(self):
424 426 '''
425 427 returns the max length of the history buffer
426 428
427 429 @return: history length
428 430 @rtype: int
429 431 '''
430 432 return len(self._IP.input_hist_raw)-1
431 433
432 434 def _get_history(self):
433 435 '''
434 436 Get's the command string of the current history level.
435 437
436 438 @return: Historic command stri
437 439 @rtype: string
438 440 '''
439 441 rv = self._IP.input_hist_raw[self._history_level].strip('\n')
440 442 return rv
441 443
442 444 def _pager_help(self, text):
443 445 '''
444 446 This function is used as a callback replacment to IPython help pager function
445 447
446 448 It puts the 'text' value inside the self._help_text string that can be retrived via
447 449 get_help_text function.
448 450 '''
449 451 if self._help_text == None:
450 452 self._help_text = text
451 453 else:
452 454 self._help_text += text
453 455
454 456 def _pager(self, IP, text):
455 457 '''
456 458 This function is used as a callback replacment to IPython pager function
457 459
458 460 It puts the 'text' value inside the self._doc_text string that can be retrived via
459 461 get_doc_text function.
460 462 '''
461 463 self._doc_text = text
462 464
463 465 def _raw_input(self, prompt=''):
464 466 '''
465 467 Custom raw_input() replacement. Get's current line from console buffer.
466 468
467 469 @param prompt: Prompt to print. Here for compatability as replacement.
468 470 @type prompt: string
469 471
470 472 @return: The current command line text.
471 473 @rtype: string
472 474 '''
473 475 return self._line_to_execute
474 476
475 477 def _execute(self):
476 478 '''
477 479 Executes the current line provided by the shell object.
478 480 '''
479 481
480 482 orig_stdout = sys.stdout
481 483 sys.stdout = IPython.Shell.Term.cout
482 484 #self.sys_displayhook_ori = sys.displayhook
483 485 #sys.displayhook = self.displayhook
484 486
485 487 try:
486 488 line = self._IP.raw_input(None, self._iter_more)
487 489 if self._IP.autoindent:
488 490 self._IP.readline_startup_hook(None)
489 491
490 492 except KeyboardInterrupt:
491 493 self._IP.write('\nKeyboardInterrupt\n')
492 494 self._IP.resetbuffer()
493 495 # keep cache in sync with the prompt counter:
494 496 self._IP.outputcache.prompt_count -= 1
495 497
496 498 if self._IP.autoindent:
497 499 self._IP.indent_current_nsp = 0
498 500 self._iter_more = 0
499 501 except:
500 502 self._IP.showtraceback()
501 503 else:
502 504 self._IP.write(str(self._IP.outputcache.prompt_out).strip())
503 505 self._iter_more = self._IP.push(line)
504 if (self._IP.SyntaxTB.last_syntax_error and self._IP.rc.autoedit_syntax):
506 if (self._IP.SyntaxTB.last_syntax_error and \
507 self._IP.rc.autoedit_syntax):
505 508 self._IP.edit_syntax_error()
506 509 if self._iter_more:
507 510 self._prompt = str(self._IP.outputcache.prompt2).strip()
508 511 if self._IP.autoindent:
509 512 self._IP.readline_startup_hook(self._IP.pre_readline)
510 513 else:
511 514 self._prompt = str(self._IP.outputcache.prompt1).strip()
512 515 self._IP.indent_current_nsp = 0 #we set indentation to 0
513 516
514 517 sys.stdout = orig_stdout
515 518 #sys.displayhook = self.sys_displayhook_ori
516 519
517 520 def _shell(self, ip, cmd):
518 521 '''
519 522 Replacement method to allow shell commands without them blocking.
520 523
521 524 @param ip: Ipython instance, same as self._IP
522 525 @type cmd: Ipython instance
523 526 @param cmd: Shell command to execute.
524 527 @type cmd: string
525 528 '''
526 529 stdin, stdout = os.popen4(cmd)
527 result = stdout.read().decode('cp437').encode(locale.getpreferredencoding())
530 result = stdout.read().decode('cp437').\
531 encode(locale.getpreferredencoding())
528 532 #we use print command because the shell command is called
529 533 #inside IPython instance and thus is redirected to thread cout
530 534 #"\x01\x1b[1;36m\x02" <-- add colour to the text...
531 535 print "\x01\x1b[1;36m\x02"+result
532 536 stdout.close()
533 537 stdin.close()
@@ -1,475 +1,509 b''
1 1 #!/usr/bin/python
2 2 # -*- coding: iso-8859-15 -*-
3 3 import wx
4 4 import wx.stc as stc
5 5 import keyword
6 6
7 7 #-----------------------------------------
8 8 # History widget for IPython
9 9 __version__ = 0.5
10 10 __author__ = "Laurent Dufrechou"
11 11 __email__ = "laurent.dufrechou _at_ gmail.com"
12 12 __license__ = "BSD"
13 13 #-----------------------------------------
14 14 class IPythonHistoryPanel(wx.Panel):
15 15
16 16 def __init__(self, parent,flt_empty=True,
17 17 flt_doc=True,flt_cmd=True,flt_magic=True):
18 18
19 19 wx.Panel.__init__(self,parent,-1)
20 20 #text_ctrl = wx.TextCtrl(self, -1, style=wx.TE_MULTILINE)
21 21 text_ctrl = PythonSTC(self, -1)
22 22
23 23
24 24 st_filt = wx.StaticText(self, -1, " Filter:")
25 25
26 26 self.filter_empty = wx.CheckBox(self, -1, "Empty commands")
27 27 self.filter_doc = wx.CheckBox(self, -1, "?: Doc commands")
28 28 self.filter_cmd = wx.CheckBox(self, -1, "!: Sys commands")
29 29 self.filter_magic = wx.CheckBox(self, -1, "%: Magic keys")
30 30
31 31 self.options={'filter_empty':{'value':'True',
32 'checkbox':self.filter_empty,'True':True,'False':False,
33 'setfunc':lambda x:None},
32 'checkbox':self.filter_empty, \
33 'True':True,'False':False,
34 'setfunc':lambda x:None},
34 35 'filter_doc':{'value':'True',
35 'checkbox':self.filter_doc,'True':True,'False':False,
36 'setfunc':lambda x:None},
36 'checkbox':self.filter_doc, \
37 'True':True,'False':False,
38 'setfunc':lambda x:None},
37 39 'filter_cmd':{'value':'True',
38 'checkbox':self.filter_cmd,'True':True,'False':False,
39 'setfunc':lambda x:None},
40 'checkbox':self.filter_cmd, \
41 'True':True,'False':False,
42 'setfunc':lambda x:None},
40 43 'filter_magic':{'value':'True',
41 'checkbox':self.filter_magic,'True':True,'False':False,
42 'setfunc':lambda x:None},
44 'checkbox':self.filter_magic, \
45 'True':True,'False':False,
46 'setfunc':lambda x:None},
43 47 }
44 48 self.reloadOptions(self.options)
45 49
46 50 self.filter_empty.Bind(wx.EVT_CHECKBOX, self.evtCheckEmptyFilter)
47 51 self.filter_doc.Bind(wx.EVT_CHECKBOX, self.evtCheckDocFilter)
48 52 self.filter_cmd.Bind(wx.EVT_CHECKBOX, self.evtCheckCmdFilter)
49 53 self.filter_magic.Bind(wx.EVT_CHECKBOX, self.evtCheckMagicFilter)
50 54
51 55 #self.filter_empty.SetValue(flt_empty)
52 56 #self.filter_doc.SetValue(flt_doc)
53 57 #self.filter_cmd.SetValue(flt_cmd)
54 58 #self.filter_magic.SetValue(flt_magic)
55 59
56 60 sizer = wx.BoxSizer(wx.VERTICAL)
57 61
58 62 sizer.Add(text_ctrl, 1, wx.EXPAND)
59 63 sizer.AddMany( [(5,5),
60 64 st_filt,
61 65 (10,10),
62 66 self.filter_empty,
63 67 self.filter_doc,
64 68 self.filter_cmd,
65 69 self.filter_magic,
66 70 (10,10),
67 71 ])
68 72 self.SetAutoLayout(True)
69 73 sizer.Fit(self)
70 74 sizer.SetSizeHints(self)
71 75 self.SetSizer(sizer)
72 76 self.text_ctrl=text_ctrl
73 77 #text_ctrl.SetText(demoText + open('Main.py').read())
74 78 text_ctrl.EmptyUndoBuffer()
75 79 text_ctrl.Colourise(0, -1)
76 80
77 81 # line numbers in the margin
78 82 text_ctrl.SetMarginType(1, stc.STC_MARGIN_NUMBER)
79 83 text_ctrl.SetMarginWidth(1, 15)
80 84
81 85
82 86 def write(self,history_line):
83 87 add = True
84 88 if self.filter_empty.GetValue() == True and history_line == '':
85 89 add = False
86 90 if len(history_line)>0:
87 91 if self.filter_doc.GetValue() == True and history_line[-1:] == '?':
88 92 add = False
89 93 if self.filter_cmd.GetValue() == True and history_line[0] == '!':
90 94 add = False
91 95 if self.filter_magic.GetValue() == True and history_line[0] == '%':
92 96 add = False
93 97 if add:
94 98 self.text_ctrl.AppendText(history_line+'\n')
95 99
96 100 #------------------------ Option Section -----------------------------------
97 101 def processOptionCheckedEvt(self, event, name):
98 102 if event.IsChecked():
99 103 self.options[name]['value']='True'
100 104 else:
101 105 self.options[name]['value']='False'
102 106 self.updateOptionTracker(name,
103 107 self.options[name]['value'])
104 108
105 109 def evtCheckEmptyFilter(self, event):
106 110 self.processOptionCheckedEvt(event, 'filter_empty')
107 111
108 112 def evtCheckDocFilter(self, event):
109 113 self.processOptionCheckedEvt(event, 'filter_doc')
110 114
111 115 def evtCheckCmdFilter(self, event):
112 116 self.processOptionCheckedEvt(event, 'filter_cmd')
113 117
114 118 def evtCheckMagicFilter(self, event):
115 119 self.processOptionCheckedEvt(event, 'filter_magic')
116 120
117 121 def getOptions(self):
118 122 return self.options
119 123
120 124 def reloadOptions(self,options):
121 125 self.options = options
122 126 for key in self.options.keys():
123 127 value = self.options[key]['value']
124 128 self.options[key]['checkbox'].SetValue(self.options[key][value])
125 129 self.options[key]['setfunc'](value)
126 130
127 131 #------------------------ Hook Section -----------------------------------
128 132 def updateOptionTracker(self,name,value):
129 133 '''
130 134 Default history tracker (does nothing)
131 135 '''
132 136 pass
133 137
134 138 def setOptionTrackerHook(self,func):
135 139 '''
136 140 Define a new history tracker
137 141 '''
138 142 self.updateOptionTracker = func
139 143
140 144
141 145 #----------------------------------------------------------------------
142 146 # Font definition for Styled Text Control
143 147
144 148 if wx.Platform == '__WXMSW__':
145 149 faces = { 'times': 'Times New Roman',
146 150 'mono' : 'Courier New',
147 151 'helv' : 'Arial',
148 152 'other': 'Comic Sans MS',
149 153 'size' : 8,
150 154 'size2': 6,
151 155 }
152 156 elif wx.Platform == '__WXMAC__':
153 157 faces = { 'times': 'Times New Roman',
154 158 'mono' : 'Monaco',
155 159 'helv' : 'Arial',
156 160 'other': 'Comic Sans MS',
157 161 'size' : 8,
158 162 'size2': 6,
159 163 }
160 164 else:
161 165 faces = { 'times': 'Times',
162 166 'mono' : 'Courier',
163 167 'helv' : 'Helvetica',
164 168 'other': 'new century schoolbook',
165 169 'size' : 8,
166 170 'size2': 6,
167 171 }
168 172
169 173
170 174 #----------------------------------------------------------------------
171 175
172 176 class PythonSTC(stc.StyledTextCtrl):
173 177
174 178 fold_symbols = 3
175 179
176 180 def __init__(self, parent, ID,
177 181 pos=wx.DefaultPosition, size=wx.DefaultSize,
178 182 style=0):
179 183 stc.StyledTextCtrl.__init__(self, parent, ID, pos, size, style)
180 184 #self.CmdKeyAssign(ord('B'), stc.STC_SCMOD_CTRL, stc.STC_CMD_ZOOMIN)
181 185 #self.CmdKeyAssign(ord('N'), stc.STC_SCMOD_CTRL, stc.STC_CMD_ZOOMOUT)
182 186
183 187 self.SetLexer(stc.STC_LEX_PYTHON)
184 188 self.SetKeyWords(0, " ".join(keyword.kwlist))
185 189
186 190 #self.SetProperty("fold", "1")
187 191 #self.SetProperty("tab.timmy.whinge.level", "1")
188 192 #self.SetMargins(0,0)
189 193
190 194 #self.SetViewWhiteSpace(False)
191 195 #self.SetBufferedDraw(False)
192 196 #self.SetViewEOL(True)
193 197 self.SetEOLMode(stc.STC_EOL_CRLF)
194 198 #self.SetUseAntiAliasing(True)
195 199
196 200 self.SetEdgeMode(stc.STC_EDGE_LINE)
197 201 self.SetEdgeColumn(80)
198 202 self.SetEdgeColour(wx.LIGHT_GREY)
199 203 self.SetLayoutCache(stc.STC_CACHE_PAGE)
200 204
201 205 # Setup a margin to hold fold markers
202 #self.SetFoldFlags(16) ### WHAT IS THIS VALUE? WHAT ARE THE OTHER FLAGS? DOES IT MATTER?
206 #self.SetFoldFlags(16)
207 ### WHAT IS THIS VALUE? WHAT ARE THE OTHER FLAGS? DOES IT MATTER?
203 208 self.SetMarginType(2, stc.STC_MARGIN_SYMBOL)
204 209 self.SetMarginMask(2, stc.STC_MASK_FOLDERS)
205 210 self.SetMarginSensitive(2, True)
206 211 self.SetMarginWidth(2, 12)
207 212
208 213 if self.fold_symbols == 0:
209 # Arrow pointing right for contracted folders, arrow pointing down for expanded
210 self.MarkerDefine(stc.STC_MARKNUM_FOLDEROPEN, stc.STC_MARK_ARROWDOWN, "black", "black")
211 self.MarkerDefine(stc.STC_MARKNUM_FOLDER, stc.STC_MARK_ARROW, "black", "black")
212 self.MarkerDefine(stc.STC_MARKNUM_FOLDERSUB, stc.STC_MARK_EMPTY, "black", "black")
213 self.MarkerDefine(stc.STC_MARKNUM_FOLDERTAIL, stc.STC_MARK_EMPTY, "black", "black")
214 self.MarkerDefine(stc.STC_MARKNUM_FOLDEREND, stc.STC_MARK_EMPTY, "white", "black")
215 self.MarkerDefine(stc.STC_MARKNUM_FOLDEROPENMID, stc.STC_MARK_EMPTY, "white", "black")
216 self.MarkerDefine(stc.STC_MARKNUM_FOLDERMIDTAIL, stc.STC_MARK_EMPTY, "white", "black")
214 # Arrow pointing right for contracted folders,
215 # arrow pointing down for expanded
216 self.MarkerDefine(stc.STC_MARKNUM_FOLDEROPEN, \
217 stc.STC_MARK_ARROWDOWN, "black", "black")
218 self.MarkerDefine(stc.STC_MARKNUM_FOLDER, \
219 stc.STC_MARK_ARROW, "black", "black")
220 self.MarkerDefine(stc.STC_MARKNUM_FOLDERSUB, \
221 stc.STC_MARK_EMPTY, "black", "black")
222 self.MarkerDefine(stc.STC_MARKNUM_FOLDERTAIL, \
223 stc.STC_MARK_EMPTY, "black", "black")
224 self.MarkerDefine(stc.STC_MARKNUM_FOLDEREND, \
225 stc.STC_MARK_EMPTY, "white", "black")
226 self.MarkerDefine(stc.STC_MARKNUM_FOLDEROPENMID, \
227 stc.STC_MARK_EMPTY, "white", "black")
228 self.MarkerDefine(stc.STC_MARKNUM_FOLDERMIDTAIL, \
229 stc.STC_MARK_EMPTY, "white", "black")
217 230
218 231 elif self.fold_symbols == 1:
219 232 # Plus for contracted folders, minus for expanded
220 self.MarkerDefine(stc.STC_MARKNUM_FOLDEROPEN, stc.STC_MARK_MINUS, "white", "black")
221 self.MarkerDefine(stc.STC_MARKNUM_FOLDER, stc.STC_MARK_PLUS, "white", "black")
222 self.MarkerDefine(stc.STC_MARKNUM_FOLDERSUB, stc.STC_MARK_EMPTY, "white", "black")
223 self.MarkerDefine(stc.STC_MARKNUM_FOLDERTAIL, stc.STC_MARK_EMPTY, "white", "black")
224 self.MarkerDefine(stc.STC_MARKNUM_FOLDEREND, stc.STC_MARK_EMPTY, "white", "black")
225 self.MarkerDefine(stc.STC_MARKNUM_FOLDEROPENMID, stc.STC_MARK_EMPTY, "white", "black")
226 self.MarkerDefine(stc.STC_MARKNUM_FOLDERMIDTAIL, stc.STC_MARK_EMPTY, "white", "black")
233 self.MarkerDefine(stc.STC_MARKNUM_FOLDEROPEN, \
234 stc.STC_MARK_MINUS, "white", "black")
235 self.MarkerDefine(stc.STC_MARKNUM_FOLDER, \
236 stc.STC_MARK_PLUS, "white", "black")
237 self.MarkerDefine(stc.STC_MARKNUM_FOLDERSUB, \
238 stc.STC_MARK_EMPTY, "white", "black")
239 self.MarkerDefine(stc.STC_MARKNUM_FOLDERTAIL, \
240 stc.STC_MARK_EMPTY, "white", "black")
241 self.MarkerDefine(stc.STC_MARKNUM_FOLDEREND, \
242 stc.STC_MARK_EMPTY, "white", "black")
243 self.MarkerDefine(stc.STC_MARKNUM_FOLDEROPENMID, \
244 stc.STC_MARK_EMPTY, "white", "black")
245 self.MarkerDefine(stc.STC_MARKNUM_FOLDERMIDTAIL, \
246 stc.STC_MARK_EMPTY, "white", "black")
227 247
228 248 elif self.fold_symbols == 2:
229 249 # Like a flattened tree control using circular headers and curved joins
230 self.MarkerDefine(stc.STC_MARKNUM_FOLDEROPEN, stc.STC_MARK_CIRCLEMINUS, "white", "#404040")
231 self.MarkerDefine(stc.STC_MARKNUM_FOLDER, stc.STC_MARK_CIRCLEPLUS, "white", "#404040")
232 self.MarkerDefine(stc.STC_MARKNUM_FOLDERSUB, stc.STC_MARK_VLINE, "white", "#404040")
233 self.MarkerDefine(stc.STC_MARKNUM_FOLDERTAIL, stc.STC_MARK_LCORNERCURVE, "white", "#404040")
234 self.MarkerDefine(stc.STC_MARKNUM_FOLDEREND, stc.STC_MARK_CIRCLEPLUSCONNECTED, "white", "#404040")
235 self.MarkerDefine(stc.STC_MARKNUM_FOLDEROPENMID, stc.STC_MARK_CIRCLEMINUSCONNECTED, "white", "#404040")
236 self.MarkerDefine(stc.STC_MARKNUM_FOLDERMIDTAIL, stc.STC_MARK_TCORNERCURVE, "white", "#404040")
250 self.MarkerDefine(stc.STC_MARKNUM_FOLDEROPEN, \
251 stc.STC_MARK_CIRCLEMINUS, "white", "#404040")
252 self.MarkerDefine(stc.STC_MARKNUM_FOLDER, \
253 stc.STC_MARK_CIRCLEPLUS, "white", "#404040")
254 self.MarkerDefine(stc.STC_MARKNUM_FOLDERSUB, \
255 stc.STC_MARK_VLINE, "white", "#404040")
256 self.MarkerDefine(stc.STC_MARKNUM_FOLDERTAIL, \
257 stc.STC_MARK_LCORNERCURVE, "white", "#404040")
258 self.MarkerDefine(stc.STC_MARKNUM_FOLDEREND, \
259 stc.STC_MARK_CIRCLEPLUSCONNECTED, "white", "#404040")
260 self.MarkerDefine(stc.STC_MARKNUM_FOLDEROPENMID, \
261 stc.STC_MARK_CIRCLEMINUSCONNECTED, "white", "#404040")
262 self.MarkerDefine(stc.STC_MARKNUM_FOLDERMIDTAIL, \
263 stc.STC_MARK_TCORNERCURVE, "white", "#404040")
237 264
238 265 elif self.fold_symbols == 3:
239 266 # Like a flattened tree control using square headers
240 self.MarkerDefine(stc.STC_MARKNUM_FOLDEROPEN, stc.STC_MARK_BOXMINUS, "white", "#808080")
241 self.MarkerDefine(stc.STC_MARKNUM_FOLDER, stc.STC_MARK_BOXPLUS, "white", "#808080")
242 self.MarkerDefine(stc.STC_MARKNUM_FOLDERSUB, stc.STC_MARK_VLINE, "white", "#808080")
243 self.MarkerDefine(stc.STC_MARKNUM_FOLDERTAIL, stc.STC_MARK_LCORNER, "white", "#808080")
244 self.MarkerDefine(stc.STC_MARKNUM_FOLDEREND, stc.STC_MARK_BOXPLUSCONNECTED, "white", "#808080")
245 self.MarkerDefine(stc.STC_MARKNUM_FOLDEROPENMID, stc.STC_MARK_BOXMINUSCONNECTED, "white", "#808080")
246 self.MarkerDefine(stc.STC_MARKNUM_FOLDERMIDTAIL, stc.STC_MARK_TCORNER, "white", "#808080")
267 self.MarkerDefine(stc.STC_MARKNUM_FOLDEROPEN, \
268 stc.STC_MARK_BOXMINUS, "white", "#808080")
269 self.MarkerDefine(stc.STC_MARKNUM_FOLDER, \
270 stc.STC_MARK_BOXPLUS, "white", "#808080")
271 self.MarkerDefine(stc.STC_MARKNUM_FOLDERSUB, \
272 stc.STC_MARK_VLINE, "white", "#808080")
273 self.MarkerDefine(stc.STC_MARKNUM_FOLDERTAIL, \
274 stc.STC_MARK_LCORNER, "white", "#808080")
275 self.MarkerDefine(stc.STC_MARKNUM_FOLDEREND, \
276 stc.STC_MARK_BOXPLUSCONNECTED, "white", "#808080")
277 self.MarkerDefine(stc.STC_MARKNUM_FOLDEROPENMID, \
278 stc.STC_MARK_BOXMINUSCONNECTED, "white", "#808080")
279 self.MarkerDefine(stc.STC_MARKNUM_FOLDERMIDTAIL, \
280 stc.STC_MARK_TCORNER, "white", "#808080")
247 281
248 282
249 283 self.Bind(stc.EVT_STC_UPDATEUI, self.OnUpdateUI)
250 284 self.Bind(stc.EVT_STC_MARGINCLICK, self.OnMarginClick)
251 285 self.Bind(wx.EVT_KEY_DOWN, self.OnKeyPressed)
252 286
253 287 # Make some styles, The lexer defines what each style is used for, we
254 288 # just have to define what each style looks like. This set is adapted from
255 289 # Scintilla sample property files.
256 290
257 291 # Global default styles for all languages
258 292 self.StyleSetSpec(stc.STC_STYLE_DEFAULT, "face:%(helv)s,size:%(size)d" % faces)
259 293 self.StyleClearAll() # Reset all to be like the default
260 294
261 295 # Global default styles for all languages
262 296 self.StyleSetSpec(stc.STC_STYLE_DEFAULT, "face:%(helv)s,size:%(size)d" % faces)
263 297 self.StyleSetSpec(stc.STC_STYLE_LINENUMBER, "back:#C0C0C0,face:%(helv)s,size:%(size2)d" % faces)
264 298 self.StyleSetSpec(stc.STC_STYLE_CONTROLCHAR, "face:%(other)s" % faces)
265 299 self.StyleSetSpec(stc.STC_STYLE_BRACELIGHT, "fore:#FFFFFF,back:#0000FF,bold")
266 300 self.StyleSetSpec(stc.STC_STYLE_BRACEBAD, "fore:#000000,back:#FF0000,bold")
267 301
268 302 # Python styles
269 303 # Default
270 304 self.StyleSetSpec(stc.STC_P_DEFAULT, "fore:#000000,face:%(helv)s,size:%(size)d" % faces)
271 305 # Comments
272 306 self.StyleSetSpec(stc.STC_P_COMMENTLINE, "fore:#007F00,face:%(other)s,size:%(size)d" % faces)
273 307 # Number
274 308 self.StyleSetSpec(stc.STC_P_NUMBER, "fore:#007F7F,size:%(size)d" % faces)
275 309 # String
276 310 self.StyleSetSpec(stc.STC_P_STRING, "fore:#7F007F,face:%(helv)s,size:%(size)d" % faces)
277 311 # Single quoted string
278 312 self.StyleSetSpec(stc.STC_P_CHARACTER, "fore:#7F007F,face:%(helv)s,size:%(size)d" % faces)
279 313 # Keyword
280 314 self.StyleSetSpec(stc.STC_P_WORD, "fore:#00007F,bold,size:%(size)d" % faces)
281 315 # Triple quotes
282 316 self.StyleSetSpec(stc.STC_P_TRIPLE, "fore:#7F0000,size:%(size)d" % faces)
283 317 # Triple double quotes
284 318 self.StyleSetSpec(stc.STC_P_TRIPLEDOUBLE, "fore:#7F0000,size:%(size)d" % faces)
285 319 # Class name definition
286 320 self.StyleSetSpec(stc.STC_P_CLASSNAME, "fore:#0000FF,bold,underline,size:%(size)d" % faces)
287 321 # Function or method name definition
288 322 self.StyleSetSpec(stc.STC_P_DEFNAME, "fore:#007F7F,bold,size:%(size)d" % faces)
289 323 # Operators
290 324 self.StyleSetSpec(stc.STC_P_OPERATOR, "bold,size:%(size)d" % faces)
291 325 # Identifiers
292 326 self.StyleSetSpec(stc.STC_P_IDENTIFIER, "fore:#000000,face:%(helv)s,size:%(size)d" % faces)
293 327 # Comment-blocks
294 328 self.StyleSetSpec(stc.STC_P_COMMENTBLOCK, "fore:#7F7F7F,size:%(size)d" % faces)
295 329 # End of line where string is not closed
296 330 self.StyleSetSpec(stc.STC_P_STRINGEOL, "fore:#000000,face:%(mono)s,back:#E0C0E0,eol,size:%(size)d" % faces)
297 331
298 332 self.SetCaretForeground("BLUE")
299 333
300 334
301 335 # register some images for use in the AutoComplete box.
302 336 #self.RegisterImage(1, images.getSmilesBitmap())
303 337 #self.RegisterImage(2,
304 338 # wx.ArtProvider.GetBitmap(wx.ART_NEW, size=(16,16)))
305 339 #self.RegisterImage(3,
306 340 # wx.ArtProvider.GetBitmap(wx.ART_COPY, size=(16,16)))
307 341
308 342
309 343 def OnKeyPressed(self, event):
310 344 if self.CallTipActive():
311 345 self.CallTipCancel()
312 346 key = event.GetKeyCode()
313 347
314 348 if key == 32 and event.ControlDown():
315 349 pos = self.GetCurrentPos()
316 350
317 351 # Tips
318 352 if event.ShiftDown():
319 353 self.CallTipSetBackground("yellow")
320 354 self.CallTipShow(pos, 'lots of of text: blah, blah, blah\n\n'
321 355 'show some suff, maybe parameters..\n\n'
322 356 'fubar(param1, param2)')
323 357 # Code completion
324 358 else:
325 359 #lst = []
326 360 #for x in range(50000):
327 361 # lst.append('%05d' % x)
328 362 #st = " ".join(lst)
329 363 #print len(st)
330 364 #self.AutoCompShow(0, st)
331 365
332 366 kw = keyword.kwlist[:]
333 367
334 368 kw.sort() # Python sorts are case sensitive
335 369 self.AutoCompSetIgnoreCase(False) # so this needs to match
336 370
337 371 # Images are specified with a appended "?type"
338 372 for i in range(len(kw)):
339 373 if kw[i] in keyword.kwlist:
340 374 kw[i] = kw[i]# + "?1"
341 375
342 376 self.AutoCompShow(0, " ".join(kw))
343 377 else:
344 378 event.Skip()
345 379
346 380
347 381 def OnUpdateUI(self, evt):
348 382 # check for matching braces
349 383 braceAtCaret = -1
350 384 braceOpposite = -1
351 385 charBefore = None
352 386 caretPos = self.GetCurrentPos()
353 387
354 388 if caretPos > 0:
355 389 charBefore = self.GetCharAt(caretPos - 1)
356 390 styleBefore = self.GetStyleAt(caretPos - 1)
357 391
358 392 # check before
359 393 if charBefore and chr(charBefore) in "[]{}()" and styleBefore == stc.STC_P_OPERATOR:
360 394 braceAtCaret = caretPos - 1
361 395
362 396 # check after
363 397 if braceAtCaret < 0:
364 398 charAfter = self.GetCharAt(caretPos)
365 399 styleAfter = self.GetStyleAt(caretPos)
366
400
367 401 if charAfter and chr(charAfter) in "[]{}()" and styleAfter == stc.STC_P_OPERATOR:
368 402 braceAtCaret = caretPos
369 403
370 404 if braceAtCaret >= 0:
371 405 braceOpposite = self.BraceMatch(braceAtCaret)
372 406
373 407 if braceAtCaret != -1 and braceOpposite == -1:
374 408 self.BraceBadLight(braceAtCaret)
375 409 else:
376 410 self.BraceHighlight(braceAtCaret, braceOpposite)
377 411 #pt = self.PointFromPosition(braceOpposite)
378 412 #self.Refresh(True, wxRect(pt.x, pt.y, 5,5))
379 413 #print pt
380 414 #self.Refresh(False)
381 415
382 416
383 417 def OnMarginClick(self, evt):
384 418 # fold and unfold as needed
385 419 if evt.GetMargin() == 2:
386 420 if evt.GetShift() and evt.GetControl():
387 421 self.FoldAll()
388 422 else:
389 423 lineClicked = self.LineFromPosition(evt.GetPosition())
390 424
391 425 if self.GetFoldLevel(lineClicked) & stc.STC_FOLDLEVELHEADERFLAG:
392 426 if evt.GetShift():
393 427 self.SetFoldExpanded(lineClicked, True)
394 428 self.Expand(lineClicked, True, True, 1)
395 429 elif evt.GetControl():
396 430 if self.GetFoldExpanded(lineClicked):
397 431 self.SetFoldExpanded(lineClicked, False)
398 432 self.Expand(lineClicked, False, True, 0)
399 433 else:
400 434 self.SetFoldExpanded(lineClicked, True)
401 435 self.Expand(lineClicked, True, True, 100)
402 436 else:
403 437 self.ToggleFold(lineClicked)
404 438
405 439
406 440 def FoldAll(self):
407 441 lineCount = self.GetLineCount()
408 442 expanding = True
409 443
410 444 # find out if we are folding or unfolding
411 445 for lineNum in range(lineCount):
412 446 if self.GetFoldLevel(lineNum) & stc.STC_FOLDLEVELHEADERFLAG:
413 447 expanding = not self.GetFoldExpanded(lineNum)
414 448 break
415 449
416 450 lineNum = 0
417 451
418 452 while lineNum < lineCount:
419 453 level = self.GetFoldLevel(lineNum)
420 454 if level & stc.STC_FOLDLEVELHEADERFLAG and \
421 455 (level & stc.STC_FOLDLEVELNUMBERMASK) == stc.STC_FOLDLEVELBASE:
422 456
423 457 if expanding:
424 458 self.SetFoldExpanded(lineNum, True)
425 459 lineNum = self.Expand(lineNum, True)
426 460 lineNum = lineNum - 1
427 461 else:
428 462 lastChild = self.GetLastChild(lineNum, -1)
429 463 self.SetFoldExpanded(lineNum, False)
430 464
431 465 if lastChild > lineNum:
432 466 self.HideLines(lineNum+1, lastChild)
433 467
434 468 lineNum = lineNum + 1
435 469
436 470
437 471
438 472 def Expand(self, line, doExpand, force=False, visLevels=0, level=-1):
439 473 lastChild = self.GetLastChild(line, level)
440 474 line = line + 1
441 475
442 476 while line <= lastChild:
443 477 if force:
444 478 if visLevels > 0:
445 479 self.ShowLines(line, line)
446 480 else:
447 481 self.HideLines(line, line)
448 482 else:
449 483 if doExpand:
450 484 self.ShowLines(line, line)
451 485
452 486 if level == -1:
453 487 level = self.GetFoldLevel(line)
454 488
455 489 if level & stc.STC_FOLDLEVELHEADERFLAG:
456 490 if force:
457 491 if visLevels > 1:
458 492 self.SetFoldExpanded(line, True)
459 493 else:
460 494 self.SetFoldExpanded(line, False)
461 495
462 496 line = self.Expand(line, doExpand, force, visLevels-1)
463 497
464 498 else:
465 499 if doExpand and self.GetFoldExpanded(line):
466 500 line = self.Expand(line, True, force, visLevels-1)
467 501 else:
468 502 line = self.Expand(line, False, force, visLevels-1)
469 503 else:
470 504 line = line + 1
471 505
472 506 return line
473 507
474 508
475 509 #----------------------------------------------------------------------
@@ -1,915 +1,915 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._ask_exit
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 _ask_exit(self):
74 74 wx.CallAfter(self.ask_exit_callback, ())
75 75
76 76 def _after_execute(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 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.get_banner()
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.get_prompt(),
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.set_threading},
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.do_execute(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 if(self.text_ctrl.getCursorPos()!=0):
637 self.text_ctrl.removeCurrentLine()
638 636 self.doc = self.IP.get_doc_text()
639 637 self.help = self.IP.get_help_text()
640 638 if self.doc:
641 639 self.pager_lines = self.doc[7:].split('\n')
642 640 self.pager_state = 'INIT'
643 641 self.setCurrentState('SHOW_DOC')
644 642 self.pager(self.doc)
645 643 elif self.help:
646 644 self.pager_lines = self.help.split('\n')
647 645 self.pager_state = 'INIT'
648 646 self.setCurrentState('SHOW_DOC')
649 647 self.pager(self.help)
650 648 else:
649 if(self.text_ctrl.getCursorPos()!=0):
650 self.text_ctrl.removeCurrentLine()
651 651 self.stateShowPrompt()
652 652
653 653 def stateShowPrompt(self):
654 654 self.setCurrentState('SHOW_PROMPT')
655 655 self.text_ctrl.setPrompt(self.IP.get_prompt())
656 656 self.text_ctrl.setIndentation(self.IP.get_indentation())
657 657 self.text_ctrl.setPromptCount(self.IP.get_prompt_count())
658 658 self.text_ctrl.showPrompt()
659 659 self.IP.init_history_index()
660 660 self.setCurrentState('IDLE')
661 661
662 662 def setCurrentState(self, state):
663 663 self.cur_state = state
664 664 self.updateStatusTracker(self.cur_state)
665 665
666 666 def pager(self,text):
667 667
668 668 if self.pager_state == 'INIT':
669 669 #print >>sys.__stdout__,"PAGER state:",self.pager_state
670 670 self.pager_nb_lines = len(self.pager_lines)
671 671 self.pager_index = 0
672 672 self.pager_do_remove = False
673 673 self.text_ctrl.write('\n')
674 674 self.pager_state = 'PROCESS_LINES'
675 675
676 676 if self.pager_state == 'PROCESS_LINES':
677 677 #print >>sys.__stdout__,"PAGER state:",self.pager_state
678 678 if self.pager_do_remove == True:
679 679 self.text_ctrl.removeCurrentLine()
680 680 self.pager_do_remove = False
681 681
682 682 if self.pager_nb_lines > 10:
683 683 #print >>sys.__stdout__,"PAGER processing 10 lines"
684 684 if self.pager_index > 0:
685 685 self.text_ctrl.write(">\x01\x1b[1;36m\x02"+self.pager_lines[self.pager_index]+'\n')
686 686 else:
687 687 self.text_ctrl.write("\x01\x1b[1;36m\x02 "+self.pager_lines[self.pager_index]+'\n')
688 688
689 689 for line in self.pager_lines[self.pager_index+1:self.pager_index+9]:
690 690 self.text_ctrl.write("\x01\x1b[1;36m\x02 "+line+'\n')
691 691 self.pager_index += 10
692 692 self.pager_nb_lines -= 10
693 693 self.text_ctrl.write("--- Push Enter to continue or 'Q' to quit---")
694 694 self.pager_do_remove = True
695 695 self.pager_state = 'WAITING'
696 696 return
697 697 else:
698 698 #print >>sys.__stdout__,"PAGER processing last lines"
699 699 if self.pager_nb_lines > 0:
700 700 if self.pager_index > 0:
701 701 self.text_ctrl.write(">\x01\x1b[1;36m\x02"+self.pager_lines[self.pager_index]+'\n')
702 702 else:
703 703 self.text_ctrl.write("\x01\x1b[1;36m\x02 "+self.pager_lines[self.pager_index]+'\n')
704 704
705 705 self.pager_index += 1
706 706 self.pager_nb_lines -= 1
707 707 if self.pager_nb_lines > 0:
708 708 for line in self.pager_lines[self.pager_index:]:
709 709 self.text_ctrl.write("\x01\x1b[1;36m\x02 "+line+'\n')
710 710 self.pager_nb_lines = 0
711 711 self.pager_state = 'DONE'
712 712 self.stateShowPrompt()
713 713
714 714 #------------------------ Key Handler ------------------------------------
715 715 def keyPress(self, event):
716 716 '''
717 717 Key press callback with plenty of shell goodness, like history,
718 718 autocompletions, etc.
719 719 '''
720 720 if event.GetKeyCode() == ord('C'):
721 721 if event.Modifiers == wx.MOD_CONTROL or event.Modifiers == wx.MOD_ALT:
722 722 if self.cur_state == 'WAIT_END_OF_EXECUTION':
723 723 #we raise an exception inside the IPython thread container
724 724 self.IP.ce.raise_exc(KeyboardInterrupt)
725 725 return
726 726
727 727 #let this before 'wx.WXK_RETURN' because we have to put 'IDLE'
728 728 #mode if AutoComp has been set as inactive
729 729 if self.cur_state == 'COMPLETING':
730 730 if not self.text_ctrl.AutoCompActive():
731 731 self.cur_state = 'IDLE'
732 732 else:
733 733 event.Skip()
734 734
735 735 if event.KeyCode == wx.WXK_RETURN:
736 736 if self.cur_state == 'IDLE':
737 737 #we change the state ot the state machine
738 738 self.setCurrentState('DO_EXECUTE_LINE')
739 739 self.stateDoExecuteLine()
740 740 return
741 741
742 742 if self.pager_state == 'WAITING':
743 743 self.pager_state = 'PROCESS_LINES'
744 744 self.pager(self.doc)
745 745 return
746 746
747 747 if self.cur_state == 'WAITING_USER_INPUT':
748 748 line=self.text_ctrl.getCurrentLine()
749 749 self.text_ctrl.write('\n')
750 750 self.setCurrentState('WAIT_END_OF_EXECUTION')
751 751 return
752 752
753 753 if event.GetKeyCode() in [ord('q'),ord('Q')]:
754 754 if self.pager_state == 'WAITING':
755 755 self.pager_state = 'DONE'
756 756 self.text_ctrl.write('\n')
757 757 self.stateShowPrompt()
758 758 return
759 759
760 760 if self.cur_state == 'WAITING_USER_INPUT':
761 761 event.Skip()
762 762
763 763 if self.cur_state == 'IDLE':
764 764 if event.KeyCode == wx.WXK_UP:
765 765 history = self.IP.history_back()
766 766 self.text_ctrl.writeHistory(history)
767 767 return
768 768 if event.KeyCode == wx.WXK_DOWN:
769 769 history = self.IP.history_forward()
770 770 self.text_ctrl.writeHistory(history)
771 771 return
772 772 if event.KeyCode == wx.WXK_TAB:
773 773 #if line empty we disable tab completion
774 774 if not self.text_ctrl.getCurrentLine().strip():
775 775 self.text_ctrl.write('\t')
776 776 return
777 777 completed, possibilities = self.IP.complete(self.text_ctrl.getCurrentLine())
778 778 if len(possibilities) > 1:
779 779 if self.text_ctrl.autocomplete_mode == 'IPYTHON':
780 780 cur_slice = self.text_ctrl.getCurrentLine()
781 781 self.text_ctrl.write('\n')
782 782 self.text_ctrl.writeCompletion(possibilities)
783 783 self.text_ctrl.write('\n')
784 784
785 785 self.text_ctrl.showPrompt()
786 786 self.text_ctrl.write(cur_slice)
787 787 self.text_ctrl.changeLine(completed or cur_slice)
788 788 else:
789 789 self.cur_state = 'COMPLETING'
790 790 self.text_ctrl.writeCompletion(possibilities)
791 791 else:
792 792 self.text_ctrl.changeLine(completed or cur_slice)
793 793 return
794 794 event.Skip()
795 795
796 796 #------------------------ Option Section ---------------------------------
797 797 def evtCheckOptionCompletion(self, event):
798 798 if event.IsChecked():
799 799 self.options['completion']['value']='STC'
800 800 else:
801 801 self.options['completion']['value']='IPYTHON'
802 802 self.text_ctrl.setCompletionMethod(self.options['completion']['value'])
803 803 self.updateOptionTracker('completion',
804 804 self.options['completion']['value'])
805 805 self.text_ctrl.SetFocus()
806 806
807 807 def evtCheckOptionBackgroundColor(self, event):
808 808 if event.IsChecked():
809 809 self.options['background_color']['value']='WHITE'
810 810 else:
811 811 self.options['background_color']['value']='BLACK'
812 812 self.text_ctrl.setBackgroundColor(self.options['background_color']['value'])
813 813 self.updateOptionTracker('background_color',
814 814 self.options['background_color']['value'])
815 815 self.text_ctrl.SetFocus()
816 816
817 817 def evtCheckOptionThreading(self, event):
818 818 if event.IsChecked():
819 819 self.options['threading']['value']='True'
820 820 self.IP.set_threading(True)
821 821 self.cout.write = self.text_ctrl.asyncWrite
822 822 else:
823 823 self.options['threading']['value']='False'
824 824 self.IP.set_threading(False)
825 825 self.cout.write = self.text_ctrl.write
826 826 self.updateOptionTracker('threading',
827 827 self.options['threading']['value'])
828 828 self.text_ctrl.SetFocus()
829 829
830 830 def getOptions(self):
831 831 return self.options
832 832
833 833 def reloadOptions(self,options):
834 834 self.options = options
835 835 for key in self.options.keys():
836 836 value = self.options[key]['value']
837 837 self.options[key]['checkbox'].SetValue(self.options[key][value])
838 838 self.options[key]['setfunc'](value)
839 839
840 840 if self.options['threading']['value']=='True':
841 841 self.IP.set_threading(True)
842 842 self.cout.write = self.text_ctrl.asyncWrite
843 843 else:
844 844 self.IP.set_threading(False)
845 845 self.cout.write = self.text_ctrl.write
846 846
847 847 #------------------------ Hook Section -----------------------------------
848 848 def updateOptionTracker(self,name,value):
849 849 '''
850 850 Default history tracker (does nothing)
851 851 '''
852 852 pass
853 853
854 854 def setOptionTrackerHook(self,func):
855 855 '''
856 856 Define a new history tracker
857 857 '''
858 858 self.updateOptionTracker = func
859 859
860 860 def updateHistoryTracker(self,command_line):
861 861 '''
862 862 Default history tracker (does nothing)
863 863 '''
864 864 pass
865 865
866 866 def setHistoryTrackerHook(self,func):
867 867 '''
868 868 Define a new history tracker
869 869 '''
870 870 self.updateHistoryTracker = func
871 871
872 872 def updateStatusTracker(self,status):
873 873 '''
874 874 Default status tracker (does nothing)
875 875 '''
876 876 pass
877 877
878 878 def setStatusTrackerHook(self,func):
879 879 '''
880 880 Define a new status tracker
881 881 '''
882 882 self.updateStatusTracker = func
883 883
884 884 def askExitHandler(self, event):
885 885 '''
886 886 Default exit handler
887 887 '''
888 888 self.text_ctrl.write('\nExit callback has not been set.')
889 889
890 890 def setAskExitHandler(self, func):
891 891 '''
892 892 Define an exit handler
893 893 '''
894 894 self.askExitHandler = func
895 895
896 896 if __name__ == '__main__':
897 897 # Some simple code to test the shell widget.
898 898 class MainWindow(wx.Frame):
899 899 def __init__(self, parent, id, title):
900 900 wx.Frame.__init__(self, parent, id, title, size=(300,250))
901 901 self._sizer = wx.BoxSizer(wx.VERTICAL)
902 902 self.shell = IPShellWidget(self)
903 903 self._sizer.Add(self.shell, 1, wx.EXPAND)
904 904 self.SetSizer(self._sizer)
905 905 self.SetAutoLayout(1)
906 906 self.Show(True)
907 907
908 908 app = wx.PySimpleApp()
909 909 frame = MainWindow(None, wx.ID_ANY, 'Ipython')
910 910 frame.SetSize((780, 460))
911 911 shell = frame.shell
912 912
913 913 app.MainLoop()
914 914
915 915
General Comments 0
You need to be logged in to leave comments. Login now