##// END OF EJS Templates
Fix buglet in frontend completion.
gvaroquaux -
Show More
@@ -1,305 +1,307 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 # 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 # Add line returns here, to make sure that the statement is
149 # Add line returns here, to make sure that the statement is
150 # complete.
150 # complete.
151 return FrontEndBase.is_complete(self, string.rstrip() + '\n\n')
151 return FrontEndBase.is_complete(self, string.rstrip() + '\n\n')
152
152
153
153
154 def write(self, string, refresh=True):
154 def write(self, string, refresh=True):
155 """ Write some characters to the display.
155 """ Write some characters to the display.
156
156
157 Subclass should overide this method.
157 Subclass should overide this method.
158
158
159 The refresh keyword argument is used in frontends with an
159 The refresh keyword argument is used in frontends with an
160 event loop, to choose whether the write should trigget an UI
160 event loop, to choose whether the write should trigget an UI
161 refresh, and thus be syncrhonous, or not.
161 refresh, and thus be syncrhonous, or not.
162 """
162 """
163 print >>sys.__stderr__, string
163 print >>sys.__stderr__, string
164
164
165
165
166 def execute(self, python_string, raw_string=None):
166 def execute(self, python_string, raw_string=None):
167 """ Stores the raw_string in the history, and sends the
167 """ Stores the raw_string in the history, and sends the
168 python string to the interpreter.
168 python string to the interpreter.
169 """
169 """
170 if raw_string is None:
170 if raw_string is None:
171 raw_string = python_string
171 raw_string = python_string
172 # Create a false result, in case there is an exception
172 # Create a false result, in case there is an exception
173 self.last_result = dict(number=self.prompt_number)
173 self.last_result = dict(number=self.prompt_number)
174 try:
174 try:
175 self.history.input_cache[-1] = raw_string.rstrip()
175 self.history.input_cache[-1] = raw_string.rstrip()
176 result = self.shell.execute(python_string)
176 result = self.shell.execute(python_string)
177 self.last_result = result
177 self.last_result = result
178 self.render_result(result)
178 self.render_result(result)
179 except:
179 except:
180 self.show_traceback()
180 self.show_traceback()
181 finally:
181 finally:
182 self.after_execute()
182 self.after_execute()
183
183
184 #--------------------------------------------------------------------------
184 #--------------------------------------------------------------------------
185 # LineFrontEndBase interface
185 # LineFrontEndBase interface
186 #--------------------------------------------------------------------------
186 #--------------------------------------------------------------------------
187
187
188 def prefilter_input(self, string):
188 def prefilter_input(self, string):
189 """ Priflter the input to turn it in valid python.
189 """ Priflter the input to turn it in valid python.
190 """
190 """
191 string = string.replace('\r\n', '\n')
191 string = string.replace('\r\n', '\n')
192 string = string.replace('\t', 4*' ')
192 string = string.replace('\t', 4*' ')
193 # Clean the trailing whitespace
193 # Clean the trailing whitespace
194 string = '\n'.join(l.rstrip() for l in string.split('\n'))
194 string = '\n'.join(l.rstrip() for l in string.split('\n'))
195 return string
195 return string
196
196
197
197
198 def after_execute(self):
198 def after_execute(self):
199 """ All the operations required after an execution to put the
199 """ All the operations required after an execution to put the
200 terminal back in a shape where it is usable.
200 terminal back in a shape where it is usable.
201 """
201 """
202 self.prompt_number += 1
202 self.prompt_number += 1
203 self.new_prompt(self.input_prompt_template.substitute(
203 self.new_prompt(self.input_prompt_template.substitute(
204 number=(self.last_result['number'] + 1)))
204 number=(self.last_result['number'] + 1)))
205 # Start a new empty history entry
205 # Start a new empty history entry
206 self._add_history(None, '')
206 self._add_history(None, '')
207 self.history_cursor = len(self.history.input_cache) - 1
207 self.history_cursor = len(self.history.input_cache) - 1
208
208
209
209
210 def complete_current_input(self):
210 def complete_current_input(self):
211 """ Do code completion on current line.
211 """ Do code completion on current line.
212 """
212 """
213 if self.debug:
213 if self.debug:
214 print >>sys.__stdout__, "complete_current_input",
214 print >>sys.__stdout__, "complete_current_input",
215 line = self.input_buffer
215 line = self.input_buffer
216 new_line, completions = self.complete(line)
216 new_line, completions = self.complete(line)
217 if len(completions)>1:
217 if len(completions)>1:
218 self.write_completion(completions, new_line=new_line)
218 self.write_completion(completions, new_line=new_line)
219 if not line == new_line:
220 self.input_buffer = new_line
219 if self.debug:
221 if self.debug:
220 print >>sys.__stdout__, 'line', line
222 print >>sys.__stdout__, 'line', line
221 print >>sys.__stdout__, 'new_line', new_line
223 print >>sys.__stdout__, 'new_line', new_line
222 print >>sys.__stdout__, completions
224 print >>sys.__stdout__, completions
223
225
224
226
225 def get_line_width(self):
227 def get_line_width(self):
226 """ Return the width of the line in characters.
228 """ Return the width of the line in characters.
227 """
229 """
228 return 80
230 return 80
229
231
230
232
231 def write_completion(self, possibilities, new_line=None):
233 def write_completion(self, possibilities, new_line=None):
232 """ Write the list of possible completions.
234 """ Write the list of possible completions.
233
235
234 new_line is the completed input line that should be displayed
236 new_line is the completed input line that should be displayed
235 after the completion are writen. If None, the input_buffer
237 after the completion are writen. If None, the input_buffer
236 before the completion is used.
238 before the completion is used.
237 """
239 """
238 if new_line is None:
240 if new_line is None:
239 new_line = self.input_buffer
241 new_line = self.input_buffer
240
242
241 self.write('\n')
243 self.write('\n')
242 max_len = len(max(possibilities, key=len)) + 1
244 max_len = len(max(possibilities, key=len)) + 1
243
245
244 # Now we check how much symbol we can put on a line...
246 # Now we check how much symbol we can put on a line...
245 chars_per_line = self.get_line_width()
247 chars_per_line = self.get_line_width()
246 symbols_per_line = max(1, chars_per_line/max_len)
248 symbols_per_line = max(1, chars_per_line/max_len)
247
249
248 pos = 1
250 pos = 1
249 buf = []
251 buf = []
250 for symbol in possibilities:
252 for symbol in possibilities:
251 if pos < symbols_per_line:
253 if pos < symbols_per_line:
252 buf.append(symbol.ljust(max_len))
254 buf.append(symbol.ljust(max_len))
253 pos += 1
255 pos += 1
254 else:
256 else:
255 buf.append(symbol.rstrip() + '\n')
257 buf.append(symbol.rstrip() + '\n')
256 pos = 1
258 pos = 1
257 self.write(''.join(buf))
259 self.write(''.join(buf))
258 self.new_prompt(self.input_prompt_template.substitute(
260 self.new_prompt(self.input_prompt_template.substitute(
259 number=self.last_result['number'] + 1))
261 number=self.last_result['number'] + 1))
260 self.input_buffer = new_line
262 self.input_buffer = new_line
261
263
262
264
263 def new_prompt(self, prompt):
265 def new_prompt(self, prompt):
264 """ Prints a prompt and starts a new editing buffer.
266 """ Prints a prompt and starts a new editing buffer.
265
267
266 Subclasses should use this method to make sure that the
268 Subclasses should use this method to make sure that the
267 terminal is put in a state favorable for a new line
269 terminal is put in a state favorable for a new line
268 input.
270 input.
269 """
271 """
270 self.input_buffer = ''
272 self.input_buffer = ''
271 self.write(prompt)
273 self.write(prompt)
272
274
273
275
274 #--------------------------------------------------------------------------
276 #--------------------------------------------------------------------------
275 # Private API
277 # Private API
276 #--------------------------------------------------------------------------
278 #--------------------------------------------------------------------------
277
279
278 def _on_enter(self):
280 def _on_enter(self):
279 """ Called when the return key is pressed in a line editing
281 """ Called when the return key is pressed in a line editing
280 buffer.
282 buffer.
281 """
283 """
282 current_buffer = self.input_buffer
284 current_buffer = self.input_buffer
283 cleaned_buffer = self.prefilter_input(current_buffer)
285 cleaned_buffer = self.prefilter_input(current_buffer)
284 if self.is_complete(cleaned_buffer):
286 if self.is_complete(cleaned_buffer):
285 self.execute(cleaned_buffer, raw_string=current_buffer)
287 self.execute(cleaned_buffer, raw_string=current_buffer)
286 else:
288 else:
287 self.input_buffer += self._get_indent_string(
289 self.input_buffer += self._get_indent_string(
288 current_buffer[:-1])
290 current_buffer[:-1])
289 if current_buffer[:-1].split('\n')[-1].rstrip().endswith(':'):
291 if current_buffer[:-1].split('\n')[-1].rstrip().endswith(':'):
290 self.input_buffer += '\t'
292 self.input_buffer += '\t'
291
293
292
294
293 def _get_indent_string(self, string):
295 def _get_indent_string(self, string):
294 """ Return the string of whitespace that prefixes a line. Used to
296 """ Return the string of whitespace that prefixes a line. Used to
295 add the right amount of indendation when creating a new line.
297 add the right amount of indendation when creating a new line.
296 """
298 """
297 string = string.replace('\t', ' '*4)
299 string = string.replace('\t', ' '*4)
298 string = string.split('\n')[-1]
300 string = string.split('\n')[-1]
299 indent_chars = len(string) - len(string.lstrip())
301 indent_chars = len(string) - len(string.lstrip())
300 indent_string = '\t'*(indent_chars // 4) + \
302 indent_string = '\t'*(indent_chars // 4) + \
301 ' '*(indent_chars % 4)
303 ' '*(indent_chars % 4)
302
304
303 return indent_string
305 return indent_string
304
306
305
307
@@ -1,75 +1,74 b''
1 # Set this prefix to where you want to install the plugin
1 # Set this prefix to where you want to install the plugin
2 PREFIX=~/usr/local
2 PREFIX=/usr/local
3 PREFIX=~/tmp/local
4
3
5 NOSE0=nosetests -vs --with-doctest --doctest-tests --detailed-errors
4 NOSE0=nosetests -vs --with-doctest --doctest-tests --detailed-errors
6 NOSE=nosetests -vvs --with-ipdoctest --doctest-tests --doctest-extension=txt \
5 NOSE=nosetests -vvs --with-ipdoctest --doctest-tests --doctest-extension=txt \
7 --detailed-errors
6 --detailed-errors
8
7
9 SRC=ipdoctest.py setup.py ../decorators.py
8 SRC=ipdoctest.py setup.py ../decorators.py
10
9
11 # Default target for clean 'make'
10 # Default target for clean 'make'
12 default: iplib
11 default: iplib
13
12
14 # The actual plugin installation
13 # The actual plugin installation
15 plugin: IPython_doctest_plugin.egg-info
14 plugin: IPython_doctest_plugin.egg-info
16
15
17 # Simple targets that test one thing
16 # Simple targets that test one thing
18 simple: plugin simple.py
17 simple: plugin simple.py
19 $(NOSE) simple.py
18 $(NOSE) simple.py
20
19
21 dtest: plugin dtexample.py
20 dtest: plugin dtexample.py
22 $(NOSE) dtexample.py
21 $(NOSE) dtexample.py
23
22
24 rtest: plugin test_refs.py
23 rtest: plugin test_refs.py
25 $(NOSE) test_refs.py
24 $(NOSE) test_refs.py
26
25
27 test: plugin dtexample.py
26 test: plugin dtexample.py
28 $(NOSE) dtexample.py test*.py test*.txt
27 $(NOSE) dtexample.py test*.py test*.txt
29
28
30 deb: plugin dtexample.py
29 deb: plugin dtexample.py
31 $(NOSE) test_combo.txt
30 $(NOSE) test_combo.txt
32
31
33 # IPython tests
32 # IPython tests
34 deco:
33 deco:
35 $(NOSE0) IPython.testing.decorators
34 $(NOSE0) IPython.testing.decorators
36
35
37 magic: plugin
36 magic: plugin
38 $(NOSE) IPython.Magic
37 $(NOSE) IPython.Magic
39
38
40 ipipe: plugin
39 ipipe: plugin
41 $(NOSE) IPython.Extensions.ipipe
40 $(NOSE) IPython.Extensions.ipipe
42
41
43 iplib: plugin
42 iplib: plugin
44 $(NOSE) IPython.iplib
43 $(NOSE) IPython.iplib
45
44
46 strd: plugin
45 strd: plugin
47 $(NOSE) IPython.strdispatch
46 $(NOSE) IPython.strdispatch
48
47
49 engine: plugin
48 engine: plugin
50 $(NOSE) IPython.kernel
49 $(NOSE) IPython.kernel
51
50
52 tf: plugin
51 tf: plugin
53 $(NOSE) IPython.config.traitlets
52 $(NOSE) IPython.config.traitlets
54
53
55 # All of ipython itself
54 # All of ipython itself
56 ipython: plugin
55 ipython: plugin
57 $(NOSE) IPython
56 $(NOSE) IPython
58
57
59
58
60 # Combined targets
59 # Combined targets
61 sr: rtest strd
60 sr: rtest strd
62
61
63 base: dtest rtest test strd deco
62 base: dtest rtest test strd deco
64
63
65 quick: base iplib ipipe
64 quick: base iplib ipipe
66
65
67 all: base ipython
66 all: base ipython
68
67
69 # Main plugin and cleanup
68 # Main plugin and cleanup
70 IPython_doctest_plugin.egg-info: $(SRC)
69 IPython_doctest_plugin.egg-info: $(SRC)
71 python setup.py install --prefix=$(PREFIX)
70 python setup.py install --prefix=$(PREFIX)
72 touch $@
71 touch $@
73
72
74 clean:
73 clean:
75 rm -rf IPython_doctest_plugin.egg-info *~ *pyc build/ dist/
74 rm -rf IPython_doctest_plugin.egg-info *~ *pyc build/ dist/
General Comments 0
You need to be logged in to leave comments. Login now