##// END OF EJS Templates
BUG: Integrate bug fixes from Enthought
Gael Varoquaux -
Show More
@@ -0,0 +1,38 b''
1 # encoding: utf-8
2 """
3 Test the LineFrontEnd
4 """
5
6 __docformat__ = "restructuredtext en"
7
8 #-------------------------------------------------------------------------------
9 # Copyright (C) 2008 The IPython Development Team
10 #
11 # Distributed under the terms of the BSD License. The full license is
12 # in the file COPYING, distributed as part of this software.
13 #-------------------------------------------------------------------------------
14
15 from IPython.frontend.linefrontendbase import LineFrontEndBase
16 from copy import deepcopy
17
18 class ConcreteLineFrontEnd(LineFrontEndBase):
19 """ A concrete class to test the LineFrontEndBase.
20 """
21 def capture_output(self):
22 pass
23
24 def release_output(self):
25 pass
26
27
28 def test_is_complete():
29 """ Tests line completion heuristic.
30 """
31 frontend = ConcreteLineFrontEnd()
32 assert not frontend.is_complete('for x in \\')
33 assert not frontend.is_complete('for x in (1, ):')
34 assert frontend.is_complete('for x in (1, ):\n pass')
35
36
37 if __name__ == '__main__':
38 test_is_complete()
@@ -1,353 +1,357 b''
1 1 """
2 2 Base front end class for all line-oriented frontends, rather than
3 3 block-oriented.
4 4
5 5 Currently this focuses on synchronous frontends.
6 6 """
7 7 __docformat__ = "restructuredtext en"
8 8
9 9 #-------------------------------------------------------------------------------
10 10 # Copyright (C) 2008 The IPython Development Team
11 11 #
12 12 # Distributed under the terms of the BSD License. The full license is in
13 13 # the file COPYING, distributed as part of this software.
14 14 #-------------------------------------------------------------------------------
15 15
16 16 #-------------------------------------------------------------------------------
17 17 # Imports
18 18 #-------------------------------------------------------------------------------
19 19 import re
20 20
21 21 import sys
22 22 import codeop
23 23
24 24 from frontendbase import FrontEndBase
25 25 from IPython.kernel.core.interpreter import Interpreter
26 26
27 27 def common_prefix(strings):
28 28 """ Given a list of strings, return the common prefix between all
29 29 these strings.
30 30 """
31 31 ref = strings[0]
32 32 prefix = ''
33 33 for size in range(len(ref)):
34 34 test_prefix = ref[:size+1]
35 35 for string in strings[1:]:
36 36 if not string.startswith(test_prefix):
37 37 return prefix
38 38 prefix = test_prefix
39 39
40 40 return prefix
41 41
42 42 #-------------------------------------------------------------------------------
43 43 # Base class for the line-oriented front ends
44 44 #-------------------------------------------------------------------------------
45 45 class LineFrontEndBase(FrontEndBase):
46 46 """ Concrete implementation of the FrontEndBase class. This is meant
47 47 to be the base class behind all the frontend that are line-oriented,
48 48 rather than block-oriented.
49 49 """
50 50
51 51 # We need to keep the prompt number, to be able to increment
52 52 # it when there is an exception.
53 53 prompt_number = 1
54 54
55 55 # We keep a reference to the last result: it helps testing and
56 56 # programatic control of the frontend.
57 57 last_result = dict(number=0)
58 58
59 59 # The input buffer being edited
60 60 input_buffer = ''
61 61
62 62 # Set to true for debug output
63 63 debug = False
64 64
65 65 # A banner to print at startup
66 66 banner = None
67 67
68 68 #--------------------------------------------------------------------------
69 69 # FrontEndBase interface
70 70 #--------------------------------------------------------------------------
71 71
72 72 def __init__(self, shell=None, history=None, banner=None, *args, **kwargs):
73 73 if shell is None:
74 74 shell = Interpreter()
75 75 FrontEndBase.__init__(self, shell=shell, history=history)
76 76
77 77 if banner is not None:
78 78 self.banner = banner
79 79
80 80 def start(self):
81 81 """ Put the frontend in a state where it is ready for user
82 82 interaction.
83 83 """
84 84 if self.banner is not None:
85 85 self.write(self.banner, refresh=False)
86 86
87 87 self.new_prompt(self.input_prompt_template.substitute(number=1))
88 88
89 89
90 90 def complete(self, line):
91 91 """Complete line in engine's user_ns
92 92
93 93 Parameters
94 94 ----------
95 95 line : string
96 96
97 97 Result
98 98 ------
99 99 The replacement for the line and the list of possible completions.
100 100 """
101 101 completions = self.shell.complete(line)
102 102 complete_sep = re.compile('[\s\{\}\[\]\(\)\=]')
103 103 if completions:
104 104 prefix = common_prefix(completions)
105 105 residual = complete_sep.split(line)[:-1]
106 106 line = line[:-len(residual)] + prefix
107 107 return line, completions
108 108
109 109
110 110 def render_result(self, result):
111 111 """ Frontend-specific rendering of the result of a calculation
112 112 that has been sent to an engine.
113 113 """
114 114 if 'stdout' in result and result['stdout']:
115 115 self.write('\n' + result['stdout'])
116 116 if 'display' in result and result['display']:
117 117 self.write("%s%s\n" % (
118 118 self.output_prompt_template.substitute(
119 119 number=result['number']),
120 120 result['display']['pprint']
121 121 ) )
122 122
123 123
124 124 def render_error(self, failure):
125 125 """ Frontend-specific rendering of error.
126 126 """
127 127 self.write('\n\n'+str(failure)+'\n\n')
128 128 return failure
129 129
130 130
131 131 def is_complete(self, string):
132 132 """ Check if a string forms a complete, executable set of
133 133 commands.
134 134
135 135 For the line-oriented frontend, multi-line code is not executed
136 136 as soon as it is complete: the users has to enter two line
137 137 returns.
138 138 """
139 139 if string in ('', '\n'):
140 140 # Prefiltering, eg through ipython0, may return an empty
141 141 # string although some operations have been accomplished. We
142 142 # thus want to consider an empty string as a complete
143 143 # statement.
144 144 return True
145 145 elif ( len(self.input_buffer.split('\n'))>2
146 146 and not re.findall(r"\n[\t ]*\n[\t ]*$", string)):
147 147 return False
148 148 else:
149 149 self.capture_output()
150 150 try:
151 151 # Add line returns here, to make sure that the statement is
152 # complete.
153 is_complete = codeop.compile_command(string.rstrip() + '\n\n',
152 # complete (except if '\' was used).
153 # This should probably be done in a different place (like
154 # maybe 'prefilter_input' method? For now, this works.
155 clean_string = string.rstrip('\n')
156 if not clean_string.endswith('\\'): clean_string +='\n\n'
157 is_complete = codeop.compile_command(clean_string,
154 158 "<string>", "exec")
155 159 self.release_output()
156 160 except Exception, e:
157 161 # XXX: Hack: return True so that the
158 162 # code gets executed and the error captured.
159 163 is_complete = True
160 164 return is_complete
161 165
162 166
163 167 def write(self, string, refresh=True):
164 168 """ Write some characters to the display.
165 169
166 170 Subclass should overide this method.
167 171
168 172 The refresh keyword argument is used in frontends with an
169 173 event loop, to choose whether the write should trigget an UI
170 174 refresh, and thus be syncrhonous, or not.
171 175 """
172 176 print >>sys.__stderr__, string
173 177
174 178
175 179 def execute(self, python_string, raw_string=None):
176 180 """ Stores the raw_string in the history, and sends the
177 181 python string to the interpreter.
178 182 """
179 183 if raw_string is None:
180 184 raw_string = python_string
181 185 # Create a false result, in case there is an exception
182 186 self.last_result = dict(number=self.prompt_number)
183 187
184 188 ## try:
185 189 ## self.history.input_cache[-1] = raw_string.rstrip()
186 190 ## result = self.shell.execute(python_string)
187 191 ## self.last_result = result
188 192 ## self.render_result(result)
189 193 ## except:
190 194 ## self.show_traceback()
191 195 ## finally:
192 196 ## self.after_execute()
193 197
194 198 try:
195 199 try:
196 200 self.history.input_cache[-1] = raw_string.rstrip()
197 201 result = self.shell.execute(python_string)
198 202 self.last_result = result
199 203 self.render_result(result)
200 204 except:
201 205 self.show_traceback()
202 206 finally:
203 207 self.after_execute()
204 208
205 209
206 210 #--------------------------------------------------------------------------
207 211 # LineFrontEndBase interface
208 212 #--------------------------------------------------------------------------
209 213
210 214 def prefilter_input(self, string):
211 215 """ Prefilter the input to turn it in valid python.
212 216 """
213 217 string = string.replace('\r\n', '\n')
214 218 string = string.replace('\t', 4*' ')
215 219 # Clean the trailing whitespace
216 220 string = '\n'.join(l.rstrip() for l in string.split('\n'))
217 221 return string
218 222
219 223
220 224 def after_execute(self):
221 225 """ All the operations required after an execution to put the
222 226 terminal back in a shape where it is usable.
223 227 """
224 228 self.prompt_number += 1
225 229 self.new_prompt(self.input_prompt_template.substitute(
226 230 number=(self.last_result['number'] + 1)))
227 231 # Start a new empty history entry
228 232 self._add_history(None, '')
229 233 self.history_cursor = len(self.history.input_cache) - 1
230 234
231 235
232 236 def complete_current_input(self):
233 237 """ Do code completion on current line.
234 238 """
235 239 if self.debug:
236 240 print >>sys.__stdout__, "complete_current_input",
237 241 line = self.input_buffer
238 242 new_line, completions = self.complete(line)
239 243 if len(completions)>1:
240 244 self.write_completion(completions, new_line=new_line)
241 245 elif not line == new_line:
242 246 self.input_buffer = new_line
243 247 if self.debug:
244 248 print >>sys.__stdout__, 'line', line
245 249 print >>sys.__stdout__, 'new_line', new_line
246 250 print >>sys.__stdout__, completions
247 251
248 252
249 253 def get_line_width(self):
250 254 """ Return the width of the line in characters.
251 255 """
252 256 return 80
253 257
254 258
255 259 def write_completion(self, possibilities, new_line=None):
256 260 """ Write the list of possible completions.
257 261
258 262 new_line is the completed input line that should be displayed
259 263 after the completion are writen. If None, the input_buffer
260 264 before the completion is used.
261 265 """
262 266 if new_line is None:
263 267 new_line = self.input_buffer
264 268
265 269 self.write('\n')
266 270 max_len = len(max(possibilities, key=len)) + 1
267 271
268 272 # Now we check how much symbol we can put on a line...
269 273 chars_per_line = self.get_line_width()
270 274 symbols_per_line = max(1, chars_per_line/max_len)
271 275
272 276 pos = 1
273 buf = []
277 completion_string = []
274 278 for symbol in possibilities:
275 279 if pos < symbols_per_line:
276 buf.append(symbol.ljust(max_len))
280 completion_string.append(symbol.ljust(max_len))
277 281 pos += 1
278 282 else:
279 buf.append(symbol.rstrip() + '\n')
283 completion_string.append(symbol.rstrip() + '\n')
280 284 pos = 1
281 self.write(''.join(buf))
285 self.write(''.join(completion_string))
282 286 self.new_prompt(self.input_prompt_template.substitute(
283 287 number=self.last_result['number'] + 1))
284 288 self.input_buffer = new_line
285 289
286 290
287 291 def new_prompt(self, prompt):
288 292 """ Prints a prompt and starts a new editing buffer.
289 293
290 294 Subclasses should use this method to make sure that the
291 295 terminal is put in a state favorable for a new line
292 296 input.
293 297 """
294 298 self.input_buffer = ''
295 299 self.write(prompt)
296 300
297 301
298 302 def continuation_prompt(self):
299 303 """Returns the current continuation prompt.
300 304 Overridden to generate a continuation prompt matching the length of the
301 305 current prompt."""
302 306
303 307 # FIXME: This is a bad hack.. I need to find a way to use the 'Prompt2'
304 308 # class in IPython/kernel/prompts.py. Basically, I am trying to get the
305 309 # length of the current prompt ("In ['number']").
306 310 return ("."*(5+len(str(self.last_result['number']))) + ':')
307 311
308 312
309 313 def execute_command(self, command, hidden=False):
310 314 """ Execute a command, not only in the model, but also in the
311 315 view, if any.
312 316 """
313 317 return self.shell.execute(command)
314 318
315 319 #--------------------------------------------------------------------------
316 320 # Private API
317 321 #--------------------------------------------------------------------------
318 322
319 323 def _on_enter(self):
320 324 """ Called when the return key is pressed in a line editing
321 325 buffer.
322 326 """
323 327 current_buffer = self.input_buffer
324 328 # XXX: This string replace is ugly, but there should be no way it
325 329 # fails.
326 330 prompt_less_buffer = re.sub('^' + self.continuation_prompt(),
327 331 '', current_buffer).replace('\n' + self.continuation_prompt(),
328 332 '\n')
329 333 cleaned_buffer = self.prefilter_input(prompt_less_buffer)
330 334 if self.is_complete(cleaned_buffer):
331 335 self.execute(cleaned_buffer, raw_string=current_buffer)
332 336 else:
333 337 self.input_buffer += self.continuation_prompt() + \
334 338 self._get_indent_string(prompt_less_buffer[:-1])
335 339 if len(current_buffer.split('\n')) == 2:
336 340 self.input_buffer += '\t'
337 341 if current_buffer[:-1].split('\n')[-1].rstrip().endswith(':'):
338 342 self.input_buffer += '\t'
339 343
340 344
341 345 def _get_indent_string(self, string):
342 346 """ Return the string of whitespace that prefixes a line. Used to
343 347 add the right amount of indendation when creating a new line.
344 348 """
345 349 string = string.replace('\t', ' '*4)
346 350 string = string.split('\n')[-1]
347 351 indent_chars = len(string) - len(string.lstrip())
348 352 indent_string = '\t'*(indent_chars // 4) + \
349 353 ' '*(indent_chars % 4)
350 354
351 355 return indent_string
352 356
353 357
@@ -1,275 +1,281 b''
1 1 """
2 2 Frontend class that uses IPython0 to prefilter the inputs.
3 3
4 4 Using the IPython0 mechanism gives us access to the magics.
5 5
6 6 This is a transitory class, used here to do the transition between
7 7 ipython0 and ipython1. This class is meant to be short-lived as more
8 8 functionnality is abstracted out of ipython0 in reusable functions and
9 9 is added on the interpreter. This class can be a used to guide this
10 10 refactoring.
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 import sys
25 25 import pydoc
26 26 import os
27 27 import re
28 28 import __builtin__
29 29
30 30 from IPython.ipmaker import make_IPython
31 31 from IPython.ipapi import IPApi
32 32 from IPython.kernel.core.redirector_output_trap import RedirectorOutputTrap
33 33
34 34 from IPython.kernel.core.sync_traceback_trap import SyncTracebackTrap
35 35
36 36 from IPython.genutils import Term
37 37
38 38 from linefrontendbase import LineFrontEndBase, common_prefix
39 39
40 40
41 41 def mk_system_call(system_call_function, command):
42 42 """ given a os.system replacement, and a leading string command,
43 43 returns a function that will execute the command with the given
44 44 argument string.
45 45 """
46 46 def my_system_call(args):
47 47 system_call_function("%s %s" % (command, args))
48 48
49 49 my_system_call.__doc__ = "Calls %s" % command
50 50 return my_system_call
51 51
52 52 #-------------------------------------------------------------------------------
53 53 # Frontend class using ipython0 to do the prefiltering.
54 54 #-------------------------------------------------------------------------------
55 55 class PrefilterFrontEnd(LineFrontEndBase):
56 56 """ Class that uses ipython0 to do prefilter the input, do the
57 57 completion and the magics.
58 58
59 59 The core trick is to use an ipython0 instance to prefilter the
60 60 input, and share the namespace between the interpreter instance used
61 61 to execute the statements and the ipython0 used for code
62 62 completion...
63 63 """
64 64
65 65 debug = False
66 66
67 67 def __init__(self, ipython0=None, *args, **kwargs):
68 68 """ Parameters:
69 69 -----------
70 70
71 71 ipython0: an optional ipython0 instance to use for command
72 72 prefiltering and completion.
73 73 """
74 # This is a hack to avoid the IPython exception hook to trigger
75 # on exceptions (https://bugs.launchpad.net/bugs/337105)
76 # XXX: This is horrible: module-leve monkey patching -> side
77 # effects.
78 from IPython import iplib
79 iplib.InteractiveShell.isthreaded = True
80
74 81 LineFrontEndBase.__init__(self, *args, **kwargs)
75 82 self.shell.output_trap = RedirectorOutputTrap(
76 83 out_callback=self.write,
77 84 err_callback=self.write,
78 85 )
79 86 self.shell.traceback_trap = SyncTracebackTrap(
80 87 formatters=self.shell.traceback_trap.formatters,
81 88 )
82 89
83 90 # Start the ipython0 instance:
84 91 self.save_output_hooks()
85 92 if ipython0 is None:
86 93 # Instanciate an IPython0 interpreter to be able to use the
87 94 # prefiltering.
88 95 # Suppress all key input, to avoid waiting
89 96 def my_rawinput(x=None):
90 97 return '\n'
91 98 old_rawinput = __builtin__.raw_input
92 99 __builtin__.raw_input = my_rawinput
93 100 # XXX: argv=[] is a bit bold.
94 101 ipython0 = make_IPython(argv=[],
95 102 user_ns=self.shell.user_ns,
96 103 user_global_ns=self.shell.user_global_ns)
97 104 __builtin__.raw_input = old_rawinput
98 105 self.ipython0 = ipython0
99 106 # Set the pager:
100 107 self.ipython0.set_hook('show_in_pager',
101 108 lambda s, string: self.write("\n" + string))
102 109 self.ipython0.write = self.write
103 110 self._ip = _ip = IPApi(self.ipython0)
104 111 # Make sure the raw system call doesn't get called, as we don't
105 112 # have a stdin accessible.
106 113 self._ip.system = self.system_call
107 114 # XXX: Muck around with magics so that they work better
108 115 # in our environment
109 116 if not sys.platform.startswith('win'):
110 117 self.ipython0.magic_ls = mk_system_call(self.system_call,
111 118 'ls -CF')
112 119 # And now clean up the mess created by ipython0
113 120 self.release_output()
114 121
115 122
116 123 if not 'banner' in kwargs and self.banner is None:
117 124 self.banner = self.ipython0.BANNER
118 125
119 126 # FIXME: __init__ and start should be two different steps
120 127 self.start()
121 128
122 129 #--------------------------------------------------------------------------
123 130 # FrontEndBase interface
124 131 #--------------------------------------------------------------------------
125 132
126 133 def show_traceback(self):
127 134 """ Use ipython0 to capture the last traceback and display it.
128 135 """
129 136 # Don't do the capture; the except_hook has already done some
130 137 # modifications to the IO streams, if we store them, we'll be
131 138 # storing the wrong ones.
132 139 #self.capture_output()
133 140 self.ipython0.showtraceback(tb_offset=-1)
134 141 self.release_output()
135 142
136 143
137 144 def execute(self, python_string, raw_string=None):
138 145 if self.debug:
139 146 print 'Executing Python code:', repr(python_string)
140 147 self.capture_output()
141 148 LineFrontEndBase.execute(self, python_string,
142 149 raw_string=raw_string)
143 150 self.release_output()
144 151
145 152
146 153 def save_output_hooks(self):
147 154 """ Store all the output hooks we can think of, to be able to
148 155 restore them.
149 156
150 157 We need to do this early, as starting the ipython0 instance will
151 158 screw ouput hooks.
152 159 """
153 160 self.__old_cout_write = Term.cout.write
154 161 self.__old_cerr_write = Term.cerr.write
155 162 self.__old_stdout = sys.stdout
156 163 self.__old_stderr= sys.stderr
157 164 self.__old_help_output = pydoc.help.output
158 165 self.__old_display_hook = sys.displayhook
159 166
160 167
161 168 def capture_output(self):
162 169 """ Capture all the output mechanisms we can think of.
163 170 """
164 171 self.save_output_hooks()
165 172 Term.cout.write = self.write
166 173 Term.cerr.write = self.write
167 174 sys.stdout = Term.cout
168 175 sys.stderr = Term.cerr
169 176 pydoc.help.output = self.shell.output_trap.out
170 177
171 178
172 179 def release_output(self):
173 180 """ Release all the different captures we have made.
174 181 """
175 182 Term.cout.write = self.__old_cout_write
176 183 Term.cerr.write = self.__old_cerr_write
177 184 sys.stdout = self.__old_stdout
178 185 sys.stderr = self.__old_stderr
179 186 pydoc.help.output = self.__old_help_output
180 187 sys.displayhook = self.__old_display_hook
181 188
182 189
183 190 def complete(self, line):
184 191 # FIXME: This should be factored out in the linefrontendbase
185 192 # method.
186 193 word = self._get_completion_text(line)
187 word = line.split('\n')[-1].split(' ')[-1]
188 194 print 'Completion', word
189 195 completions = self.ipython0.complete(word)
190 196 # FIXME: The proper sort should be done in the complete method.
191 197 key = lambda x: x.replace('_', '')
192 198 completions.sort(key=key)
193 199 if completions:
194 200 prefix = common_prefix(completions)
195 201 line = line[:-len(word)] + prefix
196 202 return line, completions
197 203
198 204
199 205 #--------------------------------------------------------------------------
200 206 # LineFrontEndBase interface
201 207 #--------------------------------------------------------------------------
202 208
203 209 def prefilter_input(self, input_string):
204 210 """ Using IPython0 to prefilter the commands to turn them
205 211 in executable statements that are valid Python strings.
206 212 """
207 213 input_string = LineFrontEndBase.prefilter_input(self, input_string)
208 214 filtered_lines = []
209 215 # The IPython0 prefilters sometime produce output. We need to
210 216 # capture it.
211 217 self.capture_output()
212 218 self.last_result = dict(number=self.prompt_number)
213 219
214 220 ## try:
215 221 ## for line in input_string.split('\n'):
216 222 ## filtered_lines.append(
217 223 ## self.ipython0.prefilter(line, False).rstrip())
218 224 ## except:
219 225 ## # XXX: probably not the right thing to do.
220 226 ## self.ipython0.showsyntaxerror()
221 227 ## self.after_execute()
222 228 ## finally:
223 229 ## self.release_output()
224 230
225 231
226 232 try:
227 233 try:
228 234 for line in input_string.split('\n'):
229 235 filtered_lines.append(
230 236 self.ipython0.prefilter(line, False).rstrip())
231 237 except:
232 238 # XXX: probably not the right thing to do.
233 239 self.ipython0.showsyntaxerror()
234 240 self.after_execute()
235 241 finally:
236 242 self.release_output()
237 243
238 244
239 245
240 246 # Clean up the trailing whitespace, to avoid indentation errors
241 247 filtered_string = '\n'.join(filtered_lines)
242 248 return filtered_string
243 249
244 250
245 251 #--------------------------------------------------------------------------
246 252 # PrefilterFrontEnd interface
247 253 #--------------------------------------------------------------------------
248 254
249 255 def system_call(self, command_string):
250 256 """ Allows for frontend to define their own system call, to be
251 257 able capture output and redirect input.
252 258 """
253 259 return os.system(command_string)
254 260
255 261
256 262 def do_exit(self):
257 263 """ Exit the shell, cleanup and save the history.
258 264 """
259 265 self.ipython0.atexit_operations()
260 266
261 267
262 268 def _get_completion_text(self, line):
263 269 """ Returns the text to be completed by breaking the line at specified
264 270 delimiters.
265 271 """
266 272 # Break at: spaces, '=', all parentheses (except if balanced).
267 273 # FIXME2: In the future, we need to make the implementation similar to
268 274 # that in the 'pyreadline' module (modes/basemode.py) where we break at
269 275 # each delimiter and try to complete the residual line, until we get a
270 276 # successful list of completions.
271 277 expression = '\s|=|,|:|\((?!.*\))|\[(?!.*\])|\{(?!.*\})'
272 278 complete_sep = re.compile(expression)
273 279 text = complete_sep.split(line)[-1]
274 280 return text
275 281
@@ -1,180 +1,244 b''
1 1 # encoding: utf-8
2 2 """
3 3 Test process execution and IO redirection.
4 4 """
5 5
6 6 __docformat__ = "restructuredtext en"
7 7
8 8 #-------------------------------------------------------------------------------
9 9 # Copyright (C) 2008 The IPython Development Team
10 10 #
11 11 # Distributed under the terms of the BSD License. The full license is
12 12 # in the file COPYING, distributed as part of this software.
13 13 #-------------------------------------------------------------------------------
14 14
15 15 from cStringIO import StringIO
16 16 import string
17 17
18 from nose.tools import assert_equal
19
18 20 from IPython.ipapi import get as get_ipython0
19 21 from IPython.frontend.prefilterfrontend import PrefilterFrontEnd
20 from copy import deepcopy
22 from copy import copy, deepcopy
23
24 def safe_deepcopy(d):
25 """ Deep copy every key of the given dict, when possible. Elsewhere
26 do a copy.
27 """
28 copied_d = dict()
29 for key, value in d.iteritems():
30 try:
31 copied_d[key] = deepcopy(value)
32 except:
33 try:
34 copied_d[key] = copy(value)
35 except:
36 copied_d[key] = value
37 return copied_d
38
21 39
22 40 class TestPrefilterFrontEnd(PrefilterFrontEnd):
23 41
24 42 input_prompt_template = string.Template('')
25 43 output_prompt_template = string.Template('')
26 44 banner = ''
27 45
28 46 def __init__(self):
29 ipython0 = get_ipython0().IP
30 47 self.out = StringIO()
31 PrefilterFrontEnd.__init__(self, ipython0=ipython0)
32 # Clean up the namespace for isolation between tests
33 user_ns = self.ipython0.user_ns
34 # We need to keep references to things so that they don't
35 # get garbage collected (this stinks).
36 self.shadow_ns = dict()
37 for i in self.ipython0.magic_who_ls():
38 self.shadow_ns[i] = user_ns.pop(i)
48 PrefilterFrontEnd.__init__(self)
39 49 # Some more code for isolation (yeah, crazy)
40 50 self._on_enter()
41 51 self.out.flush()
42 52 self.out.reset()
43 53 self.out.truncate()
44 54
45 55 def write(self, string, *args, **kwargs):
46 56 self.out.write(string)
47 57
48 58 def _on_enter(self):
49 59 self.input_buffer += '\n'
50 60 PrefilterFrontEnd._on_enter(self)
51 61
52 62
53 63 def isolate_ipython0(func):
54 64 """ Decorator to isolate execution that involves an iptyhon0.
65
66 Notes
67 ------
68
69 Apply only to functions with no arguments. Nose skips functions
70 with arguments.
55 71 """
56 def my_func(*args, **kwargs):
57 ipython0 = get_ipython0().IP
58 user_ns = deepcopy(ipython0.user_ns)
59 global_ns = deepcopy(ipython0.global_ns)
72 def my_func():
73 iplib = get_ipython0()
74 if iplib is None:
75 return func()
76 ipython0 = iplib.IP
77 global_ns = safe_deepcopy(ipython0.user_global_ns)
78 user_ns = safe_deepcopy(ipython0.user_ns)
60 79 try:
61 func(*args, **kwargs)
80 out = func()
62 81 finally:
63 82 ipython0.user_ns = user_ns
64 ipython0.global_ns = global_ns
83 ipython0.user_global_ns = global_ns
84 # Undo the hack at creation of PrefilterFrontEnd
85 from IPython import iplib
86 iplib.InteractiveShell.isthreaded = False
87 return out
65 88
89 my_func.__name__ = func.__name__
66 90 return my_func
67 91
68 92
69 93 @isolate_ipython0
70 94 def test_execution():
71 95 """ Test execution of a command.
72 96 """
73 97 f = TestPrefilterFrontEnd()
74 98 f.input_buffer = 'print 1'
75 99 f._on_enter()
76 100 out_value = f.out.getvalue()
77 assert out_value == '1\n'
101 assert_equal(out_value, '1\n')
78 102
79 103
80 104 @isolate_ipython0
81 105 def test_multiline():
82 106 """ Test execution of a multiline command.
83 107 """
84 108 f = TestPrefilterFrontEnd()
85 109 f.input_buffer = 'if True:'
86 110 f._on_enter()
87 111 f.input_buffer += 'print 1'
88 112 f._on_enter()
89 113 out_value = f.out.getvalue()
90 assert out_value == ''
114 assert_equal(out_value, '')
91 115 f._on_enter()
92 116 out_value = f.out.getvalue()
93 assert out_value == '1\n'
117 assert_equal(out_value, '1\n')
94 118 f = TestPrefilterFrontEnd()
95 119 f.input_buffer='(1 +'
96 120 f._on_enter()
97 121 f.input_buffer += '0)'
98 122 f._on_enter()
99 123 out_value = f.out.getvalue()
100 assert out_value == ''
124 assert_equal(out_value, '')
101 125 f._on_enter()
102 126 out_value = f.out.getvalue()
103 assert out_value == '1\n'
127 assert_equal(out_value, '1\n')
104 128
105 129
106 130 @isolate_ipython0
107 131 def test_capture():
108 132 """ Test the capture of output in different channels.
109 133 """
110 134 # Test on the OS-level stdout, stderr.
111 135 f = TestPrefilterFrontEnd()
112 136 f.input_buffer = \
113 137 'import os; out=os.fdopen(1, "w"); out.write("1") ; out.flush()'
114 138 f._on_enter()
115 139 out_value = f.out.getvalue()
116 assert out_value == '1'
140 assert_equal(out_value, '1')
117 141 f = TestPrefilterFrontEnd()
118 142 f.input_buffer = \
119 143 'import os; out=os.fdopen(2, "w"); out.write("1") ; out.flush()'
120 144 f._on_enter()
121 145 out_value = f.out.getvalue()
122 assert out_value == '1'
146 assert_equal(out_value, '1')
123 147
124 148
125 149 @isolate_ipython0
126 150 def test_magic():
127 151 """ Test the magic expansion and history.
128 152
129 153 This test is fairly fragile and will break when magics change.
130 154 """
131 155 f = TestPrefilterFrontEnd()
132 156 f.input_buffer += '%who'
133 157 f._on_enter()
134 158 out_value = f.out.getvalue()
135 assert out_value == 'Interactive namespace is empty.\n'
159 assert_equal(out_value, 'Interactive namespace is empty.\n')
136 160
137 161
138 162 @isolate_ipython0
139 163 def test_help():
140 164 """ Test object inspection.
141 165 """
142 166 f = TestPrefilterFrontEnd()
143 167 f.input_buffer += "def f():"
144 168 f._on_enter()
145 169 f.input_buffer += "'foobar'"
146 170 f._on_enter()
147 171 f.input_buffer += "pass"
148 172 f._on_enter()
149 173 f._on_enter()
150 174 f.input_buffer += "f?"
151 175 f._on_enter()
152 176 assert 'traceback' not in f.last_result
153 177 ## XXX: ipython doctest magic breaks this. I have no clue why
154 178 #out_value = f.out.getvalue()
155 179 #assert out_value.split()[-1] == 'foobar'
156 180
157 181
158 182 @isolate_ipython0
159 def test_completion():
160 """ Test command-line completion.
183 def test_completion_simple():
184 """ Test command-line completion on trivial examples.
161 185 """
162 186 f = TestPrefilterFrontEnd()
163 187 f.input_buffer = 'zzza = 1'
164 188 f._on_enter()
165 189 f.input_buffer = 'zzzb = 2'
166 190 f._on_enter()
167 191 f.input_buffer = 'zz'
168 192 f.complete_current_input()
169 193 out_value = f.out.getvalue()
170 assert out_value == '\nzzza zzzb '
171 assert f.input_buffer == 'zzz'
194 assert_equal(out_value, '\nzzza zzzb ')
195 assert_equal(f.input_buffer, 'zzz')
196
197
198 @isolate_ipython0
199 def test_completion_parenthesis():
200 """ Test command-line completion when a parenthesis is open.
201 """
202 f = TestPrefilterFrontEnd()
203 f.input_buffer = 'zzza = 1'
204 f._on_enter()
205 f.input_buffer = 'zzzb = 2'
206 f._on_enter()
207 f.input_buffer = 'map(zz'
208 f.complete_current_input()
209 out_value = f.out.getvalue()
210 assert_equal(out_value, '\nzzza zzzb ')
211 assert_equal(f.input_buffer, 'map(zzz')
212
213
214 @isolate_ipython0
215 def test_completion_indexing():
216 """ Test command-line completion when indexing on objects.
217 """
218 f = TestPrefilterFrontEnd()
219 f.input_buffer = 'a = [0]'
220 f._on_enter()
221 f.input_buffer = 'a[0].'
222 f.complete_current_input()
223 assert_equal(f.input_buffer, 'a[0].__')
224
225
226 @isolate_ipython0
227 def test_completion_equal():
228 """ Test command-line completion when the delimiter is "=", not " ".
229 """
230 f = TestPrefilterFrontEnd()
231 f.input_buffer = 'a=1.'
232 f.complete_current_input()
233 assert_equal(f.input_buffer, 'a=1.__')
234
172 235
173 236
174 237 if __name__ == '__main__':
175 238 test_magic()
176 239 test_help()
177 240 test_execution()
178 241 test_multiline()
179 242 test_capture()
180 test_completion()
243 test_completion_simple()
244 test_completion_complex()
@@ -1,564 +1,561 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 import string
31 31
32 32 import wx
33 33 from wx import stc
34 34
35 35 # Ipython-specific imports.
36 36 from IPython.frontend._process import PipedProcess
37 37 from console_widget import ConsoleWidget
38 38 from IPython.frontend.prefilterfrontend import PrefilterFrontEnd
39 39
40 40 #-------------------------------------------------------------------------------
41 41 # Constants
42 42 #-------------------------------------------------------------------------------
43 43
44 44 _COMPLETE_BUFFER_BG = '#FAFAF1' # Nice green
45 45 _INPUT_BUFFER_BG = '#FDFFD3' # Nice yellow
46 46 _ERROR_BG = '#FFF1F1' # Nice red
47 47
48 48 _COMPLETE_BUFFER_MARKER = 31
49 49 _ERROR_MARKER = 30
50 50 _INPUT_MARKER = 29
51 51
52 52 prompt_in1 = \
53 53 '\n\x01\x1b[0;34m\x02In [\x01\x1b[1;34m\x02$number\x01\x1b[0;34m\x02]: \x01\x1b[0m\x02'
54 54
55 55 prompt_out = \
56 56 '\x01\x1b[0;31m\x02Out[\x01\x1b[1;31m\x02$number\x01\x1b[0;31m\x02]: \x01\x1b[0m\x02'
57 57
58 58 #-------------------------------------------------------------------------------
59 59 # Classes to implement the Wx frontend
60 60 #-------------------------------------------------------------------------------
61 61 class WxController(ConsoleWidget, PrefilterFrontEnd):
62 62 """Classes to provide a Wx frontend to the
63 63 IPython.kernel.core.interpreter.
64 64
65 65 This class inherits from ConsoleWidget, that provides a console-like
66 66 widget to provide a text-rendering widget suitable for a terminal.
67 67 """
68 68
69 69 output_prompt_template = string.Template(prompt_out)
70 70
71 71 input_prompt_template = string.Template(prompt_in1)
72 72
73 73 # Print debug info on what is happening to the console.
74 74 debug = False
75 75
76 76 # The title of the terminal, as captured through the ANSI escape
77 77 # sequences.
78 78 def _set_title(self, title):
79 79 return self.Parent.SetTitle(title)
80 80
81 81 def _get_title(self):
82 82 return self.Parent.GetTitle()
83 83
84 84 title = property(_get_title, _set_title)
85 85
86 86
87 87 # The buffer being edited.
88 88 # We are duplicating the definition here because of multiple
89 89 # inheritence
90 90 def _set_input_buffer(self, string):
91 91 ConsoleWidget._set_input_buffer(self, string)
92 92 self._colorize_input_buffer()
93 93
94 94 def _get_input_buffer(self):
95 95 """ Returns the text in current edit buffer.
96 96 """
97 97 return ConsoleWidget._get_input_buffer(self)
98 98
99 99 input_buffer = property(_get_input_buffer, _set_input_buffer)
100 100
101 101
102 102 #--------------------------------------------------------------------------
103 103 # Private Attributes
104 104 #--------------------------------------------------------------------------
105 105
106 106 # A flag governing the behavior of the input. Can be:
107 107 #
108 108 # 'readline' for readline-like behavior with a prompt
109 109 # and an edit buffer.
110 110 # 'raw_input' similar to readline, but triggered by a raw-input
111 111 # call. Can be used by subclasses to act differently.
112 112 # 'subprocess' for sending the raw input directly to a
113 113 # subprocess.
114 114 # 'buffering' for buffering of the input, that will be used
115 115 # when the input state switches back to another state.
116 116 _input_state = 'readline'
117 117
118 118 # Attribute to store reference to the pipes of a subprocess, if we
119 119 # are running any.
120 120 _running_process = False
121 121
122 122 # A queue for writing fast streams to the screen without flooding the
123 123 # event loop
124 124 _out_buffer = []
125 125
126 126 # A lock to lock the _out_buffer to make sure we don't empty it
127 127 # while it is being swapped
128 128 _out_buffer_lock = Lock()
129 129
130 130 # The different line markers used to higlight the prompts.
131 131 _markers = dict()
132 132
133 133 #--------------------------------------------------------------------------
134 134 # Public API
135 135 #--------------------------------------------------------------------------
136 136
137 137 def __init__(self, parent, id=wx.ID_ANY, pos=wx.DefaultPosition,
138 138 size=wx.DefaultSize,
139 139 style=wx.CLIP_CHILDREN|wx.WANTS_CHARS,
140 140 *args, **kwds):
141 141 """ Create Shell instance.
142 142 """
143 143 ConsoleWidget.__init__(self, parent, id, pos, size, style)
144 144 PrefilterFrontEnd.__init__(self, **kwds)
145 145
146 146 # Stick in our own raw_input:
147 147 self.ipython0.raw_input = self.raw_input
148 148
149 149 # Marker for complete buffer.
150 150 self.MarkerDefine(_COMPLETE_BUFFER_MARKER, stc.STC_MARK_BACKGROUND,
151 151 background=_COMPLETE_BUFFER_BG)
152 152 # Marker for current input buffer.
153 153 self.MarkerDefine(_INPUT_MARKER, stc.STC_MARK_BACKGROUND,
154 154 background=_INPUT_BUFFER_BG)
155 155 # Marker for tracebacks.
156 156 self.MarkerDefine(_ERROR_MARKER, stc.STC_MARK_BACKGROUND,
157 157 background=_ERROR_BG)
158 158
159 159 # A time for flushing the write buffer
160 160 BUFFER_FLUSH_TIMER_ID = 100
161 161 self._buffer_flush_timer = wx.Timer(self, BUFFER_FLUSH_TIMER_ID)
162 162 wx.EVT_TIMER(self, BUFFER_FLUSH_TIMER_ID, self._buffer_flush)
163 163
164 164 if 'debug' in kwds:
165 165 self.debug = kwds['debug']
166 166 kwds.pop('debug')
167 167
168 168 # Inject self in namespace, for debug
169 169 if self.debug:
170 170 self.shell.user_ns['self'] = self
171 171 # Inject our own raw_input in namespace
172 172 self.shell.user_ns['raw_input'] = self.raw_input
173 173
174 174
175 175 def raw_input(self, prompt=''):
176 176 """ A replacement from python's raw_input.
177 177 """
178 178 self.new_prompt(prompt)
179 179 self._input_state = 'raw_input'
180 180 if hasattr(self, '_cursor'):
181 181 del self._cursor
182 182 self.SetCursor(wx.StockCursor(wx.CURSOR_CROSS))
183 183 self.__old_on_enter = self._on_enter
184 184 event_loop = wx.EventLoop()
185 185 def my_on_enter():
186 186 event_loop.Exit()
187 187 self._on_enter = my_on_enter
188 188 # XXX: Running a separate event_loop. Ugly.
189 189 event_loop.Run()
190 190 self._on_enter = self.__old_on_enter
191 191 self._input_state = 'buffering'
192 192 self._cursor = wx.BusyCursor()
193 193 return self.input_buffer.rstrip('\n')
194 194
195 195
196 196 def system_call(self, command_string):
197 197 self._input_state = 'subprocess'
198 198 event_loop = wx.EventLoop()
199 199 def _end_system_call():
200 200 self._input_state = 'buffering'
201 201 self._running_process = False
202 202 event_loop.Exit()
203 203
204 204 self._running_process = PipedProcess(command_string,
205 205 out_callback=self.buffered_write,
206 206 end_callback = _end_system_call)
207 207 self._running_process.start()
208 208 # XXX: Running a separate event_loop. Ugly.
209 209 event_loop.Run()
210 210 # Be sure to flush the buffer.
211 211 self._buffer_flush(event=None)
212 212
213 213
214 214 def do_calltip(self):
215 215 """ Analyse current and displays useful calltip for it.
216 216 """
217 217 if self.debug:
218 218 print >>sys.__stdout__, "do_calltip"
219 219 separators = re.compile('[\s\{\}\[\]\(\)\= ,:]')
220 220 symbol = self.input_buffer
221 221 symbol_string = separators.split(symbol)[-1]
222 222 base_symbol_string = symbol_string.split('.')[0]
223 223 if base_symbol_string in self.shell.user_ns:
224 224 symbol = self.shell.user_ns[base_symbol_string]
225 225 elif base_symbol_string in self.shell.user_global_ns:
226 226 symbol = self.shell.user_global_ns[base_symbol_string]
227 227 elif base_symbol_string in __builtin__.__dict__:
228 228 symbol = __builtin__.__dict__[base_symbol_string]
229 229 else:
230 230 return False
231 231 try:
232 232 for name in symbol_string.split('.')[1:] + ['__doc__']:
233 233 symbol = getattr(symbol, name)
234 234 self.AutoCompCancel()
235 235 # Check that the symbol can indeed be converted to a string:
236 236 symbol += ''
237 237 wx.CallAfter(self.CallTipShow, self.GetCurrentPos(), symbol)
238 238 except:
239 239 # The retrieve symbol couldn't be converted to a string
240 240 pass
241 241
242 242
243 243 def _popup_completion(self, create=False):
244 244 """ Updates the popup completion menu if it exists. If create is
245 245 true, open the menu.
246 246 """
247 247 if self.debug:
248 248 print >>sys.__stdout__, "_popup_completion"
249 249 line = self.input_buffer
250 250 if (self.AutoCompActive() and line and not line[-1] == '.') \
251 251 or create==True:
252 252 suggestion, completions = self.complete(line)
253 offset=0
254 253 if completions:
255 complete_sep = re.compile('[\s\{\}\[\]\(\)\= ,:]')
256 residual = complete_sep.split(line)[-1]
257 offset = len(residual)
254 offset = len(self._get_completion_text(line))
258 255 self.pop_completion(completions, offset=offset)
259 256 if self.debug:
260 257 print >>sys.__stdout__, completions
261 258
262 259
263 260 def buffered_write(self, text):
264 261 """ A write method for streams, that caches the stream in order
265 262 to avoid flooding the event loop.
266 263
267 264 This can be called outside of the main loop, in separate
268 265 threads.
269 266 """
270 267 self._out_buffer_lock.acquire()
271 268 self._out_buffer.append(text)
272 269 self._out_buffer_lock.release()
273 270 if not self._buffer_flush_timer.IsRunning():
274 271 wx.CallAfter(self._buffer_flush_timer.Start,
275 272 milliseconds=100, oneShot=True)
276 273
277 274
278 275 #--------------------------------------------------------------------------
279 276 # LineFrontEnd interface
280 277 #--------------------------------------------------------------------------
281 278
282 279 def execute(self, python_string, raw_string=None):
283 280 self._input_state = 'buffering'
284 281 self.CallTipCancel()
285 282 self._cursor = wx.BusyCursor()
286 283 if raw_string is None:
287 284 raw_string = python_string
288 285 end_line = self.current_prompt_line \
289 286 + max(1, len(raw_string.split('\n'))-1)
290 287 for i in range(self.current_prompt_line, end_line):
291 288 if i in self._markers:
292 289 self.MarkerDeleteHandle(self._markers[i])
293 290 self._markers[i] = self.MarkerAdd(i, _COMPLETE_BUFFER_MARKER)
294 291 # Use a callafter to update the display robustly under windows
295 292 def callback():
296 293 self.GotoPos(self.GetLength())
297 294 PrefilterFrontEnd.execute(self, python_string,
298 295 raw_string=raw_string)
299 296 wx.CallAfter(callback)
300 297
301 298
302 299 def execute_command(self, command, hidden=False):
303 300 """ Execute a command, not only in the model, but also in the
304 301 view.
305 302 """
306 303 if hidden:
307 304 return self.shell.execute(command)
308 305 else:
309 306 # XXX: we are not storing the input buffer previous to the
310 307 # execution, as this forces us to run the execution
311 308 # input_buffer a yield, which is not good.
312 309 ##current_buffer = self.shell.control.input_buffer
313 310 command = command.rstrip()
314 311 if len(command.split('\n')) > 1:
315 312 # The input command is several lines long, we need to
316 313 # force the execution to happen
317 314 command += '\n'
318 315 cleaned_command = self.prefilter_input(command)
319 316 self.input_buffer = command
320 317 # Do not use wx.Yield() (aka GUI.process_events()) to avoid
321 318 # recursive yields.
322 319 self.ProcessEvent(wx.PaintEvent())
323 320 self.write('\n')
324 321 if not self.is_complete(cleaned_command + '\n'):
325 322 self._colorize_input_buffer()
326 323 self.render_error('Incomplete or invalid input')
327 324 self.new_prompt(self.input_prompt_template.substitute(
328 325 number=(self.last_result['number'] + 1)))
329 326 return False
330 327 self._on_enter()
331 328 return True
332 329
333 330
334 331 def save_output_hooks(self):
335 332 self.__old_raw_input = __builtin__.raw_input
336 333 PrefilterFrontEnd.save_output_hooks(self)
337 334
338 335 def capture_output(self):
339 336 self.SetLexer(stc.STC_LEX_NULL)
340 337 PrefilterFrontEnd.capture_output(self)
341 338 __builtin__.raw_input = self.raw_input
342 339
343 340
344 341 def release_output(self):
345 342 __builtin__.raw_input = self.__old_raw_input
346 343 PrefilterFrontEnd.release_output(self)
347 344 self.SetLexer(stc.STC_LEX_PYTHON)
348 345
349 346
350 347 def after_execute(self):
351 348 PrefilterFrontEnd.after_execute(self)
352 349 # Clear the wait cursor
353 350 if hasattr(self, '_cursor'):
354 351 del self._cursor
355 352 self.SetCursor(wx.StockCursor(wx.CURSOR_CHAR))
356 353
357 354
358 355 def show_traceback(self):
359 356 start_line = self.GetCurrentLine()
360 357 PrefilterFrontEnd.show_traceback(self)
361 358 self.ProcessEvent(wx.PaintEvent())
362 359 #wx.Yield()
363 360 for i in range(start_line, self.GetCurrentLine()):
364 361 self._markers[i] = self.MarkerAdd(i, _ERROR_MARKER)
365 362
366 363
367 364 #--------------------------------------------------------------------------
368 365 # FrontEndBase interface
369 366 #--------------------------------------------------------------------------
370 367
371 368 def render_error(self, e):
372 369 start_line = self.GetCurrentLine()
373 370 self.write('\n' + e + '\n')
374 371 for i in range(start_line, self.GetCurrentLine()):
375 372 self._markers[i] = self.MarkerAdd(i, _ERROR_MARKER)
376 373
377 374
378 375 #--------------------------------------------------------------------------
379 376 # ConsoleWidget interface
380 377 #--------------------------------------------------------------------------
381 378
382 379 def new_prompt(self, prompt):
383 380 """ Display a new prompt, and start a new input buffer.
384 381 """
385 382 self._input_state = 'readline'
386 383 ConsoleWidget.new_prompt(self, prompt)
387 384 i = self.current_prompt_line
388 385 self._markers[i] = self.MarkerAdd(i, _INPUT_MARKER)
389 386
390 387
391 388 def write(self, *args, **kwargs):
392 389 # Avoid multiple inheritence, be explicit about which
393 390 # parent method class gets called
394 391 ConsoleWidget.write(self, *args, **kwargs)
395 392
396 393
397 394 def _on_key_down(self, event, skip=True):
398 395 """ Capture the character events, let the parent
399 396 widget handle them, and put our logic afterward.
400 397 """
401 398 # FIXME: This method needs to be broken down in smaller ones.
402 399 current_line_number = self.GetCurrentLine()
403 400 if event.KeyCode in (ord('c'), ord('C')) and event.ControlDown():
404 401 # Capture Control-C
405 402 if self._input_state == 'subprocess':
406 403 if self.debug:
407 404 print >>sys.__stderr__, 'Killing running process'
408 405 if hasattr(self._running_process, 'process'):
409 406 self._running_process.process.kill()
410 407 elif self._input_state == 'buffering':
411 408 if self.debug:
412 409 print >>sys.__stderr__, 'Raising KeyboardInterrupt'
413 410 raise KeyboardInterrupt
414 411 # XXX: We need to make really sure we
415 412 # get back to a prompt.
416 413 elif self._input_state == 'subprocess' and (
417 414 ( event.KeyCode<256 and
418 415 not event.ControlDown() )
419 416 or
420 417 ( event.KeyCode in (ord('d'), ord('D')) and
421 418 event.ControlDown())):
422 419 # We are running a process, we redirect keys.
423 420 ConsoleWidget._on_key_down(self, event, skip=skip)
424 421 char = chr(event.KeyCode)
425 422 # Deal with some inconsistency in wx keycodes:
426 423 if char == '\r':
427 424 char = '\n'
428 425 elif not event.ShiftDown():
429 426 char = char.lower()
430 427 if event.ControlDown() and event.KeyCode in (ord('d'), ord('D')):
431 428 char = '\04'
432 429 self._running_process.process.stdin.write(char)
433 430 self._running_process.process.stdin.flush()
434 431 elif event.KeyCode in (ord('('), 57, 53):
435 432 # Calltips
436 433 event.Skip()
437 434 self.do_calltip()
438 435 elif self.AutoCompActive() and not event.KeyCode == ord('\t'):
439 436 event.Skip()
440 437 if event.KeyCode in (wx.WXK_BACK, wx.WXK_DELETE):
441 438 wx.CallAfter(self._popup_completion, create=True)
442 439 elif not event.KeyCode in (wx.WXK_UP, wx.WXK_DOWN, wx.WXK_LEFT,
443 440 wx.WXK_RIGHT, wx.WXK_ESCAPE):
444 441 wx.CallAfter(self._popup_completion)
445 442 else:
446 443 # Up history
447 444 if event.KeyCode == wx.WXK_UP and (
448 445 ( current_line_number == self.current_prompt_line and
449 446 event.Modifiers in (wx.MOD_NONE, wx.MOD_WIN) )
450 447 or event.ControlDown() ):
451 448 new_buffer = self.get_history_previous(
452 449 self.input_buffer)
453 450 if new_buffer is not None:
454 451 self.input_buffer = new_buffer
455 452 if self.GetCurrentLine() > self.current_prompt_line:
456 453 # Go to first line, for seemless history up.
457 454 self.GotoPos(self.current_prompt_pos)
458 455 # Down history
459 456 elif event.KeyCode == wx.WXK_DOWN and (
460 457 ( current_line_number == self.LineCount -1 and
461 458 event.Modifiers in (wx.MOD_NONE, wx.MOD_WIN) )
462 459 or event.ControlDown() ):
463 460 new_buffer = self.get_history_next()
464 461 if new_buffer is not None:
465 462 self.input_buffer = new_buffer
466 463 # Tab-completion
467 464 elif event.KeyCode == ord('\t'):
468 465 current_line, current_line_number = self.CurLine
469 466 if not re.match(r'^\s*$', current_line):
470 467 self.complete_current_input()
471 468 if self.AutoCompActive():
472 469 wx.CallAfter(self._popup_completion, create=True)
473 470 else:
474 471 event.Skip()
475 472 else:
476 473 ConsoleWidget._on_key_down(self, event, skip=skip)
477 474
478 475
479 476 def _on_key_up(self, event, skip=True):
480 477 """ Called when any key is released.
481 478 """
482 479 if event.KeyCode in (59, ord('.')):
483 480 # Intercepting '.'
484 481 event.Skip()
485 482 wx.CallAfter(self._popup_completion, create=True)
486 483 else:
487 484 ConsoleWidget._on_key_up(self, event, skip=skip)
488 485 if (self.input_buffer.split('\n')[-1] == self.continuation_prompt()
489 486 and self._input_state == 'readline'):
490 487 # Make sure the continuation_prompt is followed by a whitespace
491 488 position = self.GetCurrentPos()
492 489 self.input_buffer += ' '
493 490 self.GotoPos(position)
494 491
495 492
496 493 def _on_enter(self):
497 494 """ Called on return key down, in readline input_state.
498 495 """
499 496 if self.debug:
500 497 print >>sys.__stdout__, repr(self.input_buffer)
501 498 PrefilterFrontEnd._on_enter(self)
502 499
503 500
504 501 #--------------------------------------------------------------------------
505 502 # EditWindow API
506 503 #--------------------------------------------------------------------------
507 504
508 505 def OnUpdateUI(self, event):
509 506 """ Override the OnUpdateUI of the EditWindow class, to prevent
510 507 syntax highlighting both for faster redraw, and for more
511 508 consistent look and feel.
512 509 """
513 510 if not self._input_state == 'readline':
514 511 ConsoleWidget.OnUpdateUI(self, event)
515 512
516 513 #--------------------------------------------------------------------------
517 514 # Private API
518 515 #--------------------------------------------------------------------------
519 516
520 517 def _buffer_flush(self, event):
521 518 """ Called by the timer to flush the write buffer.
522 519
523 520 This is always called in the mainloop, by the wx timer.
524 521 """
525 522 self._out_buffer_lock.acquire()
526 523 _out_buffer = self._out_buffer
527 524 self._out_buffer = []
528 525 self._out_buffer_lock.release()
529 526 self.write(''.join(_out_buffer), refresh=False)
530 527
531 528
532 529 def _colorize_input_buffer(self):
533 530 """ Keep the input buffer lines at a bright color.
534 531 """
535 532 if not self._input_state in ('readline', 'raw_input'):
536 533 return
537 534 end_line = self.GetCurrentLine()
538 535 if not sys.platform == 'win32':
539 536 end_line += 1
540 537 for i in range(self.current_prompt_line, end_line):
541 538 if i in self._markers:
542 539 self.MarkerDeleteHandle(self._markers[i])
543 540 self._markers[i] = self.MarkerAdd(i, _INPUT_MARKER)
544 541
545 542
546 543 if __name__ == '__main__':
547 544 class MainWindow(wx.Frame):
548 545 def __init__(self, parent, id, title):
549 546 wx.Frame.__init__(self, parent, id, title, size=(300,250))
550 547 self._sizer = wx.BoxSizer(wx.VERTICAL)
551 548 self.shell = WxController(self)
552 549 self._sizer.Add(self.shell, 1, wx.EXPAND)
553 550 self.SetSizer(self._sizer)
554 551 self.SetAutoLayout(1)
555 552 self.Show(True)
556 553
557 554 app = wx.PySimpleApp()
558 555 frame = MainWindow(None, wx.ID_ANY, 'Ipython')
559 556 frame.shell.SetFocus()
560 557 frame.SetSize((680, 460))
561 558 self = frame.shell
562 559
563 560 app.MainLoop()
564 561
General Comments 0
You need to be logged in to leave comments. Login now