##// END OF EJS Templates
[merge] Merging with upstream trunk.
Fernando Perez -
r2097:6aff330d merge
parent child Browse files
Show More
@@ -1,340 +1,349 b''
1 1 """
2 2 IPython extension: autoreload modules before executing the next line
3 3
4 Try::
4 Try::
5 5
6 6 %autoreload?
7 7
8 8 for documentation.
9 9 """
10 10
11 11 # Pauli Virtanen <pav@iki.fi>, 2008.
12 12 # Thomas Heller, 2000.
13 13 #
14 14 # This IPython module is written by Pauli Virtanen, based on the autoreload
15 15 # code by Thomas Heller.
16 16
17 17 #------------------------------------------------------------------------------
18 18 # Autoreload functionality
19 19 #------------------------------------------------------------------------------
20 20
21 21 import time, os, threading, sys, types, imp, inspect, traceback, atexit
22 22 import weakref
23 23
24 24 def _get_compiled_ext():
25 25 """Official way to get the extension of compiled files (.pyc or .pyo)"""
26 26 for ext, mode, typ in imp.get_suffixes():
27 27 if typ == imp.PY_COMPILED:
28 28 return ext
29 29
30 30 PY_COMPILED_EXT = _get_compiled_ext()
31 31
32 32 class ModuleReloader(object):
33 33 failed = {}
34 34 """Modules that failed to reload: {module: mtime-on-failed-reload, ...}"""
35
35
36 36 modules = {}
37 37 """Modules specially marked as autoreloadable."""
38 38
39 39 skip_modules = {}
40 40 """Modules specially marked as not autoreloadable."""
41 41
42 42 check_all = True
43 43 """Autoreload all modules, not just those listed in 'modules'"""
44 44
45 45 old_objects = {}
46 46 """(module-name, name) -> weakref, for replacing old code objects"""
47
47
48 48 def check(self, check_all=False):
49 49 """Check whether some modules need to be reloaded."""
50
50
51 51 if check_all or self.check_all:
52 52 modules = sys.modules.keys()
53 53 else:
54 54 modules = self.modules.keys()
55
55
56 56 for modname in modules:
57 57 m = sys.modules.get(modname, None)
58 58
59 59 if modname in self.skip_modules:
60 60 continue
61
61
62 62 if not hasattr(m, '__file__'):
63 63 continue
64
64
65 65 if m.__name__ == '__main__':
66 66 # we cannot reload(__main__)
67 67 continue
68
68
69 69 filename = m.__file__
70 70 dirname = os.path.dirname(filename)
71 71 path, ext = os.path.splitext(filename)
72
72
73 73 if ext.lower() == '.py':
74 74 ext = PY_COMPILED_EXT
75 75 filename = os.path.join(dirname, path + PY_COMPILED_EXT)
76
76
77 77 if ext != PY_COMPILED_EXT:
78 78 continue
79
79
80 80 try:
81 81 pymtime = os.stat(filename[:-1]).st_mtime
82 82 if pymtime <= os.stat(filename).st_mtime:
83 83 continue
84 84 if self.failed.get(filename[:-1], None) == pymtime:
85 85 continue
86 86 except OSError:
87 87 continue
88
88
89 89 try:
90 90 superreload(m, reload, self.old_objects)
91 91 if filename[:-1] in self.failed:
92 92 del self.failed[filename[:-1]]
93 93 except:
94 94 print >> sys.stderr, "[autoreload of %s failed: %s]" % (
95 95 modname, traceback.format_exc(1))
96 96 self.failed[filename[:-1]] = pymtime
97 97
98 98 #------------------------------------------------------------------------------
99 99 # superreload
100 100 #------------------------------------------------------------------------------
101 101
102 102 def update_function(old, new):
103 103 """Upgrade the code object of a function"""
104 104 for name in ['func_code', 'func_defaults', 'func_doc',
105 105 'func_closure', 'func_globals', 'func_dict']:
106 106 try:
107 107 setattr(old, name, getattr(new, name))
108 108 except (AttributeError, TypeError):
109 109 pass
110 110
111 111 def update_class(old, new):
112 112 """Replace stuff in the __dict__ of a class, and upgrade
113 113 method code objects"""
114 114 for key in old.__dict__.keys():
115 115 old_obj = getattr(old, key)
116 116
117 117 try:
118 118 new_obj = getattr(new, key)
119 119 except AttributeError:
120 120 # obsolete attribute: remove it
121 try:
121 try:
122 122 delattr(old, key)
123 123 except (AttributeError, TypeError):
124 124 pass
125 125 continue
126
126
127 127 if update_generic(old_obj, new_obj): continue
128 128
129 129 try:
130 130 setattr(old, key, getattr(new, key))
131 131 except (AttributeError, TypeError):
132 132 pass # skip non-writable attributes
133 133
134 134 def update_property(old, new):
135 135 """Replace get/set/del functions of a property"""
136 136 update_generic(old.fdel, new.fdel)
137 137 update_generic(old.fget, new.fget)
138 138 update_generic(old.fset, new.fset)
139 139
140 140 def isinstance2(a, b, typ):
141 141 return isinstance(a, typ) and isinstance(b, typ)
142 142
143 143 UPDATE_RULES = [
144 144 (lambda a, b: isinstance2(a, b, types.ClassType),
145 145 update_class),
146 146 (lambda a, b: isinstance2(a, b, types.TypeType),
147 147 update_class),
148 148 (lambda a, b: isinstance2(a, b, types.FunctionType),
149 update_function),
149 update_function),
150 150 (lambda a, b: isinstance2(a, b, property),
151 update_property),
151 update_property),
152 152 (lambda a, b: isinstance2(a, b, types.MethodType),
153 153 lambda a, b: update_function(a.im_func, b.im_func)),
154 154 ]
155 155
156 156 def update_generic(a, b):
157 157 for type_check, update in UPDATE_RULES:
158 158 if type_check(a, b):
159 159 update(a, b)
160 160 return True
161 161 return False
162 162
163 163 class StrongRef(object):
164 164 def __init__(self, obj):
165 165 self.obj = obj
166 166 def __call__(self):
167 167 return self.obj
168 168
169 169 def superreload(module, reload=reload, old_objects={}):
170 170 """Enhanced version of the builtin reload function.
171
171
172 172 superreload remembers objects previously in the module, and
173 173
174 174 - upgrades the class dictionary of every old class in the module
175 175 - upgrades the code object of every old function and method
176 176 - clears the module's namespace before reloading
177
177
178 178 """
179
179
180 180 # collect old objects in the module
181 181 for name, obj in module.__dict__.items():
182 182 if not hasattr(obj, '__module__') or obj.__module__ != module.__name__:
183 183 continue
184 184 key = (module.__name__, name)
185 185 try:
186 186 old_objects.setdefault(key, []).append(weakref.ref(obj))
187 187 except TypeError:
188 188 # weakref doesn't work for all types;
189 189 # create strong references for 'important' cases
190 190 if isinstance(obj, types.ClassType):
191 191 old_objects.setdefault(key, []).append(StrongRef(obj))
192 192
193 193 # reload module
194 194 try:
195 195 # clear namespace first from old cruft
196 196 old_name = module.__name__
197 197 module.__dict__.clear()
198 198 module.__dict__['__name__'] = old_name
199 199 except (TypeError, AttributeError, KeyError):
200 200 pass
201 201 module = reload(module)
202
202
203 203 # iterate over all objects and update functions & classes
204 204 for name, new_obj in module.__dict__.items():
205 205 key = (module.__name__, name)
206 206 if key not in old_objects: continue
207 207
208 208 new_refs = []
209 209 for old_ref in old_objects[key]:
210 210 old_obj = old_ref()
211 211 if old_obj is None: continue
212 212 new_refs.append(old_ref)
213 213 update_generic(old_obj, new_obj)
214 214
215 215 if new_refs:
216 216 old_objects[key] = new_refs
217 217 else:
218 218 del old_objects[key]
219 219
220 220 return module
221 221
222 222 reloader = ModuleReloader()
223 223
224 224 #------------------------------------------------------------------------------
225 225 # IPython connectivity
226 226 #------------------------------------------------------------------------------
227 227 import IPython.ipapi
228 228
229 229 ip = IPython.ipapi.get()
230 230
231 231 autoreload_enabled = False
232 232
233 233 def runcode_hook(self):
234 234 if not autoreload_enabled:
235 235 raise IPython.ipapi.TryNext
236 236 try:
237 237 reloader.check()
238 238 except:
239 239 pass
240 240
241 241 def enable_autoreload():
242 242 global autoreload_enabled
243 243 autoreload_enabled = True
244 244
245 245 def disable_autoreload():
246 246 global autoreload_enabled
247 247 autoreload_enabled = False
248 248
249 249 def autoreload_f(self, parameter_s=''):
250 250 r""" %autoreload => Reload modules automatically
251
251
252 252 %autoreload
253 253 Reload all modules (except those excluded by %aimport) automatically now.
254
254
255 %autoreload 0
256 Disable automatic reloading.
257
255 258 %autoreload 1
256 259 Reload all modules imported with %aimport every time before executing
257 260 the Python code typed.
258
261
259 262 %autoreload 2
260 263 Reload all modules (except those excluded by %aimport) every time
261 264 before executing the Python code typed.
262
263 Reloading Python modules in a reliable way is in general difficult,
264 and unexpected things may occur. %autoreload tries to work
265 around common pitfalls by replacing code objects of functions
266 previously in the module with new versions. This makes the following
267 things to work:
265
266 Reloading Python modules in a reliable way is in general
267 difficult, and unexpected things may occur. %autoreload tries to
268 work around common pitfalls by replacing function code objects and
269 parts of classes previously in the module with new versions. This
270 makes the following things to work:
268 271
269 272 - Functions and classes imported via 'from xxx import foo' are upgraded
270 273 to new versions when 'xxx' is reloaded.
274
271 275 - Methods and properties of classes are upgraded on reload, so that
272 276 calling 'c.foo()' on an object 'c' created before the reload causes
273 277 the new code for 'foo' to be executed.
274
278
275 279 Some of the known remaining caveats are:
276
280
277 281 - Replacing code objects does not always succeed: changing a @property
278 282 in a class to an ordinary method or a method to a member variable
279 283 can cause problems (but in old objects only).
284
280 285 - Functions that are removed (eg. via monkey-patching) from a module
281 286 before it is reloaded are not upgraded.
287
282 288 - C extension modules cannot be reloaded, and so cannot be
283 289 autoreloaded.
284
290
285 291 """
286 292 if parameter_s == '':
287 293 reloader.check(True)
288 294 elif parameter_s == '0':
289 295 disable_autoreload()
290 296 elif parameter_s == '1':
291 297 reloader.check_all = False
292 298 enable_autoreload()
293 299 elif parameter_s == '2':
294 300 reloader.check_all = True
295 301 enable_autoreload()
296 302
297 303 def aimport_f(self, parameter_s=''):
298 304 """%aimport => Import modules for automatic reloading.
299 305
300 306 %aimport
301 307 List modules to automatically import and not to import.
302 308
303 309 %aimport foo
304 310 Import module 'foo' and mark it to be autoreloaded for %autoreload 1
305 311
306 312 %aimport -foo
307 313 Mark module 'foo' to not be autoreloaded for %autoreload 1
308 314
309 315 """
310
316
311 317 modname = parameter_s
312 318 if not modname:
313 319 to_reload = reloader.modules.keys()
314 320 to_reload.sort()
315 321 to_skip = reloader.skip_modules.keys()
316 322 to_skip.sort()
317 323 if reloader.check_all:
318 324 print "Modules to reload:\nall-expect-skipped"
319 325 else:
320 326 print "Modules to reload:\n%s" % ' '.join(to_reload)
321 327 print "\nModules to skip:\n%s" % ' '.join(to_skip)
322 328 elif modname.startswith('-'):
323 329 modname = modname[1:]
324 330 try: del reloader.modules[modname]
325 331 except KeyError: pass
326 332 reloader.skip_modules[modname] = True
327 333 else:
328 334 try: del reloader.skip_modules[modname]
329 335 except KeyError: pass
330 336 reloader.modules[modname] = True
331 337
332 mod = __import__(modname)
333 ip.to_user_ns({modname: mod})
338 # Inject module to user namespace; handle also submodules properly
339 __import__(modname)
340 basename = modname.split('.')[0]
341 mod = sys.modules[basename]
342 ip.to_user_ns({basename: mod})
334 343
335 344 def init():
336 345 ip.expose_magic('autoreload', autoreload_f)
337 346 ip.expose_magic('aimport', aimport_f)
338 347 ip.set_hook('pre_runcode_hook', runcode_hook)
339
348
340 349 init()
@@ -1,625 +1,624 b''
1 1 # encoding: utf-8
2 2 """
3 3 A Wx widget to act as a console and input commands.
4 4
5 5 This widget deals with prompts and provides an edit buffer
6 6 restricted to after the last prompt.
7 7 """
8 8
9 9 __docformat__ = "restructuredtext en"
10 10
11 11 #-------------------------------------------------------------------------------
12 12 # Copyright (C) 2008 The IPython Development Team
13 13 #
14 14 # Distributed under the terms of the BSD License. The full license is
15 15 # in the file COPYING, distributed as part of this software.
16 16 #-------------------------------------------------------------------------------
17 17
18 18 #-------------------------------------------------------------------------------
19 19 # Imports
20 20 #-------------------------------------------------------------------------------
21 21
22 22 import wx
23 23 import wx.stc as stc
24 24
25 25 from wx.py import editwindow
26 26 import time
27 27 import sys
28 28 import string
29 29
30 30 LINESEP = '\n'
31 31 if sys.platform == 'win32':
32 32 LINESEP = '\n\r'
33 33
34 34 import re
35 35
36 36 # FIXME: Need to provide an API for non user-generated display on the
37 37 # screen: this should not be editable by the user.
38 38 #-------------------------------------------------------------------------------
39 39 # Constants
40 40 #-------------------------------------------------------------------------------
41 41 _COMPLETE_BUFFER_MARKER = 31
42 42 _ERROR_MARKER = 30
43 43 _INPUT_MARKER = 29
44 44
45 45 _DEFAULT_SIZE = 10
46 46 if sys.platform == 'darwin':
47 47 _DEFAULT_SIZE = 12
48 48
49 49 _DEFAULT_STYLE = {
50 50 #background definition
51 51 'default' : 'size:%d' % _DEFAULT_SIZE,
52 52 'bracegood' : 'fore:#00AA00,back:#000000,bold',
53 53 'bracebad' : 'fore:#FF0000,back:#000000,bold',
54 54
55 55 # Edge column: a number of None
56 56 'edge_column' : -1,
57 57
58 58 # properties for the various Python lexer styles
59 59 'comment' : 'fore:#007F00',
60 60 'number' : 'fore:#007F7F',
61 61 'string' : 'fore:#7F007F,italic',
62 62 'char' : 'fore:#7F007F,italic',
63 63 'keyword' : 'fore:#00007F,bold',
64 64 'triple' : 'fore:#7F0000',
65 65 'tripledouble' : 'fore:#7F0000',
66 66 'class' : 'fore:#0000FF,bold,underline',
67 67 'def' : 'fore:#007F7F,bold',
68 68 'operator' : 'bold',
69 69
70 70 # Default colors
71 71 'trace' : '#FAFAF1', # Nice green
72 72 'stdout' : '#FDFFD3', # Nice yellow
73 73 'stderr' : '#FFF1F1', # Nice red
74 74
75 75 # Default scintilla settings
76 76 'antialiasing' : True,
77 77 'carret_color' : 'BLACK',
78 78 'background_color' :'WHITE',
79 79
80 80 #prompt definition
81 81 'prompt_in1' : \
82 82 '\n\x01\x1b[0;34m\x02In [\x01\x1b[1;34m\x02$number\x01\x1b[0;34m\x02]: \x01\x1b[0m\x02',
83 83
84 84 'prompt_out': \
85 85 '\x01\x1b[0;31m\x02Out[\x01\x1b[1;31m\x02$number\x01\x1b[0;31m\x02]: \x01\x1b[0m\x02',
86 86 }
87 87
88 88 # new style numbers
89 89 _STDOUT_STYLE = 15
90 90 _STDERR_STYLE = 16
91 91 _TRACE_STYLE = 17
92 92
93 93
94 94 # system colors
95 95 #SYS_COLOUR_BACKGROUND = wx.SystemSettings.GetColour(wx.SYS_COLOUR_BACKGROUND)
96 96
97 97 # Translation table from ANSI escape sequences to color.
98 98 ANSI_STYLES = {'0;30': [0, 'BLACK'], '0;31': [1, 'RED'],
99 99 '0;32': [2, 'GREEN'], '0;33': [3, 'BROWN'],
100 100 '0;34': [4, 'BLUE'], '0;35': [5, 'PURPLE'],
101 101 '0;36': [6, 'CYAN'], '0;37': [7, 'LIGHT GREY'],
102 102 '1;30': [8, 'DARK GREY'], '1;31': [9, 'RED'],
103 103 '1;32': [10, 'SEA GREEN'], '1;33': [11, 'YELLOW'],
104 104 '1;34': [12, 'LIGHT BLUE'], '1;35':
105 105 [13, 'MEDIUM VIOLET RED'],
106 106 '1;36': [14, 'LIGHT STEEL BLUE'], '1;37': [15, 'YELLOW']}
107 107
108 108 # XXX: Maybe one day we should factor this code with ColorANSI. Right now
109 109 # ColorANSI is hard to reuse and makes our code more complex.
110 110
111 111 #we define platform specific fonts
112 112 if wx.Platform == '__WXMSW__':
113 113 FACES = { 'times': 'Times New Roman',
114 114 'mono' : 'Courier New',
115 115 'helv' : 'Arial',
116 116 'other': 'Comic Sans MS',
117 117 'size' : 10,
118 118 'size2': 8,
119 119 }
120 120 elif wx.Platform == '__WXMAC__':
121 121 FACES = { 'times': 'Times New Roman',
122 122 'mono' : 'Monaco',
123 123 'helv' : 'Arial',
124 124 'other': 'Comic Sans MS',
125 125 'size' : 10,
126 126 'size2': 8,
127 127 }
128 128 else:
129 129 FACES = { 'times': 'Times',
130 130 'mono' : 'Courier',
131 131 'helv' : 'Helvetica',
132 132 'other': 'new century schoolbook',
133 133 'size' : 10,
134 134 'size2': 8,
135 135 }
136 136
137 137
138 138 #-------------------------------------------------------------------------------
139 139 # The console widget class
140 140 #-------------------------------------------------------------------------------
141 141 class ConsoleWidget(editwindow.EditWindow):
142 142 """ Specialized styled text control view for console-like workflow.
143 143
144 144 This widget is mainly interested in dealing with the prompt and
145 145 keeping the cursor inside the editing line.
146 146 """
147 147
148 148 # This is where the title captured from the ANSI escape sequences are
149 149 # stored.
150 150 title = 'Console'
151 151
152 152 # Last prompt printed
153 153 last_prompt = ''
154 154
155 155 # The buffer being edited.
156 156 def _set_input_buffer(self, string):
157 157 self.SetSelection(self.current_prompt_pos, self.GetLength())
158 158 self.ReplaceSelection(string)
159 159 self.GotoPos(self.GetLength())
160 160
161 161 def _get_input_buffer(self):
162 162 """ Returns the text in current edit buffer.
163 163 """
164 164 input_buffer = self.GetTextRange(self.current_prompt_pos,
165 165 self.GetLength())
166 166 input_buffer = input_buffer.replace(LINESEP, '\n')
167 167 return input_buffer
168 168
169 169 input_buffer = property(_get_input_buffer, _set_input_buffer)
170 170
171 171 style = _DEFAULT_STYLE.copy()
172 172
173 173 # Translation table from ANSI escape sequences to color. Override
174 174 # this to specify your colors.
175 175 ANSI_STYLES = ANSI_STYLES.copy()
176 176
177 177 # Font faces
178 178 faces = FACES.copy()
179 179
180 180 # Store the last time a refresh was done
181 181 _last_refresh_time = 0
182 182
183 183 #--------------------------------------------------------------------------
184 184 # Public API
185 185 #--------------------------------------------------------------------------
186 186
187 187 def __init__(self, parent, id=wx.ID_ANY, pos=wx.DefaultPosition,
188 188 size=wx.DefaultSize, style=wx.WANTS_CHARS, ):
189 189 editwindow.EditWindow.__init__(self, parent, id, pos, size, style)
190 190 self.configure_scintilla()
191 191 # Track if 'enter' key as ever been processed
192 192 # This variable will only be reallowed until key goes up
193 193 self.enter_catched = False
194 194 self.current_prompt_pos = 0
195 195
196 196 self.Bind(wx.EVT_KEY_DOWN, self._on_key_down)
197 197 self.Bind(wx.EVT_KEY_UP, self._on_key_up)
198 198
199 199
200 200 def write(self, text, refresh=True):
201 201 """ Write given text to buffer, while translating the ansi escape
202 202 sequences.
203 203 """
204 204 # XXX: do not put print statements to sys.stdout/sys.stderr in
205 205 # this method, the print statements will call this method, as
206 206 # you will end up with an infinit loop
207 207 title = self.title_pat.split(text)
208 208 if len(title)>1:
209 209 self.title = title[-2]
210 210
211 211 text = self.title_pat.sub('', text)
212 212 segments = self.color_pat.split(text)
213 213 segment = segments.pop(0)
214 214 self.GotoPos(self.GetLength())
215 215 self.StartStyling(self.GetLength(), 0xFF)
216 216 try:
217 217 self.AppendText(segment)
218 218 except UnicodeDecodeError:
219 219 # XXX: Do I really want to skip the exception?
220 220 pass
221 221
222 222 if segments:
223 223 for ansi_tag, text in zip(segments[::2], segments[1::2]):
224 224 self.StartStyling(self.GetLength(), 0xFF)
225 225 try:
226 226 self.AppendText(text)
227 227 except UnicodeDecodeError:
228 228 # XXX: Do I really want to skip the exception?
229 229 pass
230 230
231 231 if ansi_tag not in self.ANSI_STYLES:
232 232 style = 0
233 233 else:
234 234 style = self.ANSI_STYLES[ansi_tag][0]
235 235
236 236 self.SetStyling(len(text), style)
237 237
238 238 self.GotoPos(self.GetLength())
239 239 if refresh:
240 240 current_time = time.time()
241 241 if current_time - self._last_refresh_time > 0.03:
242 242 if sys.platform == 'win32':
243 243 wx.SafeYield()
244 244 else:
245 245 wx.Yield()
246 246 # self.ProcessEvent(wx.PaintEvent())
247 247 self._last_refresh_time = current_time
248 248
249 249
250 250 def new_prompt(self, prompt):
251 251 """ Prints a prompt at start of line, and move the start of the
252 252 current block there.
253 253
254 254 The prompt can be given with ascii escape sequences.
255 255 """
256 256 self.write(prompt, refresh=False)
257 257 # now we update our cursor giving end of prompt
258 258 self.current_prompt_pos = self.GetLength()
259 259 self.current_prompt_line = self.GetCurrentLine()
260 260 self.EnsureCaretVisible()
261 261 self.last_prompt = prompt
262 262
263 263
264 264 def continuation_prompt(self):
265 265 """ Returns the current continuation prompt.
266 266 We need to implement this method here to deal with the
267 267 ascii escape sequences cleaning up.
268 268 """
269 269 # ASCII-less prompt
270 270 ascii_less = ''.join(self.color_pat.split(self.last_prompt)[2::2])
271 271 return "."*(len(ascii_less)-2) + ': '
272 272
273 273
274 274 def scroll_to_bottom(self):
275 275 maxrange = self.GetScrollRange(wx.VERTICAL)
276 276 self.ScrollLines(maxrange)
277 277
278 278
279 279 def pop_completion(self, possibilities, offset=0):
280 280 """ Pops up an autocompletion menu. Offset is the offset
281 281 in characters of the position at which the menu should
282 282 appear, relativ to the cursor.
283 283 """
284 284 self.AutoCompSetIgnoreCase(False)
285 285 self.AutoCompSetAutoHide(False)
286 286 self.AutoCompSetMaxHeight(len(possibilities))
287 287 self.AutoCompShow(offset, " ".join(possibilities))
288 288
289 289
290 290 def get_line_width(self):
291 291 """ Return the width of the line in characters.
292 292 """
293 293 return self.GetSize()[0]/self.GetCharWidth()
294 294
295 295
296 296 def configure_scintilla(self):
297 297 """ Set up all the styling option of the embedded scintilla
298 298 widget.
299 299 """
300 300 p = self.style.copy()
301 301
302 302 # Marker for complete buffer.
303 303 self.MarkerDefine(_COMPLETE_BUFFER_MARKER, stc.STC_MARK_BACKGROUND,
304 304 background=p['trace'])
305 305
306 306 # Marker for current input buffer.
307 307 self.MarkerDefine(_INPUT_MARKER, stc.STC_MARK_BACKGROUND,
308 308 background=p['stdout'])
309 309 # Marker for tracebacks.
310 310 self.MarkerDefine(_ERROR_MARKER, stc.STC_MARK_BACKGROUND,
311 311 background=p['stderr'])
312 312
313 313 self.SetEOLMode(stc.STC_EOL_LF)
314 314
315 315 # Ctrl"+" or Ctrl "-" can be used to zoomin/zoomout the text inside
316 316 # the widget
317 317 self.CmdKeyAssign(ord('+'), stc.STC_SCMOD_CTRL, stc.STC_CMD_ZOOMIN)
318 318 self.CmdKeyAssign(ord('-'), stc.STC_SCMOD_CTRL, stc.STC_CMD_ZOOMOUT)
319 319 # Also allow Ctrl Shift "=" for poor non US keyboard users.
320 320 self.CmdKeyAssign(ord('='), stc.STC_SCMOD_CTRL|stc.STC_SCMOD_SHIFT,
321 321 stc.STC_CMD_ZOOMIN)
322 322
323 323 # Keys: we need to clear some of the keys the that don't play
324 324 # well with a console.
325 325 self.CmdKeyClear(ord('D'), stc.STC_SCMOD_CTRL)
326 326 self.CmdKeyClear(ord('L'), stc.STC_SCMOD_CTRL)
327 327 self.CmdKeyClear(ord('T'), stc.STC_SCMOD_CTRL)
328 328 self.CmdKeyClear(ord('A'), stc.STC_SCMOD_CTRL)
329 329
330 330 self.SetEOLMode(stc.STC_EOL_CRLF)
331 331 self.SetWrapMode(stc.STC_WRAP_CHAR)
332 332 self.SetWrapMode(stc.STC_WRAP_WORD)
333 333 self.SetBufferedDraw(True)
334 334
335 335 self.SetUseAntiAliasing(p['antialiasing'])
336 336
337 337 self.SetLayoutCache(stc.STC_CACHE_PAGE)
338 338 self.SetUndoCollection(False)
339 339 self.SetUseTabs(True)
340 340 self.SetIndent(4)
341 341 self.SetTabWidth(4)
342 342
343 343 # we don't want scintilla's autocompletion to choose
344 344 # automaticaly out of a single choice list, as we pop it up
345 345 # automaticaly
346 346 self.AutoCompSetChooseSingle(False)
347 347 self.AutoCompSetMaxHeight(10)
348 348 # XXX: this doesn't seem to have an effect.
349 349 self.AutoCompSetFillUps('\n')
350 350
351 351 self.SetMargins(3, 3) #text is moved away from border with 3px
352 352 # Suppressing Scintilla margins
353 353 self.SetMarginWidth(0, 0)
354 354 self.SetMarginWidth(1, 0)
355 355 self.SetMarginWidth(2, 0)
356 356
357 357 # Xterm escape sequences
358 358 self.color_pat = re.compile('\x01?\x1b\[(.*?)m\x02?')
359 359 self.title_pat = re.compile('\x1b]0;(.*?)\x07')
360 360
361 361 # styles
362 362
363 363 self.SetCaretForeground(p['carret_color'])
364 364
365 365 background_color = p['background_color']
366 366
367 367 if 'default' in p:
368 368 if 'back' not in p['default']:
369 369 p['default'] += ',back:%s' % background_color
370 370 if 'size' not in p['default']:
371 371 p['default'] += ',size:%s' % self.faces['size']
372 372 if 'face' not in p['default']:
373 373 p['default'] += ',face:%s' % self.faces['mono']
374 374
375 375 self.StyleSetSpec(stc.STC_STYLE_DEFAULT, p['default'])
376 376 else:
377 377 self.StyleSetSpec(stc.STC_STYLE_DEFAULT,
378 378 "fore:%s,back:%s,size:%d,face:%s"
379 379 % (self.ANSI_STYLES['0;30'][1],
380 380 background_color,
381 381 self.faces['size'], self.faces['mono']))
382 382
383 383 self.StyleClearAll()
384 384
385 385 # XXX: two lines below are usefull if not using the lexer
386 386 #for style in self.ANSI_STYLES.values():
387 387 # self.StyleSetSpec(style[0], "bold,fore:%s" % style[1])
388 388
389 389 # prompt definition
390 390 self.prompt_in1 = p['prompt_in1']
391 391 self.prompt_out = p['prompt_out']
392 392
393 393 self.output_prompt_template = string.Template(self.prompt_out)
394 394 self.input_prompt_template = string.Template(self.prompt_in1)
395 395
396 396 self.StyleSetSpec(_STDOUT_STYLE, p['stdout'])
397 397 self.StyleSetSpec(_STDERR_STYLE, p['stderr'])
398 398 self.StyleSetSpec(_TRACE_STYLE, p['trace'])
399 399 self.StyleSetSpec(stc.STC_STYLE_BRACELIGHT, p['bracegood'])
400 400 self.StyleSetSpec(stc.STC_STYLE_BRACEBAD, p['bracebad'])
401 401 self.StyleSetSpec(stc.STC_P_COMMENTLINE, p['comment'])
402 402 self.StyleSetSpec(stc.STC_P_NUMBER, p['number'])
403 403 self.StyleSetSpec(stc.STC_P_STRING, p['string'])
404 404 self.StyleSetSpec(stc.STC_P_CHARACTER, p['char'])
405 405 self.StyleSetSpec(stc.STC_P_WORD, p['keyword'])
406 406 self.StyleSetSpec(stc.STC_P_WORD2, p['keyword'])
407 407 self.StyleSetSpec(stc.STC_P_TRIPLE, p['triple'])
408 408 self.StyleSetSpec(stc.STC_P_TRIPLEDOUBLE, p['tripledouble'])
409 409 self.StyleSetSpec(stc.STC_P_CLASSNAME, p['class'])
410 410 self.StyleSetSpec(stc.STC_P_DEFNAME, p['def'])
411 411 self.StyleSetSpec(stc.STC_P_OPERATOR, p['operator'])
412 412 self.StyleSetSpec(stc.STC_P_COMMENTBLOCK, p['comment'])
413 413
414 414 edge_column = p['edge_column']
415 415 if edge_column is not None and edge_column > 0:
416 416 #we add a vertical line to console widget
417 417 self.SetEdgeMode(stc.STC_EDGE_LINE)
418 418 self.SetEdgeColumn(edge_column)
419 419
420 420
421 421 #--------------------------------------------------------------------------
422 422 # EditWindow API
423 423 #--------------------------------------------------------------------------
424 424
425 425 def OnUpdateUI(self, event):
426 426 """ Override the OnUpdateUI of the EditWindow class, to prevent
427 427 syntax highlighting both for faster redraw, and for more
428 428 consistent look and feel.
429 429 """
430 430
431 431
432 432 #--------------------------------------------------------------------------
433 433 # Private API
434 434 #--------------------------------------------------------------------------
435 435
436 436 def _on_key_down(self, event, skip=True):
437 437 """ Key press callback used for correcting behavior for
438 438 console-like interfaces: the cursor is constraint to be after
439 439 the last prompt.
440 440
441 441 Return True if event as been catched.
442 442 """
443 443 catched = True
444 444 # XXX: Would the right way to do this be to have a
445 445 # dictionary at the instance level associating keys with
446 446 # callbacks? How would we deal with inheritance? And Do the
447 447 # different callbacks share local variables?
448 448
449 449 # Intercept some specific keys.
450 if event.KeyCode == ord('L') and event.ControlDown() :
450 key_code = event.GetKeyCode()
451 if key_code == ord('L') and event.ControlDown() :
451 452 self.scroll_to_bottom()
452 elif event.KeyCode == ord('K') and event.ControlDown() :
453 elif key_code == ord('K') and event.ControlDown() :
453 454 self.input_buffer = ''
454 elif event.KeyCode == ord('A') and event.ControlDown() :
455 elif key_code == ord('A') and event.ControlDown() :
455 456 self.GotoPos(self.GetLength())
456 457 self.SetSelectionStart(self.current_prompt_pos)
457 458 self.SetSelectionEnd(self.GetCurrentPos())
458 459 catched = True
459 elif event.KeyCode == ord('E') and event.ControlDown() :
460 elif key_code == ord('E') and event.ControlDown() :
460 461 self.GotoPos(self.GetLength())
461 462 catched = True
462 elif event.KeyCode == wx.WXK_PAGEUP:
463 elif key_code == wx.WXK_PAGEUP:
463 464 self.ScrollPages(-1)
464 elif event.KeyCode == wx.WXK_PAGEDOWN:
465 elif key_code == wx.WXK_PAGEDOWN:
465 466 self.ScrollPages(1)
466 elif event.KeyCode == wx.WXK_HOME:
467 elif key_code == wx.WXK_HOME:
467 468 self.GotoPos(self.GetLength())
468 elif event.KeyCode == wx.WXK_END:
469 elif key_code == wx.WXK_END:
469 470 self.GotoPos(self.GetLength())
470 elif event.KeyCode == wx.WXK_UP and event.ShiftDown():
471 elif key_code == wx.WXK_UP and event.ShiftDown():
471 472 self.ScrollLines(-1)
472 elif event.KeyCode == wx.WXK_DOWN and event.ShiftDown():
473 elif key_code == wx.WXK_DOWN and event.ShiftDown():
473 474 self.ScrollLines(1)
474 475 else:
475 476 catched = False
476 477
477 478 if self.AutoCompActive():
478 479 event.Skip()
479 480 else:
480 if event.KeyCode in (13, wx.WXK_NUMPAD_ENTER) and \
481 event.Modifiers in (wx.MOD_NONE, wx.MOD_WIN,
482 wx.MOD_SHIFT):
481 if key_code in (13, wx.WXK_NUMPAD_ENTER):
482 # XXX: not catching modifiers, to be wx2.6-compatible
483 483 catched = True
484 484 if not self.enter_catched:
485 485 self.CallTipCancel()
486 if event.Modifiers == wx.MOD_SHIFT:
486 if event.ShiftDown():
487 487 # Try to force execution
488 488 self.GotoPos(self.GetLength())
489 489 self.write('\n' + self.continuation_prompt(),
490 490 refresh=False)
491 491 self._on_enter()
492 492 else:
493 493 self._on_enter()
494 494 self.enter_catched = True
495 495
496 elif event.KeyCode == wx.WXK_HOME:
497 if event.Modifiers in (wx.MOD_NONE, wx.MOD_WIN):
496 elif key_code == wx.WXK_HOME:
497 if not event.ShiftDown():
498 498 self.GotoPos(self.current_prompt_pos)
499 499 catched = True
500
501 elif event.Modifiers == wx.MOD_SHIFT:
500 else:
502 501 # FIXME: This behavior is not ideal: if the selection
503 502 # is already started, it will jump.
504 503 self.SetSelectionStart(self.current_prompt_pos)
505 504 self.SetSelectionEnd(self.GetCurrentPos())
506 505 catched = True
507 506
508 elif event.KeyCode == wx.WXK_UP:
507 elif key_code == wx.WXK_UP:
509 508 if self.GetCurrentLine() > self.current_prompt_line:
510 509 if self.GetCurrentLine() == self.current_prompt_line + 1 \
511 510 and self.GetColumn(self.GetCurrentPos()) < \
512 511 self.GetColumn(self.current_prompt_pos):
513 512 self.GotoPos(self.current_prompt_pos)
514 513 else:
515 514 event.Skip()
516 515 catched = True
517 516
518 elif event.KeyCode in (wx.WXK_LEFT, wx.WXK_BACK):
517 elif key_code in (wx.WXK_LEFT, wx.WXK_BACK):
519 518 if not self._keep_cursor_in_buffer(self.GetCurrentPos() - 1):
520 519 event.Skip()
521 520 catched = True
522 521
523 elif event.KeyCode == wx.WXK_RIGHT:
522 elif key_code == wx.WXK_RIGHT:
524 523 if not self._keep_cursor_in_buffer(self.GetCurrentPos() + 1):
525 524 event.Skip()
526 525 catched = True
527 526
528 527
529 elif event.KeyCode == wx.WXK_DELETE:
528 elif key_code == wx.WXK_DELETE:
530 529 if not self._keep_cursor_in_buffer(self.GetCurrentPos() - 1):
531 530 event.Skip()
532 531 catched = True
533 532
534 533 if skip and not catched:
535 534 # Put the cursor back in the edit region
536 535 if not self._keep_cursor_in_buffer():
537 536 if not (self.GetCurrentPos() == self.GetLength()
538 and event.KeyCode == wx.WXK_DELETE):
537 and key_code == wx.WXK_DELETE):
539 538 event.Skip()
540 539 catched = True
541 540
542 541 return catched
543 542
544 543
545 544 def _on_key_up(self, event, skip=True):
546 545 """ If cursor is outside the editing region, put it back.
547 546 """
548 547 if skip:
549 548 event.Skip()
550 549 self._keep_cursor_in_buffer()
551 550
552 551
553 552 # XXX: I need to avoid the problem of having an empty glass;
554 553 def _keep_cursor_in_buffer(self, pos=None):
555 554 """ Checks if the cursor is where it is allowed to be. If not,
556 555 put it back.
557 556
558 557 Returns
559 558 -------
560 559 cursor_moved: Boolean
561 560 whether or not the cursor was moved by this routine.
562 561
563 562 Notes
564 563 ------
565 564 WARNING: This does proper checks only for horizontal
566 565 movements.
567 566 """
568 567 if pos is None:
569 568 current_pos = self.GetCurrentPos()
570 569 else:
571 570 current_pos = pos
572 571 if current_pos < self.current_prompt_pos:
573 572 self.GotoPos(self.current_prompt_pos)
574 573 return True
575 574 line_num = self.LineFromPosition(current_pos)
576 575 if not current_pos > self.GetLength():
577 576 line_pos = self.GetColumn(current_pos)
578 577 else:
579 578 line_pos = self.GetColumn(self.GetLength())
580 579 line = self.GetLine(line_num)
581 580 # Jump the continuation prompt
582 581 continuation_prompt = self.continuation_prompt()
583 582 if ( line.startswith(continuation_prompt)
584 583 and line_pos < len(continuation_prompt)):
585 584 if line_pos < 2:
586 585 # We are at the beginning of the line, trying to move
587 586 # forward: jump forward.
588 587 self.GotoPos(current_pos + 1 +
589 588 len(continuation_prompt) - line_pos)
590 589 else:
591 590 # Jump back up
592 591 self.GotoPos(self.GetLineEndPosition(line_num-1))
593 592 return True
594 593 elif ( current_pos > self.GetLineEndPosition(line_num)
595 594 and not current_pos == self.GetLength()):
596 595 # Jump to next line
597 596 self.GotoPos(current_pos + 1 +
598 597 len(continuation_prompt))
599 598 return True
600 599
601 600 # We re-allow enter event processing
602 601 self.enter_catched = False
603 602 return False
604 603
605 604
606 605 if __name__ == '__main__':
607 606 # Some simple code to test the console widget.
608 607 class MainWindow(wx.Frame):
609 608 def __init__(self, parent, id, title):
610 609 wx.Frame.__init__(self, parent, id, title, size=(300, 250))
611 610 self._sizer = wx.BoxSizer(wx.VERTICAL)
612 611 self.console_widget = ConsoleWidget(self)
613 612 self._sizer.Add(self.console_widget, 1, wx.EXPAND)
614 613 self.SetSizer(self._sizer)
615 614 self.SetAutoLayout(1)
616 615 self.Show(True)
617 616
618 617 app = wx.PySimpleApp()
619 618 w = MainWindow(None, wx.ID_ANY, 'ConsoleWidget')
620 619 w.SetSize((780, 460))
621 620 w.Show()
622 621
623 622 app.MainLoop()
624 623
625 624
@@ -1,601 +1,602 b''
1 1 # encoding: utf-8 -*- test-case-name:
2 2 # FIXME: Need to add tests.
3 3 # ipython1.frontend.wx.tests.test_wx_frontend -*-
4 4
5 5 """Classes to provide a Wx frontend to the
6 6 IPython.kernel.core.interpreter.
7 7
8 8 This class inherits from ConsoleWidget, that provides a console-like
9 9 widget to provide a text-rendering widget suitable for a terminal.
10 10 """
11 11
12 12 __docformat__ = "restructuredtext en"
13 13
14 14 #-------------------------------------------------------------------------------
15 15 # Copyright (C) 2008 The IPython Development Team
16 16 #
17 17 # Distributed under the terms of the BSD License. The full license is in
18 18 # the file COPYING, distributed as part of this software.
19 19 #-------------------------------------------------------------------------------
20 20
21 21 #-------------------------------------------------------------------------------
22 22 # Imports
23 23 #-------------------------------------------------------------------------------
24 24
25 25 # Major library imports
26 26 import re
27 27 import __builtin__
28 28 import sys
29 29 from threading import Lock
30 30
31 31 import wx
32 32 from wx import stc
33 33
34 34 # Ipython-specific imports.
35 35 from IPython.frontend.process import PipedProcess
36 36 from console_widget import ConsoleWidget, _COMPLETE_BUFFER_MARKER, \
37 37 _ERROR_MARKER, _INPUT_MARKER
38 38 from IPython.frontend.prefilterfrontend import PrefilterFrontEnd
39 39
40 40 #-------------------------------------------------------------------------------
41 41 # Classes to implement the Wx frontend
42 42 #-------------------------------------------------------------------------------
43 43 class WxController(ConsoleWidget, PrefilterFrontEnd):
44 44 """Classes to provide a Wx frontend to the
45 45 IPython.kernel.core.interpreter.
46 46
47 47 This class inherits from ConsoleWidget, that provides a console-like
48 48 widget to provide a text-rendering widget suitable for a terminal.
49 49 """
50 50
51 51 # Print debug info on what is happening to the console.
52 52 debug = False
53 53
54 54 # The title of the terminal, as captured through the ANSI escape
55 55 # sequences.
56 56 def _set_title(self, title):
57 57 return self.Parent.SetTitle(title)
58 58
59 59 def _get_title(self):
60 60 return self.Parent.GetTitle()
61 61
62 62 title = property(_get_title, _set_title)
63 63
64 64
65 65 # The buffer being edited.
66 66 # We are duplicating the definition here because of multiple
67 67 # inheritence
68 68 def _set_input_buffer(self, string):
69 69 ConsoleWidget._set_input_buffer(self, string)
70 70 self._colorize_input_buffer()
71 71
72 72 def _get_input_buffer(self):
73 73 """ Returns the text in current edit buffer.
74 74 """
75 75 return ConsoleWidget._get_input_buffer(self)
76 76
77 77 input_buffer = property(_get_input_buffer, _set_input_buffer)
78 78
79 79
80 80 #--------------------------------------------------------------------------
81 81 # Private Attributes
82 82 #--------------------------------------------------------------------------
83 83
84 84 # A flag governing the behavior of the input. Can be:
85 85 #
86 86 # 'readline' for readline-like behavior with a prompt
87 87 # and an edit buffer.
88 88 # 'raw_input' similar to readline, but triggered by a raw-input
89 89 # call. Can be used by subclasses to act differently.
90 90 # 'subprocess' for sending the raw input directly to a
91 91 # subprocess.
92 92 # 'buffering' for buffering of the input, that will be used
93 93 # when the input state switches back to another state.
94 94 _input_state = 'readline'
95 95
96 96 # Attribute to store reference to the pipes of a subprocess, if we
97 97 # are running any.
98 98 _running_process = False
99 99
100 100 # A queue for writing fast streams to the screen without flooding the
101 101 # event loop
102 102 _out_buffer = []
103 103
104 104 # A lock to lock the _out_buffer to make sure we don't empty it
105 105 # while it is being swapped
106 106 _out_buffer_lock = Lock()
107 107
108 108 # The different line markers used to higlight the prompts.
109 109 _markers = dict()
110 110
111 111 #--------------------------------------------------------------------------
112 112 # Public API
113 113 #--------------------------------------------------------------------------
114 114
115 115 def __init__(self, parent, id=wx.ID_ANY, pos=wx.DefaultPosition,
116 116 size=wx.DefaultSize,
117 117 style=wx.CLIP_CHILDREN|wx.WANTS_CHARS,
118 118 styledef=None,
119 119 *args, **kwds):
120 120 """ Create Shell instance.
121 121
122 122 Parameters
123 123 -----------
124 124 styledef : dict, optional
125 125 styledef is the dictionary of options used to define the
126 126 style.
127 127 """
128 128 if styledef is not None:
129 129 self.style = styledef
130 130 ConsoleWidget.__init__(self, parent, id, pos, size, style)
131 131 PrefilterFrontEnd.__init__(self, **kwds)
132 132
133 133 # Stick in our own raw_input:
134 134 self.ipython0.raw_input = self.raw_input
135 135
136 136 # A time for flushing the write buffer
137 137 BUFFER_FLUSH_TIMER_ID = 100
138 138 self._buffer_flush_timer = wx.Timer(self, BUFFER_FLUSH_TIMER_ID)
139 139 wx.EVT_TIMER(self, BUFFER_FLUSH_TIMER_ID, self._buffer_flush)
140 140
141 141 if 'debug' in kwds:
142 142 self.debug = kwds['debug']
143 143 kwds.pop('debug')
144 144
145 145 # Inject self in namespace, for debug
146 146 if self.debug:
147 147 self.shell.user_ns['self'] = self
148 148 # Inject our own raw_input in namespace
149 149 self.shell.user_ns['raw_input'] = self.raw_input
150 150
151 151 def raw_input(self, prompt=''):
152 152 """ A replacement from python's raw_input.
153 153 """
154 154 self.new_prompt(prompt)
155 155 self._input_state = 'raw_input'
156 156 if hasattr(self, '_cursor'):
157 157 del self._cursor
158 158 self.SetCursor(wx.StockCursor(wx.CURSOR_CROSS))
159 159 self.__old_on_enter = self._on_enter
160 160 event_loop = wx.EventLoop()
161 161 def my_on_enter():
162 162 event_loop.Exit()
163 163 self._on_enter = my_on_enter
164 164 # XXX: Running a separate event_loop. Ugly.
165 165 event_loop.Run()
166 166 self._on_enter = self.__old_on_enter
167 167 self._input_state = 'buffering'
168 168 self._cursor = wx.BusyCursor()
169 169 return self.input_buffer.rstrip('\n')
170 170
171 171
172 172 def system_call(self, command_string):
173 173 self._input_state = 'subprocess'
174 174 event_loop = wx.EventLoop()
175 175 def _end_system_call():
176 176 self._input_state = 'buffering'
177 177 self._running_process = False
178 178 event_loop.Exit()
179 179
180 180 self._running_process = PipedProcess(command_string,
181 181 out_callback=self.buffered_write,
182 182 end_callback = _end_system_call)
183 183 self._running_process.start()
184 184 # XXX: Running a separate event_loop. Ugly.
185 185 event_loop.Run()
186 186 # Be sure to flush the buffer.
187 187 self._buffer_flush(event=None)
188 188
189 189
190 190 def do_calltip(self):
191 191 """ Analyse current and displays useful calltip for it.
192 192 """
193 193 if self.debug:
194 194 print >>sys.__stdout__, "do_calltip"
195 195 separators = re.compile('[\s\{\}\[\]\(\)\= ,:]')
196 196 symbol = self.input_buffer
197 197 symbol_string = separators.split(symbol)[-1]
198 198 base_symbol_string = symbol_string.split('.')[0]
199 199 if base_symbol_string in self.shell.user_ns:
200 200 symbol = self.shell.user_ns[base_symbol_string]
201 201 elif base_symbol_string in self.shell.user_global_ns:
202 202 symbol = self.shell.user_global_ns[base_symbol_string]
203 203 elif base_symbol_string in __builtin__.__dict__:
204 204 symbol = __builtin__.__dict__[base_symbol_string]
205 205 else:
206 206 return False
207 207 try:
208 208 for name in symbol_string.split('.')[1:] + ['__doc__']:
209 209 symbol = getattr(symbol, name)
210 210 self.AutoCompCancel()
211 211 # Check that the symbol can indeed be converted to a string:
212 212 symbol += ''
213 213 wx.CallAfter(self.CallTipShow, self.GetCurrentPos(), symbol)
214 214 except:
215 215 # The retrieve symbol couldn't be converted to a string
216 216 pass
217 217
218 218
219 219 def _popup_completion(self, create=False):
220 220 """ Updates the popup completion menu if it exists. If create is
221 221 true, open the menu.
222 222 """
223 223 if self.debug:
224 224 print >>sys.__stdout__, "_popup_completion"
225 225 line = self.input_buffer
226 226 if (self.AutoCompActive() and line and not line[-1] == '.') \
227 227 or create==True:
228 228 suggestion, completions = self.complete(line)
229 229 if completions:
230 230 offset = len(self._get_completion_text(line))
231 231 self.pop_completion(completions, offset=offset)
232 232 if self.debug:
233 233 print >>sys.__stdout__, completions
234 234
235 235
236 236 def buffered_write(self, text):
237 237 """ A write method for streams, that caches the stream in order
238 238 to avoid flooding the event loop.
239 239
240 240 This can be called outside of the main loop, in separate
241 241 threads.
242 242 """
243 243 self._out_buffer_lock.acquire()
244 244 self._out_buffer.append(text)
245 245 self._out_buffer_lock.release()
246 246 if not self._buffer_flush_timer.IsRunning():
247 247 wx.CallAfter(self._buffer_flush_timer.Start,
248 248 milliseconds=100, oneShot=True)
249 249
250 250
251 251 def clear_screen(self):
252 252 """ Empty completely the widget.
253 253 """
254 254 self.ClearAll()
255 255 self.new_prompt(self.input_prompt_template.substitute(
256 256 number=(self.last_result['number'] + 1)))
257 257
258 258
259 259 #--------------------------------------------------------------------------
260 260 # LineFrontEnd interface
261 261 #--------------------------------------------------------------------------
262 262
263 263 def execute(self, python_string, raw_string=None):
264 264 self._input_state = 'buffering'
265 265 self.CallTipCancel()
266 266 self._cursor = wx.BusyCursor()
267 267 if raw_string is None:
268 268 raw_string = python_string
269 269 end_line = self.current_prompt_line \
270 270 + max(1, len(raw_string.split('\n'))-1)
271 271 for i in range(self.current_prompt_line, end_line):
272 272 if i in self._markers:
273 273 self.MarkerDeleteHandle(self._markers[i])
274 274 self._markers[i] = self.MarkerAdd(i, _COMPLETE_BUFFER_MARKER)
275 275 # Use a callafter to update the display robustly under windows
276 276 def callback():
277 277 self.GotoPos(self.GetLength())
278 278 PrefilterFrontEnd.execute(self, python_string,
279 279 raw_string=raw_string)
280 280 wx.CallAfter(callback)
281 281
282 282
283 283 def execute_command(self, command, hidden=False):
284 284 """ Execute a command, not only in the model, but also in the
285 285 view.
286 286 """
287 287 # XXX: This method needs to be integrated in the base fronted
288 288 # interface
289 289 if hidden:
290 290 return self.shell.execute(command)
291 291 else:
292 292 # XXX: we are not storing the input buffer previous to the
293 293 # execution, as this forces us to run the execution
294 294 # input_buffer a yield, which is not good.
295 295 ##current_buffer = self.shell.control.input_buffer
296 296 command = command.rstrip()
297 297 if len(command.split('\n')) > 1:
298 298 # The input command is several lines long, we need to
299 299 # force the execution to happen
300 300 command += '\n'
301 301 cleaned_command = self.prefilter_input(command)
302 302 self.input_buffer = command
303 303 # Do not use wx.Yield() (aka GUI.process_events()) to avoid
304 304 # recursive yields.
305 305 self.ProcessEvent(wx.PaintEvent())
306 306 self.write('\n')
307 307 if not self.is_complete(cleaned_command + '\n'):
308 308 self._colorize_input_buffer()
309 309 self.render_error('Incomplete or invalid input')
310 310 self.new_prompt(self.input_prompt_template.substitute(
311 311 number=(self.last_result['number'] + 1)))
312 312 return False
313 313 self._on_enter()
314 314 return True
315 315
316 316
317 317 def save_output_hooks(self):
318 318 self.__old_raw_input = __builtin__.raw_input
319 319 PrefilterFrontEnd.save_output_hooks(self)
320 320
321 321 def capture_output(self):
322 322 self.SetLexer(stc.STC_LEX_NULL)
323 323 PrefilterFrontEnd.capture_output(self)
324 324 __builtin__.raw_input = self.raw_input
325 325
326 326
327 327 def release_output(self):
328 328 __builtin__.raw_input = self.__old_raw_input
329 329 PrefilterFrontEnd.release_output(self)
330 330 self.SetLexer(stc.STC_LEX_PYTHON)
331 331
332 332
333 333 def after_execute(self):
334 334 PrefilterFrontEnd.after_execute(self)
335 335 # Clear the wait cursor
336 336 if hasattr(self, '_cursor'):
337 337 del self._cursor
338 338 self.SetCursor(wx.StockCursor(wx.CURSOR_CHAR))
339 339
340 340
341 341 def show_traceback(self):
342 342 start_line = self.GetCurrentLine()
343 343 PrefilterFrontEnd.show_traceback(self)
344 344 self.ProcessEvent(wx.PaintEvent())
345 345 #wx.Yield()
346 346 for i in range(start_line, self.GetCurrentLine()):
347 347 self._markers[i] = self.MarkerAdd(i, _ERROR_MARKER)
348 348
349 349
350 350 #--------------------------------------------------------------------------
351 351 # FrontEndBase interface
352 352 #--------------------------------------------------------------------------
353 353
354 354 def render_error(self, e):
355 355 start_line = self.GetCurrentLine()
356 356 self.write('\n' + e + '\n')
357 357 for i in range(start_line, self.GetCurrentLine()):
358 358 self._markers[i] = self.MarkerAdd(i, _ERROR_MARKER)
359 359
360 360
361 361 #--------------------------------------------------------------------------
362 362 # ConsoleWidget interface
363 363 #--------------------------------------------------------------------------
364 364
365 365 def new_prompt(self, prompt):
366 366 """ Display a new prompt, and start a new input buffer.
367 367 """
368 368 self._input_state = 'readline'
369 369 ConsoleWidget.new_prompt(self, prompt)
370 370 i = self.current_prompt_line
371 371 self._markers[i] = self.MarkerAdd(i, _INPUT_MARKER)
372 372
373 373
374 374 def continuation_prompt(self, *args, **kwargs):
375 375 # Avoid multiple inheritence, be explicit about which
376 376 # parent method class gets called
377 377 return ConsoleWidget.continuation_prompt(self, *args, **kwargs)
378 378
379 379
380 380 def write(self, *args, **kwargs):
381 381 # Avoid multiple inheritence, be explicit about which
382 382 # parent method class gets called
383 383 return ConsoleWidget.write(self, *args, **kwargs)
384 384
385 385
386 386 def _on_key_down(self, event, skip=True):
387 387 """ Capture the character events, let the parent
388 388 widget handle them, and put our logic afterward.
389 389 """
390 390 # FIXME: This method needs to be broken down in smaller ones.
391 391 current_line_num = self.GetCurrentLine()
392 if event.KeyCode in (ord('c'), ord('C')) and event.ControlDown():
392 key_code = event.GetKeyCode()
393 if key_code in (ord('c'), ord('C')) and event.ControlDown():
393 394 # Capture Control-C
394 395 if self._input_state == 'subprocess':
395 396 if self.debug:
396 397 print >>sys.__stderr__, 'Killing running process'
397 398 if hasattr(self._running_process, 'process'):
398 399 self._running_process.process.kill()
399 400 elif self._input_state == 'buffering':
400 401 if self.debug:
401 402 print >>sys.__stderr__, 'Raising KeyboardInterrupt'
402 403 raise KeyboardInterrupt
403 404 # XXX: We need to make really sure we
404 405 # get back to a prompt.
405 406 elif self._input_state == 'subprocess' and (
406 ( event.KeyCode<256 and
407 not event.ControlDown() )
407 ( key_code <256 and not event.ControlDown() )
408 408 or
409 ( event.KeyCode in (ord('d'), ord('D')) and
409 ( key_code in (ord('d'), ord('D')) and
410 410 event.ControlDown())):
411 411 # We are running a process, we redirect keys.
412 412 ConsoleWidget._on_key_down(self, event, skip=skip)
413 char = chr(event.KeyCode)
413 char = chr(key_code)
414 414 # Deal with some inconsistency in wx keycodes:
415 415 if char == '\r':
416 416 char = '\n'
417 417 elif not event.ShiftDown():
418 418 char = char.lower()
419 if event.ControlDown() and event.KeyCode in (ord('d'), ord('D')):
419 if event.ControlDown() and key_code in (ord('d'), ord('D')):
420 420 char = '\04'
421 421 self._running_process.process.stdin.write(char)
422 422 self._running_process.process.stdin.flush()
423 elif event.KeyCode in (ord('('), 57, 53):
423 elif key_code in (ord('('), 57, 53):
424 424 # Calltips
425 425 event.Skip()
426 426 self.do_calltip()
427 elif self.AutoCompActive() and not event.KeyCode == ord('\t'):
427 elif self.AutoCompActive() and not key_code == ord('\t'):
428 428 event.Skip()
429 if event.KeyCode in (wx.WXK_BACK, wx.WXK_DELETE):
429 if key_code in (wx.WXK_BACK, wx.WXK_DELETE):
430 430 wx.CallAfter(self._popup_completion, create=True)
431 elif not event.KeyCode in (wx.WXK_UP, wx.WXK_DOWN, wx.WXK_LEFT,
431 elif not key_code in (wx.WXK_UP, wx.WXK_DOWN, wx.WXK_LEFT,
432 432 wx.WXK_RIGHT, wx.WXK_ESCAPE):
433 433 wx.CallAfter(self._popup_completion)
434 434 else:
435 435 # Up history
436 if event.KeyCode == wx.WXK_UP and (
437 ( current_line_num == self.current_prompt_line and
438 event.Modifiers in (wx.MOD_NONE, wx.MOD_WIN) )
439 or event.ControlDown() ):
436 if key_code == wx.WXK_UP and (
437 event.ControlDown() or
438 current_line_num == self.current_prompt_line
439 ):
440 440 new_buffer = self.get_history_previous(
441 441 self.input_buffer)
442 442 if new_buffer is not None:
443 443 self.input_buffer = new_buffer
444 444 if self.GetCurrentLine() > self.current_prompt_line:
445 445 # Go to first line, for seemless history up.
446 446 self.GotoPos(self.current_prompt_pos)
447 447 # Down history
448 elif event.KeyCode == wx.WXK_DOWN and (
449 ( current_line_num == self.LineCount -1 and
450 event.Modifiers in (wx.MOD_NONE, wx.MOD_WIN) )
451 or event.ControlDown() ):
448 elif key_code == wx.WXK_DOWN and (
449 event.ControlDown() or
450 current_line_num == self.LineCount -1
451 ):
452 452 new_buffer = self.get_history_next()
453 453 if new_buffer is not None:
454 454 self.input_buffer = new_buffer
455 455 # Tab-completion
456 elif event.KeyCode == ord('\t'):
456 elif key_code == ord('\t'):
457 457 current_line, current_line_num = self.CurLine
458 if not re.match(r'^\s*$', current_line):
458 if not re.match(r'^%s\s*$' % self.continuation_prompt(),
459 current_line):
459 460 self.complete_current_input()
460 461 if self.AutoCompActive():
461 462 wx.CallAfter(self._popup_completion, create=True)
462 463 else:
463 464 event.Skip()
464 elif event.KeyCode == wx.WXK_BACK:
465 elif key_code == wx.WXK_BACK:
465 466 # If characters where erased, check if we have to
466 467 # remove a line.
467 468 # XXX: What about DEL?
468 469 # FIXME: This logics should be in ConsoleWidget, as it is
469 470 # independant of IPython
470 471 current_line, _ = self.CurLine
471 472 current_pos = self.GetCurrentPos()
472 473 current_line_num = self.LineFromPosition(current_pos)
473 474 current_col = self.GetColumn(current_pos)
474 475 len_prompt = len(self.continuation_prompt())
475 476 if ( current_line.startswith(self.continuation_prompt())
476 477 and current_col == len_prompt):
477 478 new_lines = []
478 479 for line_num, line in enumerate(
479 480 self.input_buffer.split('\n')):
480 481 if (line_num + self.current_prompt_line ==
481 482 current_line_num):
482 483 new_lines.append(line[len_prompt:])
483 484 else:
484 485 new_lines.append('\n'+line)
485 486 # The first character is '\n', due to the above
486 487 # code:
487 488 self.input_buffer = ''.join(new_lines)[1:]
488 489 self.GotoPos(current_pos - 1 - len_prompt)
489 490 else:
490 491 ConsoleWidget._on_key_down(self, event, skip=skip)
491 492 else:
492 493 ConsoleWidget._on_key_down(self, event, skip=skip)
493 494
494 495
495 496
496 497 def _on_key_up(self, event, skip=True):
497 498 """ Called when any key is released.
498 499 """
499 if event.KeyCode in (59, ord('.')):
500 if event.GetKeyCode() in (59, ord('.')):
500 501 # Intercepting '.'
501 502 event.Skip()
502 503 wx.CallAfter(self._popup_completion, create=True)
503 504 else:
504 505 ConsoleWidget._on_key_up(self, event, skip=skip)
505 506 # Make sure the continuation_prompts are always followed by a
506 507 # whitespace
507 508 new_lines = []
508 509 if self._input_state == 'readline':
509 510 position = self.GetCurrentPos()
510 511 continuation_prompt = self.continuation_prompt()[:-1]
511 512 for line in self.input_buffer.split('\n'):
512 513 if not line == continuation_prompt:
513 514 new_lines.append(line)
514 515 self.input_buffer = '\n'.join(new_lines)
515 516 self.GotoPos(position)
516 517
517 518
518 519 def _on_enter(self):
519 520 """ Called on return key down, in readline input_state.
520 521 """
521 522 last_line_num = self.LineFromPosition(self.GetLength())
522 523 current_line_num = self.LineFromPosition(self.GetCurrentPos())
523 524 new_line_pos = (last_line_num - current_line_num)
524 525 if self.debug:
525 526 print >>sys.__stdout__, repr(self.input_buffer)
526 527 self.write('\n', refresh=False)
527 528 # Under windows scintilla seems to be doing funny
528 529 # stuff to the line returns here, but the getter for
529 530 # input_buffer filters this out.
530 531 if sys.platform == 'win32':
531 532 self.input_buffer = self.input_buffer
532 533 old_prompt_num = self.current_prompt_pos
533 534 has_executed = PrefilterFrontEnd._on_enter(self,
534 535 new_line_pos=new_line_pos)
535 536 if old_prompt_num == self.current_prompt_pos:
536 537 # No execution has happened
537 538 self.GotoPos(self.GetLineEndPosition(current_line_num + 1))
538 539 return has_executed
539 540
540 541
541 542 #--------------------------------------------------------------------------
542 543 # EditWindow API
543 544 #--------------------------------------------------------------------------
544 545
545 546 def OnUpdateUI(self, event):
546 547 """ Override the OnUpdateUI of the EditWindow class, to prevent
547 548 syntax highlighting both for faster redraw, and for more
548 549 consistent look and feel.
549 550 """
550 551 if not self._input_state == 'readline':
551 552 ConsoleWidget.OnUpdateUI(self, event)
552 553
553 554 #--------------------------------------------------------------------------
554 555 # Private API
555 556 #--------------------------------------------------------------------------
556 557
557 558 def _buffer_flush(self, event):
558 559 """ Called by the timer to flush the write buffer.
559 560
560 561 This is always called in the mainloop, by the wx timer.
561 562 """
562 563 self._out_buffer_lock.acquire()
563 564 _out_buffer = self._out_buffer
564 565 self._out_buffer = []
565 566 self._out_buffer_lock.release()
566 567 self.write(''.join(_out_buffer), refresh=False)
567 568
568 569
569 570 def _colorize_input_buffer(self):
570 571 """ Keep the input buffer lines at a bright color.
571 572 """
572 573 if not self._input_state in ('readline', 'raw_input'):
573 574 return
574 575 end_line = self.GetCurrentLine()
575 576 if not sys.platform == 'win32':
576 577 end_line += 1
577 578 for i in range(self.current_prompt_line, end_line):
578 579 if i in self._markers:
579 580 self.MarkerDeleteHandle(self._markers[i])
580 581 self._markers[i] = self.MarkerAdd(i, _INPUT_MARKER)
581 582
582 583
583 584 if __name__ == '__main__':
584 585 class MainWindow(wx.Frame):
585 586 def __init__(self, parent, id, title):
586 587 wx.Frame.__init__(self, parent, id, title, size=(300,250))
587 588 self._sizer = wx.BoxSizer(wx.VERTICAL)
588 589 self.shell = WxController(self)
589 590 self._sizer.Add(self.shell, 1, wx.EXPAND)
590 591 self.SetSizer(self._sizer)
591 592 self.SetAutoLayout(1)
592 593 self.Show(True)
593 594
594 595 app = wx.PySimpleApp()
595 596 frame = MainWindow(None, wx.ID_ANY, 'Ipython')
596 597 frame.shell.SetFocus()
597 598 frame.SetSize((680, 460))
598 599 self = frame.shell
599 600
600 601 app.MainLoop()
601 602
1 NO CONTENT: modified file chmod 100755 => 100644
General Comments 0
You need to be logged in to leave comments. Login now