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