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