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