##// END OF EJS Templates
Test completion.
gvaroquaux -
Show More
@@ -1,277 +1,282 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 IPython
21 import IPython
22 import sys
22 import sys
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 #--------------------------------------------------------------------------
65 #--------------------------------------------------------------------------
66 # FrontEndBase interface
66 # FrontEndBase interface
67 #--------------------------------------------------------------------------
67 #--------------------------------------------------------------------------
68
68
69 def __init__(self, shell=None, history=None):
69 def __init__(self, shell=None, history=None):
70 if shell is None:
70 if shell is None:
71 shell = Interpreter()
71 shell = Interpreter()
72 FrontEndBase.__init__(self, shell=shell, history=history)
72 FrontEndBase.__init__(self, shell=shell, history=history)
73
73
74 self.new_prompt(self.input_prompt_template.substitute(number=1))
74 self.new_prompt(self.input_prompt_template.substitute(number=1))
75
75
76
76
77 def complete(self, line):
77 def complete(self, line):
78 """Complete line in engine's user_ns
78 """Complete line in engine's user_ns
79
79
80 Parameters
80 Parameters
81 ----------
81 ----------
82 line : string
82 line : string
83
83
84 Result
84 Result
85 ------
85 ------
86 The replacement for the line and the list of possible completions.
86 The replacement for the line and the list of possible completions.
87 """
87 """
88 completions = self.shell.complete(line)
88 completions = self.shell.complete(line)
89 complete_sep = re.compile('[\s\{\}\[\]\(\)\=]')
89 complete_sep = re.compile('[\s\{\}\[\]\(\)\=]')
90 if completions:
90 if completions:
91 prefix = common_prefix(completions)
91 prefix = common_prefix(completions)
92 residual = complete_sep.split(line)[:-1]
92 residual = complete_sep.split(line)[:-1]
93 line = line[:-len(residual)] + prefix
93 line = line[:-len(residual)] + prefix
94 return line, completions
94 return line, completions
95
95
96
96
97 def render_result(self, result):
97 def render_result(self, result):
98 """ Frontend-specific rendering of the result of a calculation
98 """ Frontend-specific rendering of the result of a calculation
99 that has been sent to an engine.
99 that has been sent to an engine.
100 """
100 """
101 if 'stdout' in result and result['stdout']:
101 if 'stdout' in result and result['stdout']:
102 self.write('\n' + result['stdout'])
102 self.write('\n' + result['stdout'])
103 if 'display' in result and result['display']:
103 if 'display' in result and result['display']:
104 self.write("%s%s\n" % (
104 self.write("%s%s\n" % (
105 self.output_prompt_template.substitute(
105 self.output_prompt_template.substitute(
106 number=result['number']),
106 number=result['number']),
107 result['display']['pprint']
107 result['display']['pprint']
108 ) )
108 ) )
109
109
110
110
111 def render_error(self, failure):
111 def render_error(self, failure):
112 """ Frontend-specific rendering of error.
112 """ Frontend-specific rendering of error.
113 """
113 """
114 self.write('\n\n'+str(failure)+'\n\n')
114 self.write('\n\n'+str(failure)+'\n\n')
115 return failure
115 return failure
116
116
117
117
118 def is_complete(self, string):
118 def is_complete(self, string):
119 """ Check if a string forms a complete, executable set of
119 """ Check if a string forms a complete, executable set of
120 commands.
120 commands.
121
121
122 For the line-oriented frontend, multi-line code is not executed
122 For the line-oriented frontend, multi-line code is not executed
123 as soon as it is complete: the users has to enter two line
123 as soon as it is complete: the users has to enter two line
124 returns.
124 returns.
125 """
125 """
126 if string in ('', '\n'):
126 if string in ('', '\n'):
127 # Prefiltering, eg through ipython0, may return an empty
127 # Prefiltering, eg through ipython0, may return an empty
128 # string although some operations have been accomplished. We
128 # string although some operations have been accomplished. We
129 # thus want to consider an empty string as a complete
129 # thus want to consider an empty string as a complete
130 # statement.
130 # statement.
131 return True
131 return True
132 elif ( len(self.input_buffer.split('\n'))>2
132 elif ( len(self.input_buffer.split('\n'))>2
133 and not re.findall(r"\n[\t ]*\n[\t ]*$", string)):
133 and not re.findall(r"\n[\t ]*\n[\t ]*$", string)):
134 return False
134 return False
135 else:
135 else:
136 # Add line returns here, to make sure that the statement is
136 # Add line returns here, to make sure that the statement is
137 # complete.
137 # complete.
138 return FrontEndBase.is_complete(self, string.rstrip() + '\n\n')
138 return FrontEndBase.is_complete(self, string.rstrip() + '\n\n')
139
139
140
140
141 def write(self, string):
141 def write(self, string):
142 """ Write some characters to the display.
142 """ Write some characters to the display.
143
143
144 Subclass should overide this method.
144 Subclass should overide this method.
145 """
145 """
146 print >>sys.__stderr__, string
146 print >>sys.__stderr__, string
147
147
148
148
149 def execute(self, python_string, raw_string=None):
149 def execute(self, python_string, raw_string=None):
150 """ Stores the raw_string in the history, and sends the
150 """ Stores the raw_string in the history, and sends the
151 python string to the interpreter.
151 python string to the interpreter.
152 """
152 """
153 if raw_string is None:
153 if raw_string is None:
154 raw_string = python_string
154 raw_string = python_string
155 # Create a false result, in case there is an exception
155 # Create a false result, in case there is an exception
156 self.last_result = dict(number=self.prompt_number)
156 self.last_result = dict(number=self.prompt_number)
157 try:
157 try:
158 self.history.input_cache[-1] = raw_string.rstrip()
158 self.history.input_cache[-1] = raw_string.rstrip()
159 result = self.shell.execute(python_string)
159 result = self.shell.execute(python_string)
160 self.last_result = result
160 self.last_result = result
161 self.render_result(result)
161 self.render_result(result)
162 except:
162 except:
163 self.show_traceback()
163 self.show_traceback()
164 finally:
164 finally:
165 self.after_execute()
165 self.after_execute()
166
166
167 #--------------------------------------------------------------------------
167 #--------------------------------------------------------------------------
168 # LineFrontEndBase interface
168 # LineFrontEndBase interface
169 #--------------------------------------------------------------------------
169 #--------------------------------------------------------------------------
170
170
171 def prefilter_input(self, string):
171 def prefilter_input(self, string):
172 """ Priflter the input to turn it in valid python.
172 """ Priflter the input to turn it in valid python.
173 """
173 """
174 string = string.replace('\r\n', '\n')
174 string = string.replace('\r\n', '\n')
175 string = string.replace('\t', 4*' ')
175 string = string.replace('\t', 4*' ')
176 # Clean the trailing whitespace
176 # Clean the trailing whitespace
177 string = '\n'.join(l.rstrip() for l in string.split('\n'))
177 string = '\n'.join(l.rstrip() for l in string.split('\n'))
178 return string
178 return string
179
179
180
180
181 def after_execute(self):
181 def after_execute(self):
182 """ All the operations required after an execution to put the
182 """ All the operations required after an execution to put the
183 terminal back in a shape where it is usable.
183 terminal back in a shape where it is usable.
184 """
184 """
185 self.prompt_number += 1
185 self.prompt_number += 1
186 self.new_prompt(self.input_prompt_template.substitute(
186 self.new_prompt(self.input_prompt_template.substitute(
187 number=(self.last_result['number'] + 1)))
187 number=(self.last_result['number'] + 1)))
188 # Start a new empty history entry
188 # Start a new empty history entry
189 self._add_history(None, '')
189 self._add_history(None, '')
190 self.history_cursor = len(self.history.input_cache) - 1
190 self.history_cursor = len(self.history.input_cache) - 1
191
191
192
192
193 def complete_current_input(self):
193 def complete_current_input(self):
194 """ Do code completion on current line.
194 """ Do code completion on current line.
195 """
195 """
196 if self.debug:
196 if self.debug:
197 print >>sys.__stdout__, "complete_current_input",
197 print >>sys.__stdout__, "complete_current_input",
198 line = self.input_buffer
198 line = self.input_buffer
199 new_line, completions = self.complete(line)
199 new_line, completions = self.complete(line)
200 if len(completions)>1:
200 if len(completions)>1:
201 self.write_completion(completions)
201 self.write_completion(completions)
202 self.input_buffer = new_line
202 self.input_buffer = new_line
203 if self.debug:
203 if self.debug:
204 print >>sys.__stdout__, completions
204 print >>sys.__stdout__, completions
205
205
206
206
207 def get_line_width(self):
208 """ Return the width of the line in characters.
209 """
210 return 80
211
212
207 def write_completion(self, possibilities):
213 def write_completion(self, possibilities):
208 """ Write the list of possible completions.
214 """ Write the list of possible completions.
209 """
215 """
210 current_buffer = self.input_buffer
216 current_buffer = self.input_buffer
211
217
212 self.write('\n')
218 self.write('\n')
213 max_len = len(max(possibilities, key=len)) + 1
219 max_len = len(max(possibilities, key=len)) + 1
214
220
215 #now we check how much symbol we can put on a line...
221 # Now we check how much symbol we can put on a line...
216 chars_per_line =self.get_line_width()
222 chars_per_line = self.get_line_width()
217 symbols_per_line = max(1, chars_per_line/max_len)
223 symbols_per_line = max(1, chars_per_line/max_len)
218
224
219 pos = 1
225 pos = 1
220 buf = []
226 buf = []
221 for symbol in possibilities:
227 for symbol in possibilities:
222 if pos < symbols_per_line:
228 if pos < symbols_per_line:
223 buf.append(symbol.ljust(max_len))
229 buf.append(symbol.ljust(max_len))
224 pos += 1
230 pos += 1
225 else:
231 else:
226 buf.append(symbol.rstrip() + '\n')
232 buf.append(symbol.rstrip() + '\n')
227 pos = 1
233 pos = 1
228 self.write(''.join(buf))
234 self.write(''.join(buf))
229 self.new_prompt(self.input_prompt_template.substitute(
235 self.new_prompt(self.input_prompt_template.substitute(
230 number=self.last_result['number'] + 1))
236 number=self.last_result['number'] + 1))
231 self.input_buffer = current_buffer
237 self.input_buffer = current_buffer
232
238
233
239
234
235 def new_prompt(self, prompt):
240 def new_prompt(self, prompt):
236 """ Prints a prompt and starts a new editing buffer.
241 """ Prints a prompt and starts a new editing buffer.
237
242
238 Subclasses should use this method to make sure that the
243 Subclasses should use this method to make sure that the
239 terminal is put in a state favorable for a new line
244 terminal is put in a state favorable for a new line
240 input.
245 input.
241 """
246 """
242 self.input_buffer = ''
247 self.input_buffer = ''
243 self.write(prompt)
248 self.write(prompt)
244
249
245
250
246 #--------------------------------------------------------------------------
251 #--------------------------------------------------------------------------
247 # Private API
252 # Private API
248 #--------------------------------------------------------------------------
253 #--------------------------------------------------------------------------
249
254
250 def _on_enter(self):
255 def _on_enter(self):
251 """ Called when the return key is pressed in a line editing
256 """ Called when the return key is pressed in a line editing
252 buffer.
257 buffer.
253 """
258 """
254 current_buffer = self.input_buffer
259 current_buffer = self.input_buffer
255 cleaned_buffer = self.prefilter_input(current_buffer)
260 cleaned_buffer = self.prefilter_input(current_buffer)
256 if self.is_complete(cleaned_buffer):
261 if self.is_complete(cleaned_buffer):
257 self.execute(cleaned_buffer, raw_string=current_buffer)
262 self.execute(cleaned_buffer, raw_string=current_buffer)
258 else:
263 else:
259 self.input_buffer += self._get_indent_string(
264 self.input_buffer += self._get_indent_string(
260 current_buffer[:-1])
265 current_buffer[:-1])
261 if current_buffer[:-1].split('\n')[-1].rstrip().endswith(':'):
266 if current_buffer[:-1].split('\n')[-1].rstrip().endswith(':'):
262 self.input_buffer += '\t'
267 self.input_buffer += '\t'
263
268
264
269
265 def _get_indent_string(self, string):
270 def _get_indent_string(self, string):
266 """ Return the string of whitespace that prefixes a line. Used to
271 """ Return the string of whitespace that prefixes a line. Used to
267 add the right amount of indendation when creating a new line.
272 add the right amount of indendation when creating a new line.
268 """
273 """
269 string = string.replace('\t', ' '*4)
274 string = string.replace('\t', ' '*4)
270 string = string.split('\n')[-1]
275 string = string.split('\n')[-1]
271 indent_chars = len(string) - len(string.lstrip())
276 indent_chars = len(string) - len(string.lstrip())
272 indent_string = '\t'*(indent_chars // 4) + \
277 indent_string = '\t'*(indent_chars // 4) + \
273 ' '*(indent_chars % 4)
278 ' '*(indent_chars % 4)
274
279
275 return indent_string
280 return indent_string
276
281
277
282
@@ -1,125 +1,129 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 IPython.frontend.prefilterfrontend import PrefilterFrontEnd
15 from IPython.frontend.prefilterfrontend import PrefilterFrontEnd
16 from cStringIO import StringIO
16 from cStringIO import StringIO
17 import string
17 import string
18
18
19 class TestPrefilterFrontEnd(PrefilterFrontEnd):
19 class TestPrefilterFrontEnd(PrefilterFrontEnd):
20
20
21 input_prompt_template = string.Template('')
21 input_prompt_template = string.Template('')
22 output_prompt_template = string.Template('')
22 output_prompt_template = string.Template('')
23
23
24 def __init__(self):
24 def __init__(self):
25 self.out = StringIO()
25 self.out = StringIO()
26 PrefilterFrontEnd.__init__(self)
26 PrefilterFrontEnd.__init__(self)
27
27
28 def write(self, string):
28 def write(self, string):
29 self.out.write(string)
29 self.out.write(string)
30
30
31 def _on_enter(self):
31 def _on_enter(self):
32 self.input_buffer += '\n'
32 self.input_buffer += '\n'
33 PrefilterFrontEnd._on_enter(self)
33 PrefilterFrontEnd._on_enter(self)
34
34
35
35
36 def test_execution():
36 def test_execution():
37 """ Test execution of a command.
37 """ Test execution of a command.
38 """
38 """
39 f = TestPrefilterFrontEnd()
39 f = TestPrefilterFrontEnd()
40 f.input_buffer = 'print 1\n'
40 f.input_buffer = 'print 1\n'
41 f._on_enter()
41 f._on_enter()
42 assert f.out.getvalue() == '1\n'
42 assert f.out.getvalue() == '1\n'
43
43
44
44
45 def test_multiline():
45 def test_multiline():
46 """ Test execution of a multiline command.
46 """ Test execution of a multiline command.
47 """
47 """
48 f = TestPrefilterFrontEnd()
48 f = TestPrefilterFrontEnd()
49 f.input_buffer = 'if True:'
49 f.input_buffer = 'if True:'
50 f._on_enter()
50 f._on_enter()
51 f.input_buffer += 'print 1'
51 f.input_buffer += 'print 1'
52 f._on_enter()
52 f._on_enter()
53 assert f.out.getvalue() == ''
53 assert f.out.getvalue() == ''
54 f._on_enter()
54 f._on_enter()
55 assert f.out.getvalue() == '1\n'
55 assert f.out.getvalue() == '1\n'
56 f = TestPrefilterFrontEnd()
56 f = TestPrefilterFrontEnd()
57 f.input_buffer='(1 +'
57 f.input_buffer='(1 +'
58 f._on_enter()
58 f._on_enter()
59 f.input_buffer += '0)'
59 f.input_buffer += '0)'
60 f._on_enter()
60 f._on_enter()
61 assert f.out.getvalue() == ''
61 assert f.out.getvalue() == ''
62 f._on_enter()
62 f._on_enter()
63 assert f.out.getvalue() == '1\n'
63 assert f.out.getvalue() == '1\n'
64
64
65
65
66 def test_capture():
66 def test_capture():
67 """ Test the capture of output in different channels.
67 """ Test the capture of output in different channels.
68 """
68 """
69 # Test on the OS-level stdout, stderr.
69 # Test on the OS-level stdout, stderr.
70 f = TestPrefilterFrontEnd()
70 f = TestPrefilterFrontEnd()
71 f.input_buffer = \
71 f.input_buffer = \
72 'import os; out=os.fdopen(1, "w"); out.write("1") ; out.flush()'
72 'import os; out=os.fdopen(1, "w"); out.write("1") ; out.flush()'
73 f._on_enter()
73 f._on_enter()
74 assert f.out.getvalue() == '1'
74 assert f.out.getvalue() == '1'
75 f = TestPrefilterFrontEnd()
75 f = TestPrefilterFrontEnd()
76 f.input_buffer = \
76 f.input_buffer = \
77 'import os; out=os.fdopen(2, "w"); out.write("1") ; out.flush()'
77 'import os; out=os.fdopen(2, "w"); out.write("1") ; out.flush()'
78 f._on_enter()
78 f._on_enter()
79 assert f.out.getvalue() == '1'
79 assert f.out.getvalue() == '1'
80
80
81
81
82 def test_magic():
82 def test_magic():
83 """ Test the magic expansion and history.
83 """ Test the magic expansion and history.
84
84
85 This test is fairly fragile and will break when magics change.
85 This test is fairly fragile and will break when magics change.
86 """
86 """
87 f = TestPrefilterFrontEnd()
87 f = TestPrefilterFrontEnd()
88 f.input_buffer += '%who\n'
88 f.input_buffer += '%who\n'
89 f._on_enter()
89 f._on_enter()
90 assert f.out.getvalue() == 'Interactive namespace is empty.\n'
90 assert f.out.getvalue() == 'Interactive namespace is empty.\n'
91
91
92
92
93 def test_help():
93 def test_help():
94 """ Test object inspection.
94 """ Test object inspection.
95 """
95 """
96 f = TestPrefilterFrontEnd()
96 f = TestPrefilterFrontEnd()
97 f.input_buffer += "def f():"
97 f.input_buffer += "def f():"
98 f._on_enter()
98 f._on_enter()
99 f.input_buffer += "'foobar'"
99 f.input_buffer += "'foobar'"
100 f._on_enter()
100 f._on_enter()
101 f.input_buffer += "pass"
101 f.input_buffer += "pass"
102 f._on_enter()
102 f._on_enter()
103 f._on_enter()
103 f._on_enter()
104 f.input_buffer += "f?"
104 f.input_buffer += "f?"
105 f._on_enter()
105 f._on_enter()
106 assert f.out.getvalue().split()[-1] == 'foobar'
106 assert f.out.getvalue().split()[-1] == 'foobar'
107
107
108
108
109 def test_completion():
109 def test_completion():
110 """ Test command-line completion.
110 """ Test command-line completion.
111 """
111 """
112 f = TestPrefilterFrontEnd()
112 f = TestPrefilterFrontEnd()
113 f.input_buffer = 'zzza = 1'
113 f.input_buffer = 'zzza = 1'
114 f._on_enter()
114 f._on_enter()
115 f.input_buffer = 'zzzb = 2'
115 f.input_buffer = 'zzzb = 2'
116 f._on_enter()
116 f._on_enter()
117 f.input_buffer = 'zz'
117 f.input_buffer = 'zz'
118 f.complete_current_input()
119 assert f.out.getvalue() == '\nzzza zzzb '
120 assert f.input_buffer == 'zzz'
118
121
119
122
120 if __name__ == '__main__':
123 if __name__ == '__main__':
121 test_magic()
124 test_magic()
122 test_help()
125 test_help()
123 test_execution()
126 test_execution()
124 test_multiline()
127 test_multiline()
125 test_capture()
128 test_capture()
129 test_completion()
General Comments 0
You need to be logged in to leave comments. Login now