##// END OF EJS Templates
Improve auto-indentation
Gael Varoquaux -
Show More
@@ -1,367 +1,367 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 last prompt displayed. Useful for continuation prompts.
59 # The last prompt displayed. Useful for continuation prompts.
60 last_prompt = ''
60 last_prompt = ''
61
61
62 # The input buffer being edited
62 # The input buffer being edited
63 input_buffer = ''
63 input_buffer = ''
64
64
65 # Set to true for debug output
65 # Set to true for debug output
66 debug = False
66 debug = False
67
67
68 # A banner to print at startup
68 # A banner to print at startup
69 banner = None
69 banner = None
70
70
71 #--------------------------------------------------------------------------
71 #--------------------------------------------------------------------------
72 # FrontEndBase interface
72 # FrontEndBase interface
73 #--------------------------------------------------------------------------
73 #--------------------------------------------------------------------------
74
74
75 def __init__(self, shell=None, history=None, banner=None, *args, **kwargs):
75 def __init__(self, shell=None, history=None, banner=None, *args, **kwargs):
76 if shell is None:
76 if shell is None:
77 shell = Interpreter()
77 shell = Interpreter()
78 FrontEndBase.__init__(self, shell=shell, history=history)
78 FrontEndBase.__init__(self, shell=shell, history=history)
79
79
80 if banner is not None:
80 if banner is not None:
81 self.banner = banner
81 self.banner = banner
82
82
83 def start(self):
83 def start(self):
84 """ Put the frontend in a state where it is ready for user
84 """ Put the frontend in a state where it is ready for user
85 interaction.
85 interaction.
86 """
86 """
87 if self.banner is not None:
87 if self.banner is not None:
88 self.write(self.banner, refresh=False)
88 self.write(self.banner, refresh=False)
89
89
90 self.new_prompt(self.input_prompt_template.substitute(number=1))
90 self.new_prompt(self.input_prompt_template.substitute(number=1))
91
91
92
92
93 def complete(self, line):
93 def complete(self, line):
94 """Complete line in engine's user_ns
94 """Complete line in engine's user_ns
95
95
96 Parameters
96 Parameters
97 ----------
97 ----------
98 line : string
98 line : string
99
99
100 Result
100 Result
101 ------
101 ------
102 The replacement for the line and the list of possible completions.
102 The replacement for the line and the list of possible completions.
103 """
103 """
104 completions = self.shell.complete(line)
104 completions = self.shell.complete(line)
105 complete_sep = re.compile('[\s\{\}\[\]\(\)\=]')
105 complete_sep = re.compile('[\s\{\}\[\]\(\)\=]')
106 if completions:
106 if completions:
107 prefix = common_prefix(completions)
107 prefix = common_prefix(completions)
108 residual = complete_sep.split(line)[:-1]
108 residual = complete_sep.split(line)[:-1]
109 line = line[:-len(residual)] + prefix
109 line = line[:-len(residual)] + prefix
110 return line, completions
110 return line, completions
111
111
112
112
113 def render_result(self, result):
113 def render_result(self, result):
114 """ Frontend-specific rendering of the result of a calculation
114 """ Frontend-specific rendering of the result of a calculation
115 that has been sent to an engine.
115 that has been sent to an engine.
116 """
116 """
117 if 'stdout' in result and result['stdout']:
117 if 'stdout' in result and result['stdout']:
118 self.write('\n' + result['stdout'])
118 self.write('\n' + result['stdout'])
119 if 'display' in result and result['display']:
119 if 'display' in result and result['display']:
120 self.write("%s%s\n" % (
120 self.write("%s%s\n" % (
121 self.output_prompt_template.substitute(
121 self.output_prompt_template.substitute(
122 number=result['number']),
122 number=result['number']),
123 result['display']['pprint']
123 result['display']['pprint']
124 ) )
124 ) )
125
125
126
126
127 def render_error(self, failure):
127 def render_error(self, failure):
128 """ Frontend-specific rendering of error.
128 """ Frontend-specific rendering of error.
129 """
129 """
130 self.write('\n\n'+str(failure)+'\n\n')
130 self.write('\n\n'+str(failure)+'\n\n')
131 return failure
131 return failure
132
132
133
133
134 def is_complete(self, string):
134 def is_complete(self, string):
135 """ Check if a string forms a complete, executable set of
135 """ Check if a string forms a complete, executable set of
136 commands.
136 commands.
137
137
138 For the line-oriented frontend, multi-line code is not executed
138 For the line-oriented frontend, multi-line code is not executed
139 as soon as it is complete: the users has to enter two line
139 as soon as it is complete: the users has to enter two line
140 returns.
140 returns.
141 """
141 """
142 if string in ('', '\n'):
142 if string in ('', '\n'):
143 # Prefiltering, eg through ipython0, may return an empty
143 # Prefiltering, eg through ipython0, may return an empty
144 # string although some operations have been accomplished. We
144 # string although some operations have been accomplished. We
145 # thus want to consider an empty string as a complete
145 # thus want to consider an empty string as a complete
146 # statement.
146 # statement.
147 return True
147 return True
148 elif ( len(self.input_buffer.split('\n'))>2
148 elif ( len(self.input_buffer.split('\n'))>2
149 and not re.findall(r"\n[\t ]*\n[\t ]*$", string)):
149 and not re.findall(r"\n[\t ]*\n[\t ]*$", string)):
150 return False
150 return False
151 else:
151 else:
152 self.capture_output()
152 self.capture_output()
153 try:
153 try:
154 # Add line returns here, to make sure that the statement is
154 # Add line returns here, to make sure that the statement is
155 # complete (except if '\' was used).
155 # complete (except if '\' was used).
156 # This should probably be done in a different place (like
156 # This should probably be done in a different place (like
157 # maybe 'prefilter_input' method? For now, this works.
157 # maybe 'prefilter_input' method? For now, this works.
158 clean_string = string.rstrip('\n')
158 clean_string = string.rstrip('\n')
159 if not clean_string.endswith('\\'): clean_string +='\n\n'
159 if not clean_string.endswith('\\'): clean_string +='\n\n'
160 is_complete = codeop.compile_command(clean_string,
160 is_complete = codeop.compile_command(clean_string,
161 "<string>", "exec")
161 "<string>", "exec")
162 self.release_output()
162 self.release_output()
163 except Exception, e:
163 except Exception, e:
164 # XXX: Hack: return True so that the
164 # XXX: Hack: return True so that the
165 # code gets executed and the error captured.
165 # code gets executed and the error captured.
166 is_complete = True
166 is_complete = True
167 return is_complete
167 return is_complete
168
168
169
169
170 def write(self, string, refresh=True):
170 def write(self, string, refresh=True):
171 """ Write some characters to the display.
171 """ Write some characters to the display.
172
172
173 Subclass should overide this method.
173 Subclass should overide this method.
174
174
175 The refresh keyword argument is used in frontends with an
175 The refresh keyword argument is used in frontends with an
176 event loop, to choose whether the write should trigget an UI
176 event loop, to choose whether the write should trigget an UI
177 refresh, and thus be syncrhonous, or not.
177 refresh, and thus be syncrhonous, or not.
178 """
178 """
179 print >>sys.__stderr__, string
179 print >>sys.__stderr__, string
180
180
181
181
182 def execute(self, python_string, raw_string=None):
182 def execute(self, python_string, raw_string=None):
183 """ Stores the raw_string in the history, and sends the
183 """ Stores the raw_string in the history, and sends the
184 python string to the interpreter.
184 python string to the interpreter.
185 """
185 """
186 if raw_string is None:
186 if raw_string is None:
187 raw_string = python_string
187 raw_string = python_string
188 # Create a false result, in case there is an exception
188 # Create a false result, in case there is an exception
189 self.last_result = dict(number=self.prompt_number)
189 self.last_result = dict(number=self.prompt_number)
190
190
191 try:
191 try:
192 try:
192 try:
193 self.history.input_cache[-1] = raw_string.rstrip()
193 self.history.input_cache[-1] = raw_string.rstrip()
194 result = self.shell.execute(python_string)
194 result = self.shell.execute(python_string)
195 self.last_result = result
195 self.last_result = result
196 self.render_result(result)
196 self.render_result(result)
197 except:
197 except:
198 self.show_traceback()
198 self.show_traceback()
199 finally:
199 finally:
200 self.after_execute()
200 self.after_execute()
201
201
202
202
203 #--------------------------------------------------------------------------
203 #--------------------------------------------------------------------------
204 # LineFrontEndBase interface
204 # LineFrontEndBase interface
205 #--------------------------------------------------------------------------
205 #--------------------------------------------------------------------------
206
206
207 def prefilter_input(self, string):
207 def prefilter_input(self, string):
208 """ Prefilter the input to turn it in valid python.
208 """ Prefilter the input to turn it in valid python.
209 """
209 """
210 string = string.replace('\r\n', '\n')
210 string = string.replace('\r\n', '\n')
211 string = string.replace('\t', 4*' ')
211 string = string.replace('\t', 4*' ')
212 # Clean the trailing whitespace
212 # Clean the trailing whitespace
213 string = '\n'.join(l.rstrip() for l in string.split('\n'))
213 string = '\n'.join(l.rstrip() for l in string.split('\n'))
214 return string
214 return string
215
215
216
216
217 def after_execute(self):
217 def after_execute(self):
218 """ All the operations required after an execution to put the
218 """ All the operations required after an execution to put the
219 terminal back in a shape where it is usable.
219 terminal back in a shape where it is usable.
220 """
220 """
221 self.prompt_number += 1
221 self.prompt_number += 1
222 self.new_prompt(self.input_prompt_template.substitute(
222 self.new_prompt(self.input_prompt_template.substitute(
223 number=(self.last_result['number'] + 1)))
223 number=(self.last_result['number'] + 1)))
224 # Start a new empty history entry
224 # Start a new empty history entry
225 self._add_history(None, '')
225 self._add_history(None, '')
226 self.history_cursor = len(self.history.input_cache) - 1
226 self.history_cursor = len(self.history.input_cache) - 1
227
227
228
228
229 def complete_current_input(self):
229 def complete_current_input(self):
230 """ Do code completion on current line.
230 """ Do code completion on current line.
231 """
231 """
232 if self.debug:
232 if self.debug:
233 print >>sys.__stdout__, "complete_current_input",
233 print >>sys.__stdout__, "complete_current_input",
234 line = self.input_buffer
234 line = self.input_buffer
235 new_line, completions = self.complete(line)
235 new_line, completions = self.complete(line)
236 if len(completions)>1:
236 if len(completions)>1:
237 self.write_completion(completions, new_line=new_line)
237 self.write_completion(completions, new_line=new_line)
238 elif not line == new_line:
238 elif not line == new_line:
239 self.input_buffer = new_line
239 self.input_buffer = new_line
240 if self.debug:
240 if self.debug:
241 print >>sys.__stdout__, 'line', line
241 print >>sys.__stdout__, 'line', line
242 print >>sys.__stdout__, 'new_line', new_line
242 print >>sys.__stdout__, 'new_line', new_line
243 print >>sys.__stdout__, completions
243 print >>sys.__stdout__, completions
244
244
245
245
246 def get_line_width(self):
246 def get_line_width(self):
247 """ Return the width of the line in characters.
247 """ Return the width of the line in characters.
248 """
248 """
249 return 80
249 return 80
250
250
251
251
252 def write_completion(self, possibilities, new_line=None):
252 def write_completion(self, possibilities, new_line=None):
253 """ Write the list of possible completions.
253 """ Write the list of possible completions.
254
254
255 new_line is the completed input line that should be displayed
255 new_line is the completed input line that should be displayed
256 after the completion are writen. If None, the input_buffer
256 after the completion are writen. If None, the input_buffer
257 before the completion is used.
257 before the completion is used.
258 """
258 """
259 if new_line is None:
259 if new_line is None:
260 new_line = self.input_buffer
260 new_line = self.input_buffer
261
261
262 self.write('\n')
262 self.write('\n')
263 max_len = len(max(possibilities, key=len)) + 1
263 max_len = len(max(possibilities, key=len)) + 1
264
264
265 # Now we check how much symbol we can put on a line...
265 # Now we check how much symbol we can put on a line...
266 chars_per_line = self.get_line_width()
266 chars_per_line = self.get_line_width()
267 symbols_per_line = max(1, chars_per_line/max_len)
267 symbols_per_line = max(1, chars_per_line/max_len)
268
268
269 pos = 1
269 pos = 1
270 completion_string = []
270 completion_string = []
271 for symbol in possibilities:
271 for symbol in possibilities:
272 if pos < symbols_per_line:
272 if pos < symbols_per_line:
273 completion_string.append(symbol.ljust(max_len))
273 completion_string.append(symbol.ljust(max_len))
274 pos += 1
274 pos += 1
275 else:
275 else:
276 completion_string.append(symbol.rstrip() + '\n')
276 completion_string.append(symbol.rstrip() + '\n')
277 pos = 1
277 pos = 1
278 self.write(''.join(completion_string))
278 self.write(''.join(completion_string))
279 self.new_prompt(self.input_prompt_template.substitute(
279 self.new_prompt(self.input_prompt_template.substitute(
280 number=self.last_result['number'] + 1))
280 number=self.last_result['number'] + 1))
281 self.input_buffer = new_line
281 self.input_buffer = new_line
282
282
283
283
284 def new_prompt(self, prompt):
284 def new_prompt(self, prompt):
285 """ Prints a prompt and starts a new editing buffer.
285 """ Prints a prompt and starts a new editing buffer.
286
286
287 Subclasses should use this method to make sure that the
287 Subclasses should use this method to make sure that the
288 terminal is put in a state favorable for a new line
288 terminal is put in a state favorable for a new line
289 input.
289 input.
290 """
290 """
291 self.input_buffer = ''
291 self.input_buffer = ''
292 self.write(prompt)
292 self.write(prompt)
293
293
294
294
295 def continuation_prompt(self):
295 def continuation_prompt(self):
296 """Returns the current continuation prompt.
296 """Returns the current continuation prompt.
297 """
297 """
298 return ("."*(len(self.last_prompt)-2) + ': ')
298 return ("."*(len(self.last_prompt)-2) + ': ')
299
299
300
300
301 def execute_command(self, command, hidden=False):
301 def execute_command(self, command, hidden=False):
302 """ Execute a command, not only in the model, but also in the
302 """ Execute a command, not only in the model, but also in the
303 view, if any.
303 view, if any.
304 """
304 """
305 return self.shell.execute(command)
305 return self.shell.execute(command)
306
306
307 #--------------------------------------------------------------------------
307 #--------------------------------------------------------------------------
308 # Private API
308 # Private API
309 #--------------------------------------------------------------------------
309 #--------------------------------------------------------------------------
310
310
311 def _on_enter(self, new_line_pos=0):
311 def _on_enter(self, new_line_pos=0):
312 """ Called when the return key is pressed in a line editing
312 """ Called when the return key is pressed in a line editing
313 buffer.
313 buffer.
314
314
315 Parameters
315 Parameters
316 ----------
316 ----------
317 new_line_pos : integer, optional
317 new_line_pos : integer, optional
318 Position of the new line to add, starting from the
318 Position of the new line to add, starting from the
319 end (0 adds a new line after the last line, -1 before
319 end (0 adds a new line after the last line, -1 before
320 the last line...)
320 the last line...)
321
321
322 Returns
322 Returns
323 -------
323 -------
324 True if execution is triggered
324 True if execution is triggered
325 """
325 """
326 current_buffer = self.input_buffer
326 current_buffer = self.input_buffer
327 # XXX: This string replace is ugly, but there should be no way it
327 # XXX: This string replace is ugly, but there should be no way it
328 # fails.
328 # fails.
329 prompt_less_buffer = re.sub('^' + self.continuation_prompt(),
329 prompt_less_buffer = re.sub('^' + self.continuation_prompt(),
330 '', current_buffer).replace('\n' + self.continuation_prompt(),
330 '', current_buffer).replace('\n' + self.continuation_prompt(),
331 '\n')
331 '\n')
332 cleaned_buffer = self.prefilter_input(prompt_less_buffer)
332 cleaned_buffer = self.prefilter_input(prompt_less_buffer)
333 if self.is_complete(cleaned_buffer):
333 if self.is_complete(cleaned_buffer):
334 self.execute(cleaned_buffer, raw_string=current_buffer)
334 self.execute(cleaned_buffer, raw_string=current_buffer)
335 return True
335 return True
336 else:
336 else:
337 new_line_pos = -new_line_pos
337 new_line_pos = -new_line_pos
338 lines = current_buffer.split('\n')[:-1]
338 lines = current_buffer.split('\n')[:-1]
339 prompt_less_lines = prompt_less_buffer.split('\n')
339 prompt_less_lines = prompt_less_buffer.split('\n')
340 new_line = self.continuation_prompt() + \
340 new_line = self.continuation_prompt() + \
341 self._get_indent_string('\n'.join(
341 self._get_indent_string('\n'.join(
342 prompt_less_lines[:new_line_pos-1]))
342 prompt_less_lines[:new_line_pos-1]))
343 if len(lines) == 2:
343 if len(lines) == 1:
344 new_line += '\t'
344 new_line += '\t'
345 if current_buffer[:-1].split('\n')[-1].rstrip().endswith(':'):
345 elif current_buffer[:-1].split('\n')[-1].rstrip().endswith(':'):
346 new_line += '\t'
346 new_line += '\t'
347
347
348 if new_line_pos == 0:
348 if new_line_pos == 0:
349 lines.append(new_line)
349 lines.append(new_line)
350 else:
350 else:
351 lines.insert(new_line_pos, new_line)
351 lines.insert(new_line_pos, new_line)
352 self.input_buffer = '\n'.join(lines)
352 self.input_buffer = '\n'.join(lines)
353
353
354
354
355 def _get_indent_string(self, string):
355 def _get_indent_string(self, string):
356 """ Return the string of whitespace that prefixes a line. Used to
356 """ Return the string of whitespace that prefixes a line. Used to
357 add the right amount of indendation when creating a new line.
357 add the right amount of indendation when creating a new line.
358 """
358 """
359 string = string.replace('\t', ' '*4)
359 string = string.replace('\t', ' '*4)
360 string = string.split('\n')[-1]
360 string = string.split('\n')[-1]
361 indent_chars = len(string) - len(string.lstrip())
361 indent_chars = len(string) - len(string.lstrip())
362 indent_string = '\t'*(indent_chars // 4) + \
362 indent_string = '\t'*(indent_chars // 4) + \
363 ' '*(indent_chars % 4)
363 ' '*(indent_chars % 4)
364
364
365 return indent_string
365 return indent_string
366
366
367
367
General Comments 0
You need to be logged in to leave comments. Login now