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