##// END OF EJS Templates
More elegant approach to previous buglet.
gvaroquaux -
Show More
@@ -1,307 +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:
219 elif not line == new_line:
220 self.input_buffer = new_line
220 self.input_buffer = new_line
221 if self.debug:
221 if self.debug:
222 print >>sys.__stdout__, 'line', line
222 print >>sys.__stdout__, 'line', line
223 print >>sys.__stdout__, 'new_line', new_line
223 print >>sys.__stdout__, 'new_line', new_line
224 print >>sys.__stdout__, completions
224 print >>sys.__stdout__, completions
225
225
226
226
227 def get_line_width(self):
227 def get_line_width(self):
228 """ Return the width of the line in characters.
228 """ Return the width of the line in characters.
229 """
229 """
230 return 80
230 return 80
231
231
232
232
233 def write_completion(self, possibilities, new_line=None):
233 def write_completion(self, possibilities, new_line=None):
234 """ Write the list of possible completions.
234 """ Write the list of possible completions.
235
235
236 new_line is the completed input line that should be displayed
236 new_line is the completed input line that should be displayed
237 after the completion are writen. If None, the input_buffer
237 after the completion are writen. If None, the input_buffer
238 before the completion is used.
238 before the completion is used.
239 """
239 """
240 if new_line is None:
240 if new_line is None:
241 new_line = self.input_buffer
241 new_line = self.input_buffer
242
242
243 self.write('\n')
243 self.write('\n')
244 max_len = len(max(possibilities, key=len)) + 1
244 max_len = len(max(possibilities, key=len)) + 1
245
245
246 # 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...
247 chars_per_line = self.get_line_width()
247 chars_per_line = self.get_line_width()
248 symbols_per_line = max(1, chars_per_line/max_len)
248 symbols_per_line = max(1, chars_per_line/max_len)
249
249
250 pos = 1
250 pos = 1
251 buf = []
251 buf = []
252 for symbol in possibilities:
252 for symbol in possibilities:
253 if pos < symbols_per_line:
253 if pos < symbols_per_line:
254 buf.append(symbol.ljust(max_len))
254 buf.append(symbol.ljust(max_len))
255 pos += 1
255 pos += 1
256 else:
256 else:
257 buf.append(symbol.rstrip() + '\n')
257 buf.append(symbol.rstrip() + '\n')
258 pos = 1
258 pos = 1
259 self.write(''.join(buf))
259 self.write(''.join(buf))
260 self.new_prompt(self.input_prompt_template.substitute(
260 self.new_prompt(self.input_prompt_template.substitute(
261 number=self.last_result['number'] + 1))
261 number=self.last_result['number'] + 1))
262 self.input_buffer = new_line
262 self.input_buffer = new_line
263
263
264
264
265 def new_prompt(self, prompt):
265 def new_prompt(self, prompt):
266 """ Prints a prompt and starts a new editing buffer.
266 """ Prints a prompt and starts a new editing buffer.
267
267
268 Subclasses should use this method to make sure that the
268 Subclasses should use this method to make sure that the
269 terminal is put in a state favorable for a new line
269 terminal is put in a state favorable for a new line
270 input.
270 input.
271 """
271 """
272 self.input_buffer = ''
272 self.input_buffer = ''
273 self.write(prompt)
273 self.write(prompt)
274
274
275
275
276 #--------------------------------------------------------------------------
276 #--------------------------------------------------------------------------
277 # Private API
277 # Private API
278 #--------------------------------------------------------------------------
278 #--------------------------------------------------------------------------
279
279
280 def _on_enter(self):
280 def _on_enter(self):
281 """ Called when the return key is pressed in a line editing
281 """ Called when the return key is pressed in a line editing
282 buffer.
282 buffer.
283 """
283 """
284 current_buffer = self.input_buffer
284 current_buffer = self.input_buffer
285 cleaned_buffer = self.prefilter_input(current_buffer)
285 cleaned_buffer = self.prefilter_input(current_buffer)
286 if self.is_complete(cleaned_buffer):
286 if self.is_complete(cleaned_buffer):
287 self.execute(cleaned_buffer, raw_string=current_buffer)
287 self.execute(cleaned_buffer, raw_string=current_buffer)
288 else:
288 else:
289 self.input_buffer += self._get_indent_string(
289 self.input_buffer += self._get_indent_string(
290 current_buffer[:-1])
290 current_buffer[:-1])
291 if current_buffer[:-1].split('\n')[-1].rstrip().endswith(':'):
291 if current_buffer[:-1].split('\n')[-1].rstrip().endswith(':'):
292 self.input_buffer += '\t'
292 self.input_buffer += '\t'
293
293
294
294
295 def _get_indent_string(self, string):
295 def _get_indent_string(self, string):
296 """ Return the string of whitespace that prefixes a line. Used to
296 """ Return the string of whitespace that prefixes a line. Used to
297 add the right amount of indendation when creating a new line.
297 add the right amount of indendation when creating a new line.
298 """
298 """
299 string = string.replace('\t', ' '*4)
299 string = string.replace('\t', ' '*4)
300 string = string.split('\n')[-1]
300 string = string.split('\n')[-1]
301 indent_chars = len(string) - len(string.lstrip())
301 indent_chars = len(string) - len(string.lstrip())
302 indent_string = '\t'*(indent_chars // 4) + \
302 indent_string = '\t'*(indent_chars // 4) + \
303 ' '*(indent_chars % 4)
303 ' '*(indent_chars % 4)
304
304
305 return indent_string
305 return indent_string
306
306
307
307
General Comments 0
You need to be logged in to leave comments. Login now