##// END OF EJS Templates
Added an option to enable/disable threading as suggested by Ville.
ldufrechou -
Show More
@@ -1,471 +1,507 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 def __init__(self, instance, after):
65 def __init__(self, instance):
66 66 ThreadEx.__init__(self)
67 67 self.instance = instance
68 self._afterExecute = after
69
68
70 69 def run(self):
71 70 '''Thread main loop'''
72 71 try:
73 72 self.instance._doc_text = None
74 73 self.instance._help_text = None
75 74 self.instance._execute()
76 75 # used for uper class to generate event after execution
77 self._afterExecute()
76 self.instance._afterExecute()
78 77
79 78 except KeyboardInterrupt:
80 79 pass
81 80
82 81
83 82 ##############################################################################
84 83 class NonBlockingIPShell(object):
85 84 '''
86 85 Create an IPython instance, running the commands in a separate,
87 86 non-blocking thread.
88 87 This allows embedding in any GUI without blockage.
89 88
90 89 Note: The ThreadEx class supports asynchroneous function call
91 90 via raise_exc()
92 91 '''
93 92
94 93 def __init__(self, argv=[], user_ns={}, user_global_ns=None,
95 94 cin=None, cout=None, cerr=None,
96 95 ask_exit_handler=None):
97 96 '''
98 97 @param argv: Command line options for IPython
99 98 @type argv: list
100 99 @param user_ns: User namespace.
101 100 @type user_ns: dictionary
102 101 @param user_global_ns: User global namespace.
103 102 @type user_global_ns: dictionary.
104 103 @param cin: Console standard input.
105 104 @type cin: IO stream
106 105 @param cout: Console standard output.
107 106 @type cout: IO stream
108 107 @param cerr: Console standard error.
109 108 @type cerr: IO stream
110 109 @param exit_handler: Replacement for builtin exit() function
111 110 @type exit_handler: function
112 111 @param time_loop: Define the sleep time between two thread's loop
113 112 @type int
114 113 '''
115 114 #ipython0 initialisation
116 115 self._IP = None
117 116 self._term = None
118 117 self.initIpython0(argv, user_ns, user_global_ns,
119 118 cin, cout, cerr,
120 119 ask_exit_handler)
121 120
122 121 #vars used by _execute
123 122 self._iter_more = 0
124 123 self._history_level = 0
125 124 self._complete_sep = re.compile('[\s\{\}\[\]\(\)\=]')
126 125 self._prompt = str(self._IP.outputcache.prompt1).strip()
127 126
128 127 #thread working vars
129 128 self._line_to_execute = ''
130
129 self._threading = True
130
131 131 #vars that will be checked by GUI loop to handle thread states...
132 132 #will be replaced later by PostEvent GUI funtions...
133 133 self._doc_text = None
134 134 self._help_text = None
135 135 self._add_button = None
136 136
137 137 def initIpython0(self, argv=[], user_ns={}, user_global_ns=None,
138 138 cin=None, cout=None, cerr=None,
139 139 ask_exit_handler=None):
140 ''' Initialize an ithon0 instance '''
140 ''' Initialize an ipython0 instance '''
141 141
142 142 #first we redefine in/out/error functions of IPython
143 143 if cin:
144 144 IPython.Shell.Term.cin = cin
145 145 if cout:
146 146 IPython.Shell.Term.cout = cout
147 147 if cerr:
148 148 IPython.Shell.Term.cerr = cerr
149 149
150 150 # This is to get rid of the blockage that accurs during
151 151 # IPython.Shell.InteractiveShell.user_setup()
152 152 IPython.iplib.raw_input = lambda x: None
153 153
154 154 self._term = IPython.genutils.IOTerm(cin=cin, cout=cout, cerr=cerr)
155 155
156 156 excepthook = sys.excepthook
157 157 #Hack to save sys.displayhook, because ipython seems to overwrite it...
158 158 self.sys_displayhook_ori = sys.displayhook
159 159
160 160 self._IP = IPython.Shell.make_IPython(
161 161 argv,user_ns=user_ns,
162 162 user_global_ns=user_global_ns,
163 163 embedded=True,
164 164 shell_class=IPython.Shell.InteractiveShell)
165 165
166 166 #we restore sys.displayhook
167 167 sys.displayhook = self.sys_displayhook_ori
168 168
169 169 #we replace IPython default encoding by wx locale encoding
170 170 loc = locale.getpreferredencoding()
171 171 if loc:
172 172 self._IP.stdin_encoding = loc
173 173 #we replace the ipython default pager by our pager
174 174 self._IP.set_hook('show_in_pager', self._pager)
175 175
176 176 #we replace the ipython default shell command caller by our shell handler
177 177 self._IP.set_hook('shell_hook', self._shell)
178 178
179 179 #we replace the ipython default input command caller by our method
180 180 IPython.iplib.raw_input_original = self._raw_input
181 181 #we replace the ipython default exit command by our method
182 182 self._IP.exit = ask_exit_handler
183 183 #we replace the help command
184 184 self._IP.user_ns['help'] = _Helper(self._pager_help)
185 185
186 186 #we disable cpase magic... until we found a way to use it properly.
187 187 #import IPython.ipapi
188 188 ip = IPython.ipapi.get()
189 189 def bypassMagic(self, arg):
190 190 print '%this magic is currently disabled.'
191 191 ip.expose_magic('cpaste', bypassMagic)
192 192
193 193 sys.excepthook = excepthook
194 194
195 195 #----------------------- Thread management section ----------------------
196 196 def doExecute(self, line):
197 197 """
198 198 Tell the thread to process the 'line' command
199 199 """
200 200
201 201 self._line_to_execute = line
202 #we launch the ipython line execution in a thread to make it interruptible
203 #with include it in self namespace to be able to call ce.raise_exc(KeyboardInterrupt)
204 self.ce = _CodeExecutor(self, self._afterExecute)
205 self.ce.start()
206
202 if self._threading:
203 #we launch the ipython line execution in a thread to make it interruptible
204 #with include it in self namespace to be able to call ce.raise_exc(KeyboardInterrupt)
205 self.ce = _CodeExecutor(self)
206 self.ce.start()
207 else:
208 try:
209 self._doc_text = None
210 self._help_text = None
211 self._execute()
212 # used for uper class to generate event after execution
213 self._afterExecute()
214
215 except KeyboardInterrupt:
216 pass
207 217 #----------------------- IPython management section ----------------------
218 def getThreading(self):
219 """
220 Returns threading status, is set to True, then each command sent to
221 the interpreter will be executed in a separated thread allowing,
222 for example, breaking a long running commands.
223 Disallowing it, permits better compatibilty with instance that is embedding
224 IPython instance.
225
226 @return: Execution method
227 @rtype: bool
228 """
229 return self._threading
230
231 def setThreading(self, state):
232 """
233 Sets threading state, if set to True, then each command sent to
234 the interpreter will be executed in a separated thread allowing,
235 for example, breaking a long running commands.
236 Disallowing it, permits better compatibilty with instance that is embedding
237 IPython instance.
238
239 @param state: Sets threading state
240 @type bool
241 """
242 self._threading = state
243
208 244 def getDocText(self):
209 245 """
210 246 Returns the output of the processing that need to be paged (if any)
211 247
212 248 @return: The std output string.
213 249 @rtype: string
214 250 """
215 251 return self._doc_text
216 252
217 253 def getHelpText(self):
218 254 """
219 255 Returns the output of the processing that need to be paged via help pager(if any)
220 256
221 257 @return: The std output string.
222 258 @rtype: string
223 259 """
224 260 return self._help_text
225 261
226 262 def getBanner(self):
227 263 """
228 264 Returns the IPython banner for useful info on IPython instance
229 265
230 266 @return: The banner string.
231 267 @rtype: string
232 268 """
233 269 return self._IP.BANNER
234 270
235 271 def getPromptCount(self):
236 272 """
237 273 Returns the prompt number.
238 274 Each time a user execute a line in the IPython shell the prompt count is increased
239 275
240 276 @return: The prompt number
241 277 @rtype: int
242 278 """
243 279 return self._IP.outputcache.prompt_count
244 280
245 281 def getPrompt(self):
246 282 """
247 283 Returns current prompt inside IPython instance
248 284 (Can be In [...]: ot ...:)
249 285
250 286 @return: The current prompt.
251 287 @rtype: string
252 288 """
253 289 return self._prompt
254 290
255 291 def getIndentation(self):
256 292 """
257 293 Returns the current indentation level
258 294 Usefull to put the caret at the good start position if we want to do autoindentation.
259 295
260 296 @return: The indentation level.
261 297 @rtype: int
262 298 """
263 299 return self._IP.indent_current_nsp
264 300
265 301 def updateNamespace(self, ns_dict):
266 302 '''
267 303 Add the current dictionary to the shell namespace.
268 304
269 305 @param ns_dict: A dictionary of symbol-values.
270 306 @type ns_dict: dictionary
271 307 '''
272 308 self._IP.user_ns.update(ns_dict)
273 309
274 310 def complete(self, line):
275 311 '''
276 312 Returns an auto completed line and/or posibilities for completion.
277 313
278 314 @param line: Given line so far.
279 315 @type line: string
280 316
281 317 @return: Line completed as for as possible,
282 318 and possible further completions.
283 319 @rtype: tuple
284 320 '''
285 321 split_line = self._complete_sep.split(line)
286 322 possibilities = self._IP.complete(split_line[-1])
287 323 if possibilities:
288 324
289 325 def _commonPrefix(str1, str2):
290 326 '''
291 327 Reduction function. returns common prefix of two given strings.
292 328
293 329 @param str1: First string.
294 330 @type str1: string
295 331 @param str2: Second string
296 332 @type str2: string
297 333
298 334 @return: Common prefix to both strings.
299 335 @rtype: string
300 336 '''
301 337 for i in range(len(str1)):
302 338 if not str2.startswith(str1[:i+1]):
303 339 return str1[:i]
304 340 return str1
305 341 common_prefix = reduce(_commonPrefix, possibilities)
306 342 completed = line[:-len(split_line[-1])]+common_prefix
307 343 else:
308 344 completed = line
309 345 return completed, possibilities
310 346
311 347 def historyBack(self):
312 348 '''
313 349 Provides one history command back.
314 350
315 351 @return: The command string.
316 352 @rtype: string
317 353 '''
318 354 history = ''
319 355 #the below while loop is used to suppress empty history lines
320 356 while((history == '' or history == '\n') and self._history_level >0):
321 357 if self._history_level >= 1:
322 358 self._history_level -= 1
323 359 history = self._getHistory()
324 360 return history
325 361
326 362 def historyForward(self):
327 363 '''
328 364 Provides one history command forward.
329 365
330 366 @return: The command string.
331 367 @rtype: string
332 368 '''
333 369 history = ''
334 370 #the below while loop is used to suppress empty history lines
335 371 while((history == '' or history == '\n') \
336 372 and self._history_level <= self._getHistoryMaxIndex()):
337 373 if self._history_level < self._getHistoryMaxIndex():
338 374 self._history_level += 1
339 375 history = self._getHistory()
340 376 else:
341 377 if self._history_level == self._getHistoryMaxIndex():
342 378 history = self._getHistory()
343 379 self._history_level += 1
344 380 else:
345 381 history = ''
346 382 return history
347 383
348 384 def initHistoryIndex(self):
349 385 '''
350 386 set history to last command entered
351 387 '''
352 388 self._history_level = self._getHistoryMaxIndex()+1
353 389
354 390 #----------------------- IPython PRIVATE management section --------------
355 391 def _afterExecute(self):
356 392 '''
357 393 Can be redefined to generate post event after excution is done
358 394 '''
359 395 pass
360 396
361 397 #def _askExit(self):
362 398 # '''
363 399 # Can be redefined to generate post event to exit the Ipython shell
364 400 # '''
365 401 # pass
366 402
367 403 def _getHistoryMaxIndex(self):
368 404 '''
369 405 returns the max length of the history buffer
370 406
371 407 @return: history length
372 408 @rtype: int
373 409 '''
374 410 return len(self._IP.input_hist_raw)-1
375 411
376 412 def _getHistory(self):
377 413 '''
378 414 Get's the command string of the current history level.
379 415
380 416 @return: Historic command stri
381 417 @rtype: string
382 418 '''
383 419 rv = self._IP.input_hist_raw[self._history_level].strip('\n')
384 420 return rv
385 421
386 422 def _pager_help(self, text):
387 423 '''
388 424 This function is used as a callback replacment to IPython help pager function
389 425
390 426 It puts the 'text' value inside the self._help_text string that can be retrived via
391 427 getHelpText function.
392 428 '''
393 429 if self._help_text == None:
394 430 self._help_text = text
395 431 else:
396 432 self._help_text += text
397 433
398 434 def _pager(self, IP, text):
399 435 '''
400 436 This function is used as a callback replacment to IPython pager function
401 437
402 438 It puts the 'text' value inside the self._doc_text string that can be retrived via
403 439 getDocText function.
404 440 '''
405 441 self._doc_text = text
406 442
407 443 def _raw_input(self, prompt=''):
408 444 '''
409 445 Custom raw_input() replacement. Get's current line from console buffer.
410 446
411 447 @param prompt: Prompt to print. Here for compatability as replacement.
412 448 @type prompt: string
413 449
414 450 @return: The current command line text.
415 451 @rtype: string
416 452 '''
417 453 return self._line_to_execute
418 454
419 455 def _execute(self):
420 456 '''
421 457 Executes the current line provided by the shell object.
422 458 '''
423 459 orig_stdout = sys.stdout
424 460 sys.stdout = IPython.Shell.Term.cout
425 461
426 462 try:
427 463 line = self._IP.raw_input(None, self._iter_more)
428 464 if self._IP.autoindent:
429 465 self._IP.readline_startup_hook(None)
430 466
431 467 except KeyboardInterrupt:
432 468 self._IP.write('\nKeyboardInterrupt\n')
433 469 self._IP.resetbuffer()
434 470 # keep cache in sync with the prompt counter:
435 471 self._IP.outputcache.prompt_count -= 1
436 472
437 473 if self._IP.autoindent:
438 474 self._IP.indent_current_nsp = 0
439 475 self._iter_more = 0
440 476 except:
441 477 self._IP.showtraceback()
442 478 else:
443 479 self._iter_more = self._IP.push(line)
444 480 if (self._IP.SyntaxTB.last_syntax_error and self._IP.rc.autoedit_syntax):
445 481 self._IP.edit_syntax_error()
446 482 if self._iter_more:
447 483 self._prompt = str(self._IP.outputcache.prompt2).strip()
448 484 if self._IP.autoindent:
449 485 self._IP.readline_startup_hook(self._IP.pre_readline)
450 486 else:
451 487 self._prompt = str(self._IP.outputcache.prompt1).strip()
452 488 self._IP.indent_current_nsp = 0 #we set indentation to 0
453 489 sys.stdout = orig_stdout
454 490
455 491 def _shell(self, ip, cmd):
456 492 '''
457 493 Replacement method to allow shell commands without them blocking.
458 494
459 495 @param ip: Ipython instance, same as self._IP
460 496 @type cmd: Ipython instance
461 497 @param cmd: Shell command to execute.
462 498 @type cmd: string
463 499 '''
464 500 stdin, stdout = os.popen4(cmd)
465 501 result = stdout.read().decode('cp437').encode(locale.getpreferredencoding())
466 502 #we use print command because the shell command is called
467 503 #inside IPython instance and thus is redirected to thread cout
468 504 #"\x01\x1b[1;36m\x02" <-- add colour to the text...
469 505 print "\x01\x1b[1;36m\x02"+result
470 506 stdout.close()
471 507 stdin.close()
@@ -1,885 +1,907 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 __version__ = 0.8
22 __version__ = 0.9
23 23 __author__ = "Laurent Dufrechou"
24 24 __email__ = "laurent.dufrechou _at_ gmail.com"
25 25 __license__ = "BSD"
26 26
27 27 import wx
28 28 import wx.stc as stc
29 29
30 30 import re
31 31 from StringIO import StringIO
32 32
33 33 import sys
34 34 import codecs
35 35 import locale
36 36 for enc in (locale.getpreferredencoding(),
37 37 sys.getfilesystemencoding(),
38 38 sys.getdefaultencoding()):
39 39 try:
40 40 codecs.lookup(enc)
41 41 ENCODING = enc
42 42 break
43 43 except LookupError:
44 44 pass
45 45 else:
46 46 ENCODING = 'utf-8'
47 47
48 48 from ipshell_nonblocking import NonBlockingIPShell
49 49
50 50 class WxNonBlockingIPShell(NonBlockingIPShell):
51 51 '''
52 52 An NonBlockingIPShell Thread that is WX dependent.
53 53 '''
54 54 def __init__(self, parent,
55 55 argv=[],user_ns={},user_global_ns=None,
56 56 cin=None, cout=None, cerr=None,
57 57 ask_exit_handler=None):
58 58
59 59 NonBlockingIPShell.__init__(self, argv, user_ns, user_global_ns,
60 60 cin, cout, cerr,
61 61 ask_exit_handler)
62 62
63 63 self.parent = parent
64 64
65 65 self.ask_exit_callback = ask_exit_handler
66 66 self._IP.exit = self._askExit
67 67
68 68 def addGUIShortcut(self, text, func):
69 69 wx.CallAfter(self.parent.add_button_handler,
70 70 button_info={ 'text':text,
71 71 'func':self.parent.doExecuteLine(func)})
72 72
73 73 def _askExit(self):
74 74 wx.CallAfter(self.ask_exit_callback, ())
75 75
76 76 def _afterExecute(self):
77 77 wx.CallAfter(self.parent.evtStateExecuteDone, ())
78 78
79 79
80 80 class WxConsoleView(stc.StyledTextCtrl):
81 81 '''
82 82 Specialized styled text control view for console-like workflow.
83 83 We use here a scintilla frontend thus it can be reused in any GUI that
84 84 supports scintilla with less work.
85 85
86 86 @cvar ANSI_COLORS_BLACK: Mapping of terminal colors to X11 names.
87 87 (with Black background)
88 88 @type ANSI_COLORS_BLACK: dictionary
89 89
90 90 @cvar ANSI_COLORS_WHITE: Mapping of terminal colors to X11 names.
91 91 (with White background)
92 92 @type ANSI_COLORS_WHITE: dictionary
93 93
94 94 @ivar color_pat: Regex of terminal color pattern
95 95 @type color_pat: _sre.SRE_Pattern
96 96 '''
97 97 ANSI_STYLES_BLACK = {'0;30': [0, 'WHITE'], '0;31': [1, 'RED'],
98 98 '0;32': [2, 'GREEN'], '0;33': [3, 'BROWN'],
99 99 '0;34': [4, 'BLUE'], '0;35': [5, 'PURPLE'],
100 100 '0;36': [6, 'CYAN'], '0;37': [7, 'LIGHT GREY'],
101 101 '1;30': [8, 'DARK GREY'], '1;31': [9, 'RED'],
102 102 '1;32': [10, 'SEA GREEN'], '1;33': [11, 'YELLOW'],
103 103 '1;34': [12, 'LIGHT BLUE'], '1;35':
104 104 [13, 'MEDIUM VIOLET RED'],
105 105 '1;36': [14, 'LIGHT STEEL BLUE'], '1;37': [15, 'YELLOW']}
106 106
107 107 ANSI_STYLES_WHITE = {'0;30': [0, 'BLACK'], '0;31': [1, 'RED'],
108 108 '0;32': [2, 'GREEN'], '0;33': [3, 'BROWN'],
109 109 '0;34': [4, 'BLUE'], '0;35': [5, 'PURPLE'],
110 110 '0;36': [6, 'CYAN'], '0;37': [7, 'LIGHT GREY'],
111 111 '1;30': [8, 'DARK GREY'], '1;31': [9, 'RED'],
112 112 '1;32': [10, 'SEA GREEN'], '1;33': [11, 'YELLOW'],
113 113 '1;34': [12, 'LIGHT BLUE'], '1;35':
114 114 [13, 'MEDIUM VIOLET RED'],
115 115 '1;36': [14, 'LIGHT STEEL BLUE'], '1;37': [15, 'YELLOW']}
116 116
117 117 def __init__(self, parent, prompt, intro="", background_color="BLACK",
118 118 pos=wx.DefaultPosition, ID = -1, size=wx.DefaultSize,
119 119 style=0, autocomplete_mode = 'IPYTHON'):
120 120 '''
121 121 Initialize console view.
122 122
123 123 @param parent: Parent widget
124 124 @param prompt: User specified prompt
125 125 @type intro: string
126 126 @param intro: User specified startup introduction string
127 127 @type intro: string
128 128 @param background_color: Can be BLACK or WHITE
129 129 @type background_color: string
130 130 @param other: init param of styledTextControl (can be used as-is)
131 131 @param autocomplete_mode: Can be 'IPYTHON' or 'STC'
132 132 'IPYTHON' show autocompletion the ipython way
133 133 'STC" show it scintilla text control way
134 134 '''
135 135 stc.StyledTextCtrl.__init__(self, parent, ID, pos, size, style)
136 136
137 137 ####### Scintilla configuration ###################################
138 138
139 139 # Ctrl + B or Ctrl + N can be used to zoomin/zoomout the text inside
140 140 # the widget
141 141 self.CmdKeyAssign(ord('B'), stc.STC_SCMOD_CTRL, stc.STC_CMD_ZOOMIN)
142 142 self.CmdKeyAssign(ord('N'), stc.STC_SCMOD_CTRL, stc.STC_CMD_ZOOMOUT)
143 143
144 144 #We draw a line at position 80
145 145 self.SetEdgeMode(stc.STC_EDGE_LINE)
146 146 self.SetEdgeColumn(80)
147 147 self.SetEdgeColour(wx.LIGHT_GREY)
148 148
149 149 #self.SetViewWhiteSpace(True)
150 150 #self.SetViewEOL(True)
151 151 self.SetEOLMode(stc.STC_EOL_CRLF)
152 152 #self.SetWrapMode(stc.STC_WRAP_CHAR)
153 153 #self.SetWrapMode(stc.STC_WRAP_WORD)
154 154 self.SetBufferedDraw(True)
155 155 #self.SetUseAntiAliasing(True)
156 156 self.SetLayoutCache(stc.STC_CACHE_PAGE)
157 157 self.SetUndoCollection(False)
158 158 self.SetUseTabs(True)
159 159 self.SetIndent(4)
160 160 self.SetTabWidth(4)
161 161
162 162 self.EnsureCaretVisible()
163 163
164 164 self.SetMargins(3, 3) #text is moved away from border with 3px
165 165 # Suppressing Scintilla margins
166 166 self.SetMarginWidth(0, 0)
167 167 self.SetMarginWidth(1, 0)
168 168 self.SetMarginWidth(2, 0)
169 169
170 170 self.background_color = background_color
171 171 self.buildStyles()
172 172
173 173 self.indent = 0
174 174 self.prompt_count = 0
175 175 self.color_pat = re.compile('\x01?\x1b\[(.*?)m\x02?')
176 176
177 177 self.write(intro)
178 178 self.setPrompt(prompt)
179 179 self.showPrompt()
180 180
181 181 self.autocomplete_mode = autocomplete_mode
182 182
183 183 self.Bind(wx.EVT_KEY_DOWN, self._onKeypress)
184 184
185 185 def buildStyles(self):
186 186 #we define platform specific fonts
187 187 if wx.Platform == '__WXMSW__':
188 188 faces = { 'times': 'Times New Roman',
189 189 'mono' : 'Courier New',
190 190 'helv' : 'Arial',
191 191 'other': 'Comic Sans MS',
192 192 'size' : 10,
193 193 'size2': 8,
194 194 }
195 195 elif wx.Platform == '__WXMAC__':
196 196 faces = { 'times': 'Times New Roman',
197 197 'mono' : 'Monaco',
198 198 'helv' : 'Arial',
199 199 'other': 'Comic Sans MS',
200 200 'size' : 10,
201 201 'size2': 8,
202 202 }
203 203 else:
204 204 faces = { 'times': 'Times',
205 205 'mono' : 'Courier',
206 206 'helv' : 'Helvetica',
207 207 'other': 'new century schoolbook',
208 208 'size' : 10,
209 209 'size2': 8,
210 210 }
211 211
212 212 # make some styles
213 213 if self.background_color != "BLACK":
214 214 self.background_color = "WHITE"
215 215 self.SetCaretForeground("BLACK")
216 216 self.ANSI_STYLES = self.ANSI_STYLES_WHITE
217 217 else:
218 218 self.SetCaretForeground("WHITE")
219 219 self.ANSI_STYLES = self.ANSI_STYLES_BLACK
220 220
221 221 self.StyleSetSpec(stc.STC_STYLE_DEFAULT,
222 222 "fore:%s,back:%s,size:%d,face:%s"
223 223 % (self.ANSI_STYLES['0;30'][1],
224 224 self.background_color,
225 225 faces['size'], faces['mono']))
226 226 self.StyleClearAll()
227 227 self.StyleSetSpec(stc.STC_STYLE_BRACELIGHT,
228 228 "fore:#FF0000,back:#0000FF,bold")
229 229 self.StyleSetSpec(stc.STC_STYLE_BRACEBAD,
230 230 "fore:#000000,back:#FF0000,bold")
231 231
232 232 for style in self.ANSI_STYLES.values():
233 233 self.StyleSetSpec(style[0], "bold,fore:%s" % style[1])
234 234
235 235 #######################################################################
236 236
237 237 def setBackgroundColor(self, color):
238 238 self.background_color = color
239 239 self.buildStyles()
240 240
241 241 def getBackgroundColor(self, color):
242 242 return self.background_color
243 243
244 244 def asyncWrite(self, text):
245 245 '''
246 246 Write given text to buffer in an asynchroneous way.
247 247 It is used from another thread to be able to acces the GUI.
248 248 @param text: Text to append
249 249 @type text: string
250 250 '''
251 251 try:
252 #print >>sys.__stdout__,'entering'
253 252 wx.MutexGuiEnter()
254 #print >>sys.__stdout__,'locking the GUI'
255 253
256 254 #be sure not to be interrutpted before the MutexGuiLeave!
257 255 self.write(text)
258 256
259 #print >>sys.__stdout__,'done'
260
261 257 except KeyboardInterrupt:
262 #print >>sys.__stdout__,'got keyboard interrupt'
263 258 wx.MutexGuiLeave()
264 #print >>sys.__stdout__,'interrupt unlock the GUI'
265 259 raise KeyboardInterrupt
266 260 wx.MutexGuiLeave()
267 #print >>sys.__stdout__,'normal unlock the GUI'
268 261
269 262
270 263 def write(self, text):
271 264 '''
272 265 Write given text to buffer.
273 266
274 267 @param text: Text to append.
275 268 @type text: string
276 269 '''
277 270 segments = self.color_pat.split(text)
278 271 segment = segments.pop(0)
279 272 self.StartStyling(self.getCurrentLineEnd(), 0xFF)
280 273 self.AppendText(segment)
281 274
282 275 if segments:
283 276 ansi_tags = self.color_pat.findall(text)
284 277
285 278 for tag in ansi_tags:
286 279 i = segments.index(tag)
287 280 self.StartStyling(self.getCurrentLineEnd(), 0xFF)
288 281 self.AppendText(segments[i+1])
289 282
290 283 if tag != '0':
291 284 self.SetStyling(len(segments[i+1]), self.ANSI_STYLES[tag][0])
292 285
293 286 segments.pop(i)
294 287
295 288 self.moveCursor(self.getCurrentLineEnd())
296 289
297 290 def getPromptLen(self):
298 291 '''
299 292 Return the length of current prompt
300 293 '''
301 294 return len(str(self.prompt_count)) + 7
302 295
303 296 def setPrompt(self, prompt):
304 297 self.prompt = prompt
305 298
306 299 def setIndentation(self, indentation):
307 300 self.indent = indentation
308 301
309 302 def setPromptCount(self, count):
310 303 self.prompt_count = count
311 304
312 305 def showPrompt(self):
313 306 '''
314 307 Prints prompt at start of line.
315 308
316 309 @param prompt: Prompt to print.
317 310 @type prompt: string
318 311 '''
319 312 self.write(self.prompt)
320 313 #now we update the position of end of prompt
321 314 self.current_start = self.getCurrentLineEnd()
322 315
323 316 autoindent = self.indent*' '
324 317 autoindent = autoindent.replace(' ','\t')
325 318 self.write(autoindent)
326 319
327 320 def changeLine(self, text):
328 321 '''
329 322 Replace currently entered command line with given text.
330 323
331 324 @param text: Text to use as replacement.
332 325 @type text: string
333 326 '''
334 327 self.SetSelection(self.getCurrentPromptStart(), self.getCurrentLineEnd())
335 328 self.ReplaceSelection(text)
336 329 self.moveCursor(self.getCurrentLineEnd())
337 330
338 331 def getCurrentPromptStart(self):
339 332 return self.current_start
340 333
341 334 def getCurrentLineStart(self):
342 335 return self.GotoLine(self.LineFromPosition(self.GetCurrentPos()))
343 336
344 337 def getCurrentLineEnd(self):
345 338 return self.GetLength()
346 339
347 340 def getCurrentLine(self):
348 341 '''
349 342 Get text in current command line.
350 343
351 344 @return: Text of current command line.
352 345 @rtype: string
353 346 '''
354 347 return self.GetTextRange(self.getCurrentPromptStart(),
355 348 self.getCurrentLineEnd())
356 349
357 350 def moveCursorOnNewValidKey(self):
358 351 #If cursor is at wrong position put it at last line...
359 352 if self.GetCurrentPos() < self.getCurrentPromptStart():
360 353 self.GotoPos(self.getCurrentPromptStart())
361 354
362 355 def removeFromTo(self, from_pos, to_pos):
363 356 if from_pos < to_pos:
364 357 self.SetSelection(from_pos, to_pos)
365 358 self.DeleteBack()
366 359
367 360 def removeCurrentLine(self):
368 361 self.LineDelete()
369 362
370 363 def moveCursor(self, position):
371 364 self.GotoPos(position)
372 365
373 366 def getCursorPos(self):
374 367 return self.GetCurrentPos()
375 368
376 369 def selectFromTo(self, from_pos, to_pos):
377 370 self.SetSelectionStart(from_pos)
378 371 self.SetSelectionEnd(to_pos)
379 372
380 373 def writeHistory(self, history):
381 374 self.removeFromTo(self.getCurrentPromptStart(), self.getCurrentLineEnd())
382 375 self.changeLine(history)
383 376
384 377 def setCompletionMethod(self, completion):
385 378 if completion in ['IPYTHON', 'STC']:
386 379 self.autocomplete_mode = completion
387 380 else:
388 381 raise AttributeError
389 382
390 383 def getCompletionMethod(self, completion):
391 384 return self.autocomplete_mode
392 385
393 386 def writeCompletion(self, possibilities):
394 387 if self.autocomplete_mode == 'IPYTHON':
395 388 max_len = len(max(possibilities, key=len))
396 389 max_symbol = ' '*max_len
397 390
398 391 #now we check how much symbol we can put on a line...
399 392 test_buffer = max_symbol + ' '*4
400 393
401 394 allowed_symbols = 80/len(test_buffer)
402 395 if allowed_symbols == 0:
403 396 allowed_symbols = 1
404 397
405 398 pos = 1
406 399 buf = ''
407 400 for symbol in possibilities:
408 401 #buf += symbol+'\n'#*spaces)
409 402 if pos < allowed_symbols:
410 403 spaces = max_len - len(symbol) + 4
411 404 buf += symbol+' '*spaces
412 405 pos += 1
413 406 else:
414 407 buf += symbol+'\n'
415 408 pos = 1
416 409 self.write(buf)
417 410 else:
418 411 possibilities.sort() # Python sorts are case sensitive
419 412 self.AutoCompSetIgnoreCase(False)
420 413 self.AutoCompSetAutoHide(False)
421 414 #let compute the length ot last word
422 415 splitter = [' ', '(', '[', '{']
423 416 last_word = self.getCurrentLine()
424 417 for breaker in splitter:
425 418 last_word = last_word.split(breaker)[-1]
426 419 self.AutoCompShow(len(last_word), " ".join(possibilities))
427 420
428 421 def _onKeypress(self, event, skip=True):
429 422 '''
430 423 Key press callback used for correcting behavior for console-like
431 424 interfaces. For example 'home' should go to prompt, not to begining of
432 425 line.
433 426
434 427 @param widget: Widget that key press accored in.
435 428 @type widget: gtk.Widget
436 429 @param event: Event object
437 430 @type event: gtk.gdk.Event
438 431
439 432 @return: Return True if event as been catched.
440 433 @rtype: boolean
441 434 '''
442 435
443 436 if not self.AutoCompActive():
444 437 if event.GetKeyCode() == wx.WXK_HOME:
445 438 if event.Modifiers == wx.MOD_NONE:
446 439 self.moveCursorOnNewValidKey()
447 440 self.moveCursor(self.getCurrentPromptStart())
448 441 return True
449 442 elif event.Modifiers == wx.MOD_SHIFT:
450 443 self.moveCursorOnNewValidKey()
451 444 self.selectFromTo(self.getCurrentPromptStart(), self.getCursorPos())
452 445 return True
453 446 else:
454 447 return False
455 448
456 449 elif event.GetKeyCode() == wx.WXK_LEFT:
457 450 if event.Modifiers == wx.MOD_NONE:
458 451 self.moveCursorOnNewValidKey()
459 452
460 453 self.moveCursor(self.getCursorPos()-1)
461 454 if self.getCursorPos() < self.getCurrentPromptStart():
462 455 self.moveCursor(self.getCurrentPromptStart())
463 456 return True
464 457
465 458 elif event.GetKeyCode() == wx.WXK_BACK:
466 459 self.moveCursorOnNewValidKey()
467 460 if self.getCursorPos() > self.getCurrentPromptStart():
468 461 event.Skip()
469 462 return True
470 463
471 464 if skip:
472 465 if event.GetKeyCode() not in [wx.WXK_PAGEUP, wx.WXK_PAGEDOWN]\
473 466 and event.Modifiers == wx.MOD_NONE:
474 467 self.moveCursorOnNewValidKey()
475 468
476 469 event.Skip()
477 470 return True
478 471 return False
479 472 else:
480 473 event.Skip()
481 474
482 475 def OnUpdateUI(self, evt):
483 476 # check for matching braces
484 477 braceAtCaret = -1
485 478 braceOpposite = -1
486 479 charBefore = None
487 480 caretPos = self.GetCurrentPos()
488 481
489 482 if caretPos > 0:
490 483 charBefore = self.GetCharAt(caretPos - 1)
491 484 styleBefore = self.GetStyleAt(caretPos - 1)
492 485
493 486 # check before
494 487 if charBefore and chr(charBefore) in "[]{}()" and styleBefore == stc.STC_P_OPERATOR:
495 488 braceAtCaret = caretPos - 1
496 489
497 490 # check after
498 491 if braceAtCaret < 0:
499 492 charAfter = self.GetCharAt(caretPos)
500 493 styleAfter = self.GetStyleAt(caretPos)
501 494
502 495 if charAfter and chr(charAfter) in "[]{}()" and styleAfter == stc.STC_P_OPERATOR:
503 496 braceAtCaret = caretPos
504 497
505 498 if braceAtCaret >= 0:
506 499 braceOpposite = self.BraceMatch(braceAtCaret)
507 500
508 501 if braceAtCaret != -1 and braceOpposite == -1:
509 502 self.BraceBadLight(braceAtCaret)
510 503 else:
511 504 self.BraceHighlight(braceAtCaret, braceOpposite)
512 505 #pt = self.PointFromPosition(braceOpposite)
513 506 #self.Refresh(True, wxRect(pt.x, pt.y, 5,5))
514 507 #print pt
515 508 #self.Refresh(False)
516 509
517 510 class IPShellWidget(wx.Panel):
518 511 '''
519 512 This is wx.Panel that embbed the IPython Thread and the wx.StyledTextControl
520 513 If you want to port this to any other GUI toolkit, just replace the
521 514 WxConsoleView by YOURGUIConsoleView and make YOURGUIIPythonView derivate
522 515 from whatever container you want. I've choosed to derivate from a wx.Panel
523 516 because it seems to be more useful
524 517 Any idea to make it more 'generic' welcomed.
525 518 '''
526 519
527 520 def __init__(self, parent, intro=None,
528 521 background_color="BLACK", add_button_handler=None,
529 522 wx_ip_shell=None, user_ns={},user_global_ns=None,
530 523 ):
531 524 '''
532 525 Initialize.
533 526 Instanciate an IPython thread.
534 527 Instanciate a WxConsoleView.
535 528 Redirect I/O to console.
536 529 '''
537 530 wx.Panel.__init__(self,parent,wx.ID_ANY)
538 531
539 532 self.parent = parent
540 533 ### IPython non blocking shell instanciation ###
541 534 self.cout = StringIO()
542 535 self.add_button_handler = add_button_handler
543 536
544 537 if wx_ip_shell is not None:
545 538 self.IP = wx_ip_shell
546 539 else:
547 540 self.IP = WxNonBlockingIPShell(self,
548 541 cout = self.cout, cerr = self.cout,
549 542 ask_exit_handler = self.askExitCallback)
550 543
551 544 ### IPython wx console view instanciation ###
552 545 #If user didn't defined an intro text, we create one for him
553 546 #If you really wnat an empty intro just call wxIPythonViewPanel
554 547 #with intro=''
555 548 if intro is None:
556 549 welcome_text = "Welcome to WxIPython Shell.\n\n"
557 550 welcome_text+= self.IP.getBanner()
558 551 welcome_text+= "!command -> Execute command in shell\n"
559 552 welcome_text+= "TAB -> Autocompletion\n"
560 553 else:
561 554 welcome_text = intro
562 555
563 556 self.text_ctrl = WxConsoleView(self,
564 557 self.IP.getPrompt(),
565 558 intro=welcome_text,
566 559 background_color=background_color)
567 560
568 self.cout.write = self.text_ctrl.asyncWrite
569
570 561 option_text = wx.StaticText(self, -1, "Options:")
571 562 self.completion_option = wx.CheckBox(self, -1, "Scintilla Completion")
572 563 #self.completion_option.SetValue(False)
573 564 self.background_option = wx.CheckBox(self, -1, "White Background")
574 565 #self.background_option.SetValue(False)
566 self.threading_option = wx.CheckBox(self, -1, "Execute in thread")
567 #self.threading_option.SetValue(False)
575 568
576 569 self.options={'completion':{'value':'IPYTHON',
577 570 'checkbox':self.completion_option,'STC':True,'IPYTHON':False,
578 571 'setfunc':self.text_ctrl.setCompletionMethod},
579 572 'background_color':{'value':'BLACK',
580 573 'checkbox':self.background_option,'WHITE':True,'BLACK':False,
581 574 'setfunc':self.text_ctrl.setBackgroundColor},
575 'threading':{'value':'True',
576 'checkbox':self.threading_option,'True':True,'False':False,
577 'setfunc':self.IP.setThreading},
582 578 }
579
580 #self.cout.write dEfault option is asynchroneous because default sate is threading ON
581 self.cout.write = self.text_ctrl.asyncWrite
582 #we reloard options
583 583 self.reloadOptions(self.options)
584 584
585 585 self.text_ctrl.Bind(wx.EVT_KEY_DOWN, self.keyPress)
586 586 self.completion_option.Bind(wx.EVT_CHECKBOX, self.evtCheckOptionCompletion)
587 587 self.background_option.Bind(wx.EVT_CHECKBOX, self.evtCheckOptionBackgroundColor)
588 self.threading_option.Bind(wx.EVT_CHECKBOX, self.evtCheckOptionThreading)
588 589
589 590 ### making the layout of the panel ###
590 591 sizer = wx.BoxSizer(wx.VERTICAL)
591 592 sizer.Add(self.text_ctrl, 1, wx.EXPAND)
592 593 option_sizer = wx.BoxSizer(wx.HORIZONTAL)
593 594 sizer.Add(option_sizer, 0)
594 595 option_sizer.AddMany([(10, 20),
595 596 (option_text, 0, wx.ALIGN_CENTER_VERTICAL),
596 597 (5, 5),
597 598 (self.completion_option, 0, wx.ALIGN_CENTER_VERTICAL),
598 599 (8, 8),
599 (self.background_option, 0, wx.ALIGN_CENTER_VERTICAL)
600 (self.background_option, 0, wx.ALIGN_CENTER_VERTICAL),
601 (8, 8),
602 (self.threading_option, 0, wx.ALIGN_CENTER_VERTICAL)
600 603 ])
601 604 self.SetAutoLayout(True)
602 605 sizer.Fit(self)
603 606 sizer.SetSizeHints(self)
604 607 self.SetSizer(sizer)
605 608 #and we focus on the widget :)
606 609 self.SetFocus()
607 610
608 611 #widget state management (for key handling different cases)
609 612 self.setCurrentState('IDLE')
610 613 self.pager_state = 'DONE'
611 614 self.raw_input_current_line = 0
612 615
613 616 def askExitCallback(self, event):
614 617 self.askExitHandler(event)
615 618
616 619 #---------------------- IPython Thread Management ------------------------
617 620 def stateDoExecuteLine(self):
618 621 lines=self.text_ctrl.getCurrentLine()
619 622 self.text_ctrl.write('\n')
620 623 lines_to_execute = lines.replace('\t',' '*4)
621 624 lines_to_execute = lines_to_execute.replace('\r','')
622 625 self.IP.doExecute(lines_to_execute.encode(ENCODING))
623 626 self.updateHistoryTracker(lines)
624 627 self.setCurrentState('WAIT_END_OF_EXECUTION')
625 628
626 629 def evtStateExecuteDone(self,evt):
627 630 self.doc = self.IP.getDocText()
628 631 self.help = self.IP.getHelpText()
629 632 if self.doc:
630 633 self.pager_lines = self.doc[7:].split('\n')
631 634 self.pager_state = 'INIT'
632 635 self.setCurrentState('SHOW_DOC')
633 636 self.pager(self.doc)
634 637 elif self.help:
635 638 self.pager_lines = self.help.split('\n')
636 639 self.pager_state = 'INIT'
637 640 self.setCurrentState('SHOW_DOC')
638 641 self.pager(self.help)
639 642 else:
640 643 self.stateShowPrompt()
641 644
642 645 def stateShowPrompt(self):
643 646 self.setCurrentState('SHOW_PROMPT')
644 647 self.text_ctrl.setPrompt(self.IP.getPrompt())
645 648 self.text_ctrl.setIndentation(self.IP.getIndentation())
646 649 self.text_ctrl.setPromptCount(self.IP.getPromptCount())
647 650 self.text_ctrl.showPrompt()
648 651 self.IP.initHistoryIndex()
649 652 self.setCurrentState('IDLE')
650 653
651 654 def setCurrentState(self, state):
652 655 self.cur_state = state
653 656 self.updateStatusTracker(self.cur_state)
654 657
655 658 def pager(self,text):
656 659
657 660 if self.pager_state == 'INIT':
658 661 #print >>sys.__stdout__,"PAGER state:",self.pager_state
659 662 self.pager_nb_lines = len(self.pager_lines)
660 663 self.pager_index = 0
661 664 self.pager_do_remove = False
662 665 self.text_ctrl.write('\n')
663 666 self.pager_state = 'PROCESS_LINES'
664 667
665 668 if self.pager_state == 'PROCESS_LINES':
666 669 #print >>sys.__stdout__,"PAGER state:",self.pager_state
667 670 if self.pager_do_remove == True:
668 671 self.text_ctrl.removeCurrentLine()
669 672 self.pager_do_remove = False
670 673
671 674 if self.pager_nb_lines > 10:
672 675 #print >>sys.__stdout__,"PAGER processing 10 lines"
673 676 if self.pager_index > 0:
674 677 self.text_ctrl.write(">\x01\x1b[1;36m\x02"+self.pager_lines[self.pager_index]+'\n')
675 678 else:
676 679 self.text_ctrl.write("\x01\x1b[1;36m\x02 "+self.pager_lines[self.pager_index]+'\n')
677 680
678 681 for line in self.pager_lines[self.pager_index+1:self.pager_index+9]:
679 682 self.text_ctrl.write("\x01\x1b[1;36m\x02 "+line+'\n')
680 683 self.pager_index += 10
681 684 self.pager_nb_lines -= 10
682 685 self.text_ctrl.write("--- Push Enter to continue or 'Q' to quit---")
683 686 self.pager_do_remove = True
684 687 self.pager_state = 'WAITING'
685 688 return
686 689 else:
687 690 #print >>sys.__stdout__,"PAGER processing last lines"
688 691 if self.pager_nb_lines > 0:
689 692 if self.pager_index > 0:
690 693 self.text_ctrl.write(">\x01\x1b[1;36m\x02"+self.pager_lines[self.pager_index]+'\n')
691 694 else:
692 695 self.text_ctrl.write("\x01\x1b[1;36m\x02 "+self.pager_lines[self.pager_index]+'\n')
693 696
694 697 self.pager_index += 1
695 698 self.pager_nb_lines -= 1
696 699 if self.pager_nb_lines > 0:
697 700 for line in self.pager_lines[self.pager_index:]:
698 701 self.text_ctrl.write("\x01\x1b[1;36m\x02 "+line+'\n')
699 702 self.pager_nb_lines = 0
700 703 self.pager_state = 'DONE'
701 704 self.stateShowPrompt()
702 705
703 706 #------------------------ Key Handler ------------------------------------
704 707 def keyPress(self, event):
705 708 '''
706 709 Key press callback with plenty of shell goodness, like history,
707 710 autocompletions, etc.
708 711 '''
709 712 if event.GetKeyCode() == ord('C'):
710 713 if event.Modifiers == wx.MOD_CONTROL or event.Modifiers == wx.MOD_ALT:
711 714 if self.cur_state == 'WAIT_END_OF_EXECUTION':
712 715 #we raise an exception inside the IPython thread container
713 716 self.IP.ce.raise_exc(KeyboardInterrupt)
714 717 return
715 718
716 719 #let this before 'wx.WXK_RETURN' because we have to put 'IDLE'
717 720 #mode if AutoComp has been set as inactive
718 721 if self.cur_state == 'COMPLETING':
719 722 if not self.text_ctrl.AutoCompActive():
720 723 self.cur_state = 'IDLE'
721 724 else:
722 725 event.Skip()
723 726
724 727 if event.KeyCode == wx.WXK_RETURN:
725 728 if self.cur_state == 'IDLE':
726 729 #we change the state ot the state machine
727 730 self.setCurrentState('DO_EXECUTE_LINE')
728 731 self.stateDoExecuteLine()
729 732 return
730 733
731 734 if self.pager_state == 'WAITING':
732 735 self.pager_state = 'PROCESS_LINES'
733 736 self.pager(self.doc)
734 737 return
735 738
736 739 if self.cur_state == 'WAITING_USER_INPUT':
737 740 line=self.text_ctrl.getCurrentLine()
738 741 self.text_ctrl.write('\n')
739 742 self.setCurrentState('WAIT_END_OF_EXECUTION')
740 743 return
741 744
742 745 if event.GetKeyCode() in [ord('q'),ord('Q')]:
743 746 if self.pager_state == 'WAITING':
744 747 self.pager_state = 'DONE'
745 748 self.text_ctrl.write('\n')
746 749 self.stateShowPrompt()
747 750 return
748 751
749 752 if self.cur_state == 'WAITING_USER_INPUT':
750 753 event.Skip()
751 754
752 755 if self.cur_state == 'IDLE':
753 756 if event.KeyCode == wx.WXK_UP:
754 757 history = self.IP.historyBack()
755 758 self.text_ctrl.writeHistory(history)
756 759 return
757 760 if event.KeyCode == wx.WXK_DOWN:
758 761 history = self.IP.historyForward()
759 762 self.text_ctrl.writeHistory(history)
760 763 return
761 764 if event.KeyCode == wx.WXK_TAB:
762 765 #if line empty we disable tab completion
763 766 if not self.text_ctrl.getCurrentLine().strip():
764 767 self.text_ctrl.write('\t')
765 768 return
766 769 completed, possibilities = self.IP.complete(self.text_ctrl.getCurrentLine())
767 770 if len(possibilities) > 1:
768 771 if self.text_ctrl.autocomplete_mode == 'IPYTHON':
769 772 cur_slice = self.text_ctrl.getCurrentLine()
770 773 self.text_ctrl.write('\n')
771 774 self.text_ctrl.writeCompletion(possibilities)
772 775 self.text_ctrl.write('\n')
773 776
774 777 self.text_ctrl.showPrompt()
775 778 self.text_ctrl.write(cur_slice)
776 779 self.text_ctrl.changeLine(completed or cur_slice)
777 780 else:
778 781 self.cur_state = 'COMPLETING'
779 782 self.text_ctrl.writeCompletion(possibilities)
780 783 else:
781 784 self.text_ctrl.changeLine(completed or cur_slice)
782 785 return
783 786 event.Skip()
784 787
785 788 #------------------------ Option Section ---------------------------------
786 789 def evtCheckOptionCompletion(self, event):
787 790 if event.IsChecked():
788 791 self.options['completion']['value']='STC'
789 792 else:
790 793 self.options['completion']['value']='IPYTHON'
791 794 self.text_ctrl.setCompletionMethod(self.options['completion']['value'])
792 795 self.updateOptionTracker('completion',
793 796 self.options['completion']['value'])
794 797 self.text_ctrl.SetFocus()
795 798
796 799 def evtCheckOptionBackgroundColor(self, event):
797 800 if event.IsChecked():
798 801 self.options['background_color']['value']='WHITE'
799 802 else:
800 803 self.options['background_color']['value']='BLACK'
801 804 self.text_ctrl.setBackgroundColor(self.options['background_color']['value'])
802 805 self.updateOptionTracker('background_color',
803 806 self.options['background_color']['value'])
804 807 self.text_ctrl.SetFocus()
805
808
809 def evtCheckOptionThreading(self, event):
810 if event.IsChecked():
811 self.options['threading']['value']='True'
812 self.IP.setThreading(True)
813 self.cout.write = self.text_ctrl.asyncWrite
814 else:
815 self.options['threading']['value']='False'
816 self.IP.setThreading(False)
817 self.cout.write = self.text_ctrl.write
818 self.updateOptionTracker('threading',
819 self.options['threading']['value'])
820 self.text_ctrl.SetFocus()
821
806 822 def getOptions(self):
807 823 return self.options
808 824
809 825 def reloadOptions(self,options):
810 826 self.options = options
811 827 for key in self.options.keys():
812 828 value = self.options[key]['value']
813 829 self.options[key]['checkbox'].SetValue(self.options[key][value])
814 830 self.options[key]['setfunc'](value)
815 831
816
832 if self.options['threading']['value']=='True':
833 self.IP.setThreading(True)
834 self.cout.write = self.text_ctrl.asyncWrite
835 else:
836 self.IP.setThreading(False)
837 self.cout.write = self.text_ctrl.write
838
817 839 #------------------------ Hook Section -----------------------------------
818 840 def updateOptionTracker(self,name,value):
819 841 '''
820 842 Default history tracker (does nothing)
821 843 '''
822 844 pass
823 845
824 846 def setOptionTrackerHook(self,func):
825 847 '''
826 848 Define a new history tracker
827 849 '''
828 850 self.updateOptionTracker = func
829 851
830 852 def updateHistoryTracker(self,command_line):
831 853 '''
832 854 Default history tracker (does nothing)
833 855 '''
834 856 pass
835 857
836 858 def setHistoryTrackerHook(self,func):
837 859 '''
838 860 Define a new history tracker
839 861 '''
840 862 self.updateHistoryTracker = func
841 863
842 864 def updateStatusTracker(self,status):
843 865 '''
844 866 Default status tracker (does nothing)
845 867 '''
846 868 pass
847 869
848 870 def setStatusTrackerHook(self,func):
849 871 '''
850 872 Define a new status tracker
851 873 '''
852 874 self.updateStatusTracker = func
853 875
854 876 def askExitHandler(self, event):
855 877 '''
856 878 Default exit handler
857 879 '''
858 880 self.text_ctrl.write('\nExit callback has not been set.')
859 881
860 882 def setAskExitHandler(self, func):
861 883 '''
862 884 Define an exit handler
863 885 '''
864 886 self.askExitHandler = func
865 887
866 888 if __name__ == '__main__':
867 889 # Some simple code to test the shell widget.
868 890 class MainWindow(wx.Frame):
869 891 def __init__(self, parent, id, title):
870 892 wx.Frame.__init__(self, parent, id, title, size=(300,250))
871 893 self._sizer = wx.BoxSizer(wx.VERTICAL)
872 894 self.shell = IPShellWidget(self)
873 895 self._sizer.Add(self.shell, 1, wx.EXPAND)
874 896 self.SetSizer(self._sizer)
875 897 self.SetAutoLayout(1)
876 898 self.Show(True)
877 899
878 900 app = wx.PySimpleApp()
879 901 frame = MainWindow(None, wx.ID_ANY, 'Ipython')
880 902 frame.SetSize((780, 460))
881 903 shell = frame.shell
882 904
883 905 app.MainLoop()
884 906
885 907
@@ -1,252 +1,252 b''
1 1 #!/usr/bin/python
2 2 # -*- coding: iso-8859-15 -*-
3 3
4 4 import wx.aui
5 5 import sys
6 6 #used for about dialog
7 7 from wx.lib.wordwrap import wordwrap
8 8
9 9 #used for ipython GUI objects
10 10 from IPython.gui.wx.ipython_view import IPShellWidget
11 11 from IPython.gui.wx.ipython_history import IPythonHistoryPanel
12 12
13 13 #used to create options.conf file in user directory
14 14 from IPython.ipapi import get
15 15
16 __version__ = 0.8
16 __version__ = 0.9
17 17 __author__ = "Laurent Dufrechou"
18 18 __email__ = "laurent.dufrechou _at_ gmail.com"
19 19 __license__ = "BSD"
20 20
21 21 #-----------------------------------------
22 22 # Creating one main frame for our
23 23 # application with movables windows
24 24 #-----------------------------------------
25 25 class MyFrame(wx.Frame):
26 26 """Creating one main frame for our
27 27 application with movables windows"""
28 28 def __init__(self, parent=None, id=-1, title="WxIPython",
29 29 pos=wx.DefaultPosition,
30 30 size=(800, 600), style=wx.DEFAULT_FRAME_STYLE):
31 31 wx.Frame.__init__(self, parent, id, title, pos, size, style)
32 32 self._mgr = wx.aui.AuiManager()
33 33
34 34 # notify PyAUI which frame to use
35 35 self._mgr.SetManagedWindow(self)
36 36
37 37 #create differents panels and make them persistant
38 38 self.history_panel = IPythonHistoryPanel(self)
39 39
40 40 self.history_panel.setOptionTrackerHook(self.optionSave)
41 41
42 42 self.ipython_panel = IPShellWidget(self,background_color = "BLACK")
43 43 #self.ipython_panel = IPShellWidget(self,background_color = "WHITE")
44 44
45 45 self.ipython_panel.setHistoryTrackerHook(self.history_panel.write)
46 46 self.ipython_panel.setStatusTrackerHook(self.updateStatus)
47 47 self.ipython_panel.setAskExitHandler(self.OnExitDlg)
48 48 self.ipython_panel.setOptionTrackerHook(self.optionSave)
49 49
50 50 self.optionLoad()
51 51
52 52 self.statusbar = self.createStatus()
53 53 self.createMenu()
54 54
55 55 ########################################################################
56 56 ### add the panes to the manager
57 57 # main panels
58 58 self._mgr.AddPane(self.ipython_panel , wx.CENTER, "IPython Shell")
59 59 self._mgr.AddPane(self.history_panel , wx.RIGHT, "IPython history")
60 60
61 61 # now we specify some panel characteristics
62 62 self._mgr.GetPane(self.ipython_panel).CaptionVisible(True);
63 63 self._mgr.GetPane(self.history_panel).CaptionVisible(True);
64 64 self._mgr.GetPane(self.history_panel).MinSize((200,400));
65 65
66 66 # tell the manager to "commit" all the changes just made
67 67 self._mgr.Update()
68 68
69 69 #global event handling
70 70 self.Bind(wx.EVT_CLOSE, self.OnClose)
71 71 self.Bind(wx.EVT_MENU, self.OnClose,id=wx.ID_EXIT)
72 72 self.Bind(wx.EVT_MENU, self.OnShowIPythonPanel,id=wx.ID_HIGHEST+1)
73 73 self.Bind(wx.EVT_MENU, self.OnShowHistoryPanel,id=wx.ID_HIGHEST+2)
74 74 self.Bind(wx.EVT_MENU, self.OnShowAbout, id=wx.ID_HIGHEST+3)
75 75 self.Bind(wx.EVT_MENU, self.OnShowAllPanel,id=wx.ID_HIGHEST+6)
76 76
77 77 warn_text = 'Hello from IPython and wxPython.\n'
78 78 warn_text +='Please Note that this work is still EXPERIMENTAL\n'
79 79 warn_text +='It does NOT emulate currently all the IPython functions.\n'
80 80
81 81 dlg = wx.MessageDialog(self,
82 82 warn_text,
83 83 'Warning Box',
84 84 wx.OK | wx.ICON_INFORMATION
85 85 )
86 86 dlg.ShowModal()
87 87 dlg.Destroy()
88 88
89 89 def optionSave(self, name, value):
90 90 ip = get()
91 91 path = ip.IP.rc.ipythondir
92 92 opt = open(path + '/options.conf','w')
93 93
94 94 try:
95 95 options_ipython_panel = self.ipython_panel.getOptions()
96 96 options_history_panel = self.history_panel.getOptions()
97 97
98 98 for key in options_ipython_panel.keys():
99 99 opt.write(key + '=' + options_ipython_panel[key]['value']+'\n')
100 100 for key in options_history_panel.keys():
101 101 opt.write(key + '=' + options_history_panel[key]['value']+'\n')
102 102 finally:
103 103 opt.close()
104 104
105 105 def optionLoad(self):
106 106 try:
107 107 ip = get()
108 108 path = ip.IP.rc.ipythondir
109 109 opt = open(path + '/options.conf','r')
110 110 lines = opt.readlines()
111 111 opt.close()
112 112
113 113 options_ipython_panel = self.ipython_panel.getOptions()
114 114 options_history_panel = self.history_panel.getOptions()
115 115
116 116 for line in lines:
117 117 key = line.split('=')[0]
118 118 value = line.split('=')[1].replace('\n','').replace('\r','')
119 119 if key in options_ipython_panel.keys():
120 120 options_ipython_panel[key]['value'] = value
121 121 elif key in options_history_panel.keys():
122 122 options_history_panel[key]['value'] = value
123 123 else:
124 124 print >>sys.__stdout__,"Warning: key ",key,"not found in widget options. Check Options.conf"
125 125 self.ipython_panel.reloadOptions(options_ipython_panel)
126 126 self.history_panel.reloadOptions(options_history_panel)
127 127
128 128 except IOError:
129 129 print >>sys.__stdout__,"Could not open Options.conf, defaulting to default values."
130 130
131 131
132 132 def createMenu(self):
133 133 """local method used to create one menu bar"""
134 134
135 135 mb = wx.MenuBar()
136 136
137 137 file_menu = wx.Menu()
138 138 file_menu.Append(wx.ID_EXIT, "Exit")
139 139
140 140 view_menu = wx.Menu()
141 141 view_menu.Append(wx.ID_HIGHEST+1, "Show IPython Panel")
142 142 view_menu.Append(wx.ID_HIGHEST+2, "Show History Panel")
143 143 view_menu.AppendSeparator()
144 144 view_menu.Append(wx.ID_HIGHEST+6, "Show All")
145 145
146 146 about_menu = wx.Menu()
147 147 about_menu.Append(wx.ID_HIGHEST+3, "About")
148 148
149 149 #view_menu.AppendSeparator()
150 150 #options_menu = wx.Menu()
151 151 #options_menu.AppendCheckItem(wx.ID_HIGHEST+7, "Allow Floating")
152 152 #options_menu.AppendCheckItem(wx.ID_HIGHEST+8, "Transparent Hint")
153 153 #options_menu.AppendCheckItem(wx.ID_HIGHEST+9, "Transparent Hint Fade-in")
154 154
155 155
156 156 mb.Append(file_menu, "File")
157 157 mb.Append(view_menu, "View")
158 158 mb.Append(about_menu, "About")
159 159 #mb.Append(options_menu, "Options")
160 160
161 161 self.SetMenuBar(mb)
162 162
163 163 def createStatus(self):
164 164 statusbar = self.CreateStatusBar(2, wx.ST_SIZEGRIP)
165 165 statusbar.SetStatusWidths([-2, -3])
166 166 statusbar.SetStatusText("Ready", 0)
167 167 statusbar.SetStatusText("WxIPython "+str(__version__), 1)
168 168 return statusbar
169 169
170 170 def updateStatus(self,text):
171 171 states = {'IDLE':'Idle',
172 172 'DO_EXECUTE_LINE':'Send command',
173 173 'WAIT_END_OF_EXECUTION':'Running command',
174 174 'WAITING_USER_INPUT':'Waiting user input',
175 175 'SHOW_DOC':'Showing doc',
176 176 'SHOW_PROMPT':'Showing prompt'}
177 177 self.statusbar.SetStatusText(states[text], 0)
178 178
179 179 def OnClose(self, event):
180 180 """#event used to close program """
181 181 # deinitialize the frame manager
182 182 self._mgr.UnInit()
183 183 self.Destroy()
184 184 event.Skip()
185 185
186 186 def OnExitDlg(self, event):
187 187 dlg = wx.MessageDialog(self, 'Are you sure you want to quit WxIPython',
188 188 'WxIPython exit',
189 189 wx.ICON_QUESTION |
190 190 wx.YES_NO | wx.NO_DEFAULT
191 191 )
192 192 if dlg.ShowModal() == wx.ID_YES:
193 193 dlg.Destroy()
194 194 self._mgr.UnInit()
195 195 self.Destroy()
196 196 dlg.Destroy()
197 197
198 198 #event to display IPython pannel
199 199 def OnShowIPythonPanel(self,event):
200 200 """ #event to display Boxpannel """
201 201 self._mgr.GetPane(self.ipython_panel).Show(True)
202 202 self._mgr.Update()
203 203 #event to display History pannel
204 204 def OnShowHistoryPanel(self,event):
205 205 self._mgr.GetPane(self.history_panel).Show(True)
206 206 self._mgr.Update()
207 207
208 208 def OnShowAllPanel(self,event):
209 209 """#event to display all Pannels"""
210 210 self._mgr.GetPane(self.ipython_panel).Show(True)
211 211 self._mgr.GetPane(self.history_panel).Show(True)
212 212 self._mgr.Update()
213 213
214 214 def OnShowAbout(self, event):
215 215 # First we create and fill the info object
216 216 info = wx.AboutDialogInfo()
217 217 info.Name = "WxIPython"
218 218 info.Version = str(__version__)
219 219 info.Copyright = "(C) 2007 Laurent Dufrechou"
220 220 info.Description = wordwrap(
221 221 "A Gui that embbed a multithreaded IPython Shell",
222 222 350, wx.ClientDC(self))
223 223 info.WebSite = ("http://ipython.scipy.org/", "IPython home page")
224 224 info.Developers = [ "Laurent Dufrechou" ]
225 225 licenseText="BSD License.\nAll rights reserved. This program and the accompanying materials are made available under the terms of the BSD which accompanies this distribution, and is available at http://www.opensource.org/licenses/bsd-license.php"
226 226 info.License = wordwrap(licenseText, 500, wx.ClientDC(self))
227 227
228 228 # Then we call wx.AboutBox giving it that info object
229 229 wx.AboutBox(info)
230 230
231 231 #-----------------------------------------
232 232 #Creating our application
233 233 #-----------------------------------------
234 234 class MyApp(wx.PySimpleApp):
235 235 """Creating our application"""
236 236 def __init__(self):
237 237 wx.PySimpleApp.__init__(self)
238 238
239 239 self.frame = MyFrame()
240 240 self.frame.Show()
241 241
242 242 #-----------------------------------------
243 243 #Main loop
244 244 #-----------------------------------------
245 245 def main():
246 246 app = MyApp()
247 247 app.SetTopWindow(app.frame)
248 248 app.MainLoop()
249 249
250 250 #if launched as main program run this
251 251 if __name__ == '__main__':
252 252 main()
General Comments 0
You need to be logged in to leave comments. Login now