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