##// END OF EJS Templates
Fix buglet in frontend completion.
gvaroquaux -
Show More
@@ -1,305 +1,307 b''
1 1 """
2 2 Base front end class for all line-oriented frontends, rather than
3 3 block-oriented.
4 4
5 5 Currently this focuses on synchronous frontends.
6 6 """
7 7 __docformat__ = "restructuredtext en"
8 8
9 9 #-------------------------------------------------------------------------------
10 10 # Copyright (C) 2008 The IPython Development Team
11 11 #
12 12 # Distributed under the terms of the BSD License. The full license is in
13 13 # the file COPYING, distributed as part of this software.
14 14 #-------------------------------------------------------------------------------
15 15
16 16 #-------------------------------------------------------------------------------
17 17 # Imports
18 18 #-------------------------------------------------------------------------------
19 19 import re
20 20
21 21 import IPython
22 22 import sys
23 23
24 24 from frontendbase import FrontEndBase
25 25 from IPython.kernel.core.interpreter import Interpreter
26 26
27 27 def common_prefix(strings):
28 28 """ Given a list of strings, return the common prefix between all
29 29 these strings.
30 30 """
31 31 ref = strings[0]
32 32 prefix = ''
33 33 for size in range(len(ref)):
34 34 test_prefix = ref[:size+1]
35 35 for string in strings[1:]:
36 36 if not string.startswith(test_prefix):
37 37 return prefix
38 38 prefix = test_prefix
39 39
40 40 return prefix
41 41
42 42 #-------------------------------------------------------------------------------
43 43 # Base class for the line-oriented front ends
44 44 #-------------------------------------------------------------------------------
45 45 class LineFrontEndBase(FrontEndBase):
46 46 """ Concrete implementation of the FrontEndBase class. This is meant
47 47 to be the base class behind all the frontend that are line-oriented,
48 48 rather than block-oriented.
49 49 """
50 50
51 51 # We need to keep the prompt number, to be able to increment
52 52 # it when there is an exception.
53 53 prompt_number = 1
54 54
55 55 # We keep a reference to the last result: it helps testing and
56 56 # programatic control of the frontend.
57 57 last_result = dict(number=0)
58 58
59 59 # The input buffer being edited
60 60 input_buffer = ''
61 61
62 62 # Set to true for debug output
63 63 debug = False
64 64
65 65 # A banner to print at startup
66 66 banner = None
67 67
68 68 #--------------------------------------------------------------------------
69 69 # FrontEndBase interface
70 70 #--------------------------------------------------------------------------
71 71
72 72 def __init__(self, shell=None, history=None, banner=None, *args, **kwargs):
73 73 if shell is None:
74 74 shell = Interpreter()
75 75 FrontEndBase.__init__(self, shell=shell, history=history)
76 76
77 77 if banner is not None:
78 78 self.banner = banner
79 79
80 80 def start(self):
81 81 """ Put the frontend in a state where it is ready for user
82 82 interaction.
83 83 """
84 84 if self.banner is not None:
85 85 self.write(self.banner, refresh=False)
86 86
87 87 self.new_prompt(self.input_prompt_template.substitute(number=1))
88 88
89 89
90 90 def complete(self, line):
91 91 """Complete line in engine's user_ns
92 92
93 93 Parameters
94 94 ----------
95 95 line : string
96 96
97 97 Result
98 98 ------
99 99 The replacement for the line and the list of possible completions.
100 100 """
101 101 completions = self.shell.complete(line)
102 102 complete_sep = re.compile('[\s\{\}\[\]\(\)\=]')
103 103 if completions:
104 104 prefix = common_prefix(completions)
105 105 residual = complete_sep.split(line)[:-1]
106 106 line = line[:-len(residual)] + prefix
107 107 return line, completions
108 108
109 109
110 110 def render_result(self, result):
111 111 """ Frontend-specific rendering of the result of a calculation
112 112 that has been sent to an engine.
113 113 """
114 114 if 'stdout' in result and result['stdout']:
115 115 self.write('\n' + result['stdout'])
116 116 if 'display' in result and result['display']:
117 117 self.write("%s%s\n" % (
118 118 self.output_prompt_template.substitute(
119 119 number=result['number']),
120 120 result['display']['pprint']
121 121 ) )
122 122
123 123
124 124 def render_error(self, failure):
125 125 """ Frontend-specific rendering of error.
126 126 """
127 127 self.write('\n\n'+str(failure)+'\n\n')
128 128 return failure
129 129
130 130
131 131 def is_complete(self, string):
132 132 """ Check if a string forms a complete, executable set of
133 133 commands.
134 134
135 135 For the line-oriented frontend, multi-line code is not executed
136 136 as soon as it is complete: the users has to enter two line
137 137 returns.
138 138 """
139 139 if string in ('', '\n'):
140 140 # Prefiltering, eg through ipython0, may return an empty
141 141 # string although some operations have been accomplished. We
142 142 # thus want to consider an empty string as a complete
143 143 # statement.
144 144 return True
145 145 elif ( len(self.input_buffer.split('\n'))>2
146 146 and not re.findall(r"\n[\t ]*\n[\t ]*$", string)):
147 147 return False
148 148 else:
149 149 # Add line returns here, to make sure that the statement is
150 150 # complete.
151 151 return FrontEndBase.is_complete(self, string.rstrip() + '\n\n')
152 152
153 153
154 154 def write(self, string, refresh=True):
155 155 """ Write some characters to the display.
156 156
157 157 Subclass should overide this method.
158 158
159 159 The refresh keyword argument is used in frontends with an
160 160 event loop, to choose whether the write should trigget an UI
161 161 refresh, and thus be syncrhonous, or not.
162 162 """
163 163 print >>sys.__stderr__, string
164 164
165 165
166 166 def execute(self, python_string, raw_string=None):
167 167 """ Stores the raw_string in the history, and sends the
168 168 python string to the interpreter.
169 169 """
170 170 if raw_string is None:
171 171 raw_string = python_string
172 172 # Create a false result, in case there is an exception
173 173 self.last_result = dict(number=self.prompt_number)
174 174 try:
175 175 self.history.input_cache[-1] = raw_string.rstrip()
176 176 result = self.shell.execute(python_string)
177 177 self.last_result = result
178 178 self.render_result(result)
179 179 except:
180 180 self.show_traceback()
181 181 finally:
182 182 self.after_execute()
183 183
184 184 #--------------------------------------------------------------------------
185 185 # LineFrontEndBase interface
186 186 #--------------------------------------------------------------------------
187 187
188 188 def prefilter_input(self, string):
189 189 """ Priflter the input to turn it in valid python.
190 190 """
191 191 string = string.replace('\r\n', '\n')
192 192 string = string.replace('\t', 4*' ')
193 193 # Clean the trailing whitespace
194 194 string = '\n'.join(l.rstrip() for l in string.split('\n'))
195 195 return string
196 196
197 197
198 198 def after_execute(self):
199 199 """ All the operations required after an execution to put the
200 200 terminal back in a shape where it is usable.
201 201 """
202 202 self.prompt_number += 1
203 203 self.new_prompt(self.input_prompt_template.substitute(
204 204 number=(self.last_result['number'] + 1)))
205 205 # Start a new empty history entry
206 206 self._add_history(None, '')
207 207 self.history_cursor = len(self.history.input_cache) - 1
208 208
209 209
210 210 def complete_current_input(self):
211 211 """ Do code completion on current line.
212 212 """
213 213 if self.debug:
214 214 print >>sys.__stdout__, "complete_current_input",
215 215 line = self.input_buffer
216 216 new_line, completions = self.complete(line)
217 217 if len(completions)>1:
218 218 self.write_completion(completions, new_line=new_line)
219 if not line == new_line:
220 self.input_buffer = new_line
219 221 if self.debug:
220 222 print >>sys.__stdout__, 'line', line
221 223 print >>sys.__stdout__, 'new_line', new_line
222 224 print >>sys.__stdout__, completions
223 225
224 226
225 227 def get_line_width(self):
226 228 """ Return the width of the line in characters.
227 229 """
228 230 return 80
229 231
230 232
231 233 def write_completion(self, possibilities, new_line=None):
232 234 """ Write the list of possible completions.
233 235
234 236 new_line is the completed input line that should be displayed
235 237 after the completion are writen. If None, the input_buffer
236 238 before the completion is used.
237 239 """
238 240 if new_line is None:
239 241 new_line = self.input_buffer
240 242
241 243 self.write('\n')
242 244 max_len = len(max(possibilities, key=len)) + 1
243 245
244 246 # Now we check how much symbol we can put on a line...
245 247 chars_per_line = self.get_line_width()
246 248 symbols_per_line = max(1, chars_per_line/max_len)
247 249
248 250 pos = 1
249 251 buf = []
250 252 for symbol in possibilities:
251 253 if pos < symbols_per_line:
252 254 buf.append(symbol.ljust(max_len))
253 255 pos += 1
254 256 else:
255 257 buf.append(symbol.rstrip() + '\n')
256 258 pos = 1
257 259 self.write(''.join(buf))
258 260 self.new_prompt(self.input_prompt_template.substitute(
259 261 number=self.last_result['number'] + 1))
260 262 self.input_buffer = new_line
261 263
262 264
263 265 def new_prompt(self, prompt):
264 266 """ Prints a prompt and starts a new editing buffer.
265 267
266 268 Subclasses should use this method to make sure that the
267 269 terminal is put in a state favorable for a new line
268 270 input.
269 271 """
270 272 self.input_buffer = ''
271 273 self.write(prompt)
272 274
273 275
274 276 #--------------------------------------------------------------------------
275 277 # Private API
276 278 #--------------------------------------------------------------------------
277 279
278 280 def _on_enter(self):
279 281 """ Called when the return key is pressed in a line editing
280 282 buffer.
281 283 """
282 284 current_buffer = self.input_buffer
283 285 cleaned_buffer = self.prefilter_input(current_buffer)
284 286 if self.is_complete(cleaned_buffer):
285 287 self.execute(cleaned_buffer, raw_string=current_buffer)
286 288 else:
287 289 self.input_buffer += self._get_indent_string(
288 290 current_buffer[:-1])
289 291 if current_buffer[:-1].split('\n')[-1].rstrip().endswith(':'):
290 292 self.input_buffer += '\t'
291 293
292 294
293 295 def _get_indent_string(self, string):
294 296 """ Return the string of whitespace that prefixes a line. Used to
295 297 add the right amount of indendation when creating a new line.
296 298 """
297 299 string = string.replace('\t', ' '*4)
298 300 string = string.split('\n')[-1]
299 301 indent_chars = len(string) - len(string.lstrip())
300 302 indent_string = '\t'*(indent_chars // 4) + \
301 303 ' '*(indent_chars % 4)
302 304
303 305 return indent_string
304 306
305 307
@@ -1,75 +1,74 b''
1 1 # Set this prefix to where you want to install the plugin
2 PREFIX=~/usr/local
3 PREFIX=~/tmp/local
2 PREFIX=/usr/local
4 3
5 4 NOSE0=nosetests -vs --with-doctest --doctest-tests --detailed-errors
6 5 NOSE=nosetests -vvs --with-ipdoctest --doctest-tests --doctest-extension=txt \
7 6 --detailed-errors
8 7
9 8 SRC=ipdoctest.py setup.py ../decorators.py
10 9
11 10 # Default target for clean 'make'
12 11 default: iplib
13 12
14 13 # The actual plugin installation
15 14 plugin: IPython_doctest_plugin.egg-info
16 15
17 16 # Simple targets that test one thing
18 17 simple: plugin simple.py
19 18 $(NOSE) simple.py
20 19
21 20 dtest: plugin dtexample.py
22 21 $(NOSE) dtexample.py
23 22
24 23 rtest: plugin test_refs.py
25 24 $(NOSE) test_refs.py
26 25
27 26 test: plugin dtexample.py
28 27 $(NOSE) dtexample.py test*.py test*.txt
29 28
30 29 deb: plugin dtexample.py
31 30 $(NOSE) test_combo.txt
32 31
33 32 # IPython tests
34 33 deco:
35 34 $(NOSE0) IPython.testing.decorators
36 35
37 36 magic: plugin
38 37 $(NOSE) IPython.Magic
39 38
40 39 ipipe: plugin
41 40 $(NOSE) IPython.Extensions.ipipe
42 41
43 42 iplib: plugin
44 43 $(NOSE) IPython.iplib
45 44
46 45 strd: plugin
47 46 $(NOSE) IPython.strdispatch
48 47
49 48 engine: plugin
50 49 $(NOSE) IPython.kernel
51 50
52 51 tf: plugin
53 52 $(NOSE) IPython.config.traitlets
54 53
55 54 # All of ipython itself
56 55 ipython: plugin
57 56 $(NOSE) IPython
58 57
59 58
60 59 # Combined targets
61 60 sr: rtest strd
62 61
63 62 base: dtest rtest test strd deco
64 63
65 64 quick: base iplib ipipe
66 65
67 66 all: base ipython
68 67
69 68 # Main plugin and cleanup
70 69 IPython_doctest_plugin.egg-info: $(SRC)
71 70 python setup.py install --prefix=$(PREFIX)
72 71 touch $@
73 72
74 73 clean:
75 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