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