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