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