##// END OF EJS Templates
ENH: Add continuation prompts
Gael Varoquaux -
Show More
@@ -1,337 +1,353 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 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 self.capture_output()
149 self.capture_output()
150 try:
150 try:
151 # Add line returns here, to make sure that the statement is
151 # Add line returns here, to make sure that the statement is
152 # complete.
152 # complete.
153 is_complete = codeop.compile_command(string.rstrip() + '\n\n',
153 is_complete = codeop.compile_command(string.rstrip() + '\n\n',
154 "<string>", "exec")
154 "<string>", "exec")
155 self.release_output()
155 self.release_output()
156 except Exception, e:
156 except Exception, e:
157 # XXX: Hack: return True so that the
157 # XXX: Hack: return True so that the
158 # code gets executed and the error captured.
158 # code gets executed and the error captured.
159 is_complete = True
159 is_complete = True
160 return is_complete
160 return is_complete
161
161
162
162
163 def write(self, string, refresh=True):
163 def write(self, string, refresh=True):
164 """ Write some characters to the display.
164 """ Write some characters to the display.
165
165
166 Subclass should overide this method.
166 Subclass should overide this method.
167
167
168 The refresh keyword argument is used in frontends with an
168 The refresh keyword argument is used in frontends with an
169 event loop, to choose whether the write should trigget an UI
169 event loop, to choose whether the write should trigget an UI
170 refresh, and thus be syncrhonous, or not.
170 refresh, and thus be syncrhonous, or not.
171 """
171 """
172 print >>sys.__stderr__, string
172 print >>sys.__stderr__, string
173
173
174
174
175 def execute(self, python_string, raw_string=None):
175 def execute(self, python_string, raw_string=None):
176 """ Stores the raw_string in the history, and sends the
176 """ Stores the raw_string in the history, and sends the
177 python string to the interpreter.
177 python string to the interpreter.
178 """
178 """
179 if raw_string is None:
179 if raw_string is None:
180 raw_string = python_string
180 raw_string = python_string
181 # Create a false result, in case there is an exception
181 # Create a false result, in case there is an exception
182 self.last_result = dict(number=self.prompt_number)
182 self.last_result = dict(number=self.prompt_number)
183
183
184 ## try:
184 ## try:
185 ## self.history.input_cache[-1] = raw_string.rstrip()
185 ## self.history.input_cache[-1] = raw_string.rstrip()
186 ## result = self.shell.execute(python_string)
186 ## result = self.shell.execute(python_string)
187 ## self.last_result = result
187 ## self.last_result = result
188 ## self.render_result(result)
188 ## self.render_result(result)
189 ## except:
189 ## except:
190 ## self.show_traceback()
190 ## self.show_traceback()
191 ## finally:
191 ## finally:
192 ## self.after_execute()
192 ## self.after_execute()
193
193
194 try:
194 try:
195 try:
195 try:
196 self.history.input_cache[-1] = raw_string.rstrip()
196 self.history.input_cache[-1] = raw_string.rstrip()
197 result = self.shell.execute(python_string)
197 result = self.shell.execute(python_string)
198 self.last_result = result
198 self.last_result = result
199 self.render_result(result)
199 self.render_result(result)
200 except:
200 except:
201 self.show_traceback()
201 self.show_traceback()
202 finally:
202 finally:
203 self.after_execute()
203 self.after_execute()
204
204
205
205
206 #--------------------------------------------------------------------------
206 #--------------------------------------------------------------------------
207 # LineFrontEndBase interface
207 # LineFrontEndBase interface
208 #--------------------------------------------------------------------------
208 #--------------------------------------------------------------------------
209
209
210 def prefilter_input(self, string):
210 def prefilter_input(self, string):
211 """ Prefilter the input to turn it in valid python.
211 """ Prefilter the input to turn it in valid python.
212 """
212 """
213 string = string.replace('\r\n', '\n')
213 string = string.replace('\r\n', '\n')
214 string = string.replace('\t', 4*' ')
214 string = string.replace('\t', 4*' ')
215 # Clean the trailing whitespace
215 # Clean the trailing whitespace
216 string = '\n'.join(l.rstrip() for l in string.split('\n'))
216 string = '\n'.join(l.rstrip() for l in string.split('\n'))
217 return string
217 return string
218
218
219
219
220 def after_execute(self):
220 def after_execute(self):
221 """ All the operations required after an execution to put the
221 """ All the operations required after an execution to put the
222 terminal back in a shape where it is usable.
222 terminal back in a shape where it is usable.
223 """
223 """
224 self.prompt_number += 1
224 self.prompt_number += 1
225 self.new_prompt(self.input_prompt_template.substitute(
225 self.new_prompt(self.input_prompt_template.substitute(
226 number=(self.last_result['number'] + 1)))
226 number=(self.last_result['number'] + 1)))
227 # Start a new empty history entry
227 # Start a new empty history entry
228 self._add_history(None, '')
228 self._add_history(None, '')
229 self.history_cursor = len(self.history.input_cache) - 1
229 self.history_cursor = len(self.history.input_cache) - 1
230
230
231
231
232 def complete_current_input(self):
232 def complete_current_input(self):
233 """ Do code completion on current line.
233 """ Do code completion on current line.
234 """
234 """
235 if self.debug:
235 if self.debug:
236 print >>sys.__stdout__, "complete_current_input",
236 print >>sys.__stdout__, "complete_current_input",
237 line = self.input_buffer
237 line = self.input_buffer
238 new_line, completions = self.complete(line)
238 new_line, completions = self.complete(line)
239 if len(completions)>1:
239 if len(completions)>1:
240 self.write_completion(completions, new_line=new_line)
240 self.write_completion(completions, new_line=new_line)
241 elif not line == new_line:
241 elif not line == new_line:
242 self.input_buffer = new_line
242 self.input_buffer = new_line
243 if self.debug:
243 if self.debug:
244 print >>sys.__stdout__, 'line', line
244 print >>sys.__stdout__, 'line', line
245 print >>sys.__stdout__, 'new_line', new_line
245 print >>sys.__stdout__, 'new_line', new_line
246 print >>sys.__stdout__, completions
246 print >>sys.__stdout__, completions
247
247
248
248
249 def get_line_width(self):
249 def get_line_width(self):
250 """ Return the width of the line in characters.
250 """ Return the width of the line in characters.
251 """
251 """
252 return 80
252 return 80
253
253
254
254
255 def write_completion(self, possibilities, new_line=None):
255 def write_completion(self, possibilities, new_line=None):
256 """ Write the list of possible completions.
256 """ Write the list of possible completions.
257
257
258 new_line is the completed input line that should be displayed
258 new_line is the completed input line that should be displayed
259 after the completion are writen. If None, the input_buffer
259 after the completion are writen. If None, the input_buffer
260 before the completion is used.
260 before the completion is used.
261 """
261 """
262 if new_line is None:
262 if new_line is None:
263 new_line = self.input_buffer
263 new_line = self.input_buffer
264
264
265 self.write('\n')
265 self.write('\n')
266 max_len = len(max(possibilities, key=len)) + 1
266 max_len = len(max(possibilities, key=len)) + 1
267
267
268 # Now we check how much symbol we can put on a line...
268 # Now we check how much symbol we can put on a line...
269 chars_per_line = self.get_line_width()
269 chars_per_line = self.get_line_width()
270 symbols_per_line = max(1, chars_per_line/max_len)
270 symbols_per_line = max(1, chars_per_line/max_len)
271
271
272 pos = 1
272 pos = 1
273 buf = []
273 buf = []
274 for symbol in possibilities:
274 for symbol in possibilities:
275 if pos < symbols_per_line:
275 if pos < symbols_per_line:
276 buf.append(symbol.ljust(max_len))
276 buf.append(symbol.ljust(max_len))
277 pos += 1
277 pos += 1
278 else:
278 else:
279 buf.append(symbol.rstrip() + '\n')
279 buf.append(symbol.rstrip() + '\n')
280 pos = 1
280 pos = 1
281 self.write(''.join(buf))
281 self.write(''.join(buf))
282 self.new_prompt(self.input_prompt_template.substitute(
282 self.new_prompt(self.input_prompt_template.substitute(
283 number=self.last_result['number'] + 1))
283 number=self.last_result['number'] + 1))
284 self.input_buffer = new_line
284 self.input_buffer = new_line
285
285
286
286
287 def new_prompt(self, prompt):
287 def new_prompt(self, prompt):
288 """ Prints a prompt and starts a new editing buffer.
288 """ Prints a prompt and starts a new editing buffer.
289
289
290 Subclasses should use this method to make sure that the
290 Subclasses should use this method to make sure that the
291 terminal is put in a state favorable for a new line
291 terminal is put in a state favorable for a new line
292 input.
292 input.
293 """
293 """
294 self.input_buffer = ''
294 self.input_buffer = ''
295 self.write(prompt)
295 self.write(prompt)
296
296
297
297
298 def continuation_prompt(self):
299 """Returns the current continuation prompt.
300 Overridden to generate a continuation prompt matching the length of the
301 current prompt."""
302
303 # FIXME: This is a bad hack.. I need to find a way to use the 'Prompt2'
304 # class in IPython/kernel/prompts.py. Basically, I am trying to get the
305 # length of the current prompt ("In ['number']").
306 return ("."*(5+len(str(self.last_result['number']))) + ':')
307
308
298 def execute_command(self, command, hidden=False):
309 def execute_command(self, command, hidden=False):
299 """ Execute a command, not only in the model, but also in the
310 """ Execute a command, not only in the model, but also in the
300 view, if any.
311 view, if any.
301 """
312 """
302 return self.shell.execute(command)
313 return self.shell.execute(command)
303
314
304 #--------------------------------------------------------------------------
315 #--------------------------------------------------------------------------
305 # Private API
316 # Private API
306 #--------------------------------------------------------------------------
317 #--------------------------------------------------------------------------
307
318
308 def _on_enter(self):
319 def _on_enter(self):
309 """ Called when the return key is pressed in a line editing
320 """ Called when the return key is pressed in a line editing
310 buffer.
321 buffer.
311 """
322 """
312 current_buffer = self.input_buffer
323 current_buffer = self.input_buffer
313 cleaned_buffer = self.prefilter_input(current_buffer)
324 # XXX: This string replace is ugly, but there should be no way it
325 # fails.
326 prompt_less_buffer = re.sub('^' + self.continuation_prompt(),
327 '', current_buffer).replace('\n' + self.continuation_prompt(),
328 '\n')
329 cleaned_buffer = self.prefilter_input(prompt_less_buffer)
314 if self.is_complete(cleaned_buffer):
330 if self.is_complete(cleaned_buffer):
315 self.execute(cleaned_buffer, raw_string=current_buffer)
331 self.execute(cleaned_buffer, raw_string=current_buffer)
316 else:
332 else:
317 self.input_buffer += self._get_indent_string(
333 self.input_buffer += self.continuation_prompt() + \
318 current_buffer[:-1])
334 self._get_indent_string(prompt_less_buffer[:-1])
319 if len(current_buffer.split('\n')) == 2:
335 if len(current_buffer.split('\n')) == 2:
320 self.input_buffer += '\t\t'
336 self.input_buffer += '\t'
321 if current_buffer[:-1].split('\n')[-1].rstrip().endswith(':'):
337 if current_buffer[:-1].split('\n')[-1].rstrip().endswith(':'):
322 self.input_buffer += '\t'
338 self.input_buffer += '\t'
323
339
324
340
325 def _get_indent_string(self, string):
341 def _get_indent_string(self, string):
326 """ Return the string of whitespace that prefixes a line. Used to
342 """ Return the string of whitespace that prefixes a line. Used to
327 add the right amount of indendation when creating a new line.
343 add the right amount of indendation when creating a new line.
328 """
344 """
329 string = string.replace('\t', ' '*4)
345 string = string.replace('\t', ' '*4)
330 string = string.split('\n')[-1]
346 string = string.split('\n')[-1]
331 indent_chars = len(string) - len(string.lstrip())
347 indent_chars = len(string) - len(string.lstrip())
332 indent_string = '\t'*(indent_chars // 4) + \
348 indent_string = '\t'*(indent_chars // 4) + \
333 ' '*(indent_chars % 4)
349 ' '*(indent_chars % 4)
334
350
335 return indent_string
351 return indent_string
336
352
337
353
@@ -1,257 +1,275 b''
1 """
1 """
2 Frontend class that uses IPython0 to prefilter the inputs.
2 Frontend class that uses IPython0 to prefilter the inputs.
3
3
4 Using the IPython0 mechanism gives us access to the magics.
4 Using the IPython0 mechanism gives us access to the magics.
5
5
6 This is a transitory class, used here to do the transition between
6 This is a transitory class, used here to do the transition between
7 ipython0 and ipython1. This class is meant to be short-lived as more
7 ipython0 and ipython1. This class is meant to be short-lived as more
8 functionnality is abstracted out of ipython0 in reusable functions and
8 functionnality is abstracted out of ipython0 in reusable functions and
9 is added on the interpreter. This class can be a used to guide this
9 is added on the interpreter. This class can be a used to guide this
10 refactoring.
10 refactoring.
11 """
11 """
12 __docformat__ = "restructuredtext en"
12 __docformat__ = "restructuredtext en"
13
13
14 #-------------------------------------------------------------------------------
14 #-------------------------------------------------------------------------------
15 # Copyright (C) 2008 The IPython Development Team
15 # Copyright (C) 2008 The IPython Development Team
16 #
16 #
17 # Distributed under the terms of the BSD License. The full license is in
17 # Distributed under the terms of the BSD License. The full license is in
18 # the file COPYING, distributed as part of this software.
18 # the file COPYING, distributed as part of this software.
19 #-------------------------------------------------------------------------------
19 #-------------------------------------------------------------------------------
20
20
21 #-------------------------------------------------------------------------------
21 #-------------------------------------------------------------------------------
22 # Imports
22 # Imports
23 #-------------------------------------------------------------------------------
23 #-------------------------------------------------------------------------------
24 import sys
24 import sys
25 import pydoc
25 import pydoc
26 import os
26 import os
27 import re
27 import __builtin__
28 import __builtin__
28
29
29 from IPython.ipmaker import make_IPython
30 from IPython.ipmaker import make_IPython
30 from IPython.ipapi import IPApi
31 from IPython.ipapi import IPApi
31 from IPython.kernel.core.redirector_output_trap import RedirectorOutputTrap
32 from IPython.kernel.core.redirector_output_trap import RedirectorOutputTrap
32
33
33 from IPython.kernel.core.sync_traceback_trap import SyncTracebackTrap
34 from IPython.kernel.core.sync_traceback_trap import SyncTracebackTrap
34
35
35 from IPython.genutils import Term
36 from IPython.genutils import Term
36
37
37 from linefrontendbase import LineFrontEndBase, common_prefix
38 from linefrontendbase import LineFrontEndBase, common_prefix
38
39
39
40
40 def mk_system_call(system_call_function, command):
41 def mk_system_call(system_call_function, command):
41 """ given a os.system replacement, and a leading string command,
42 """ given a os.system replacement, and a leading string command,
42 returns a function that will execute the command with the given
43 returns a function that will execute the command with the given
43 argument string.
44 argument string.
44 """
45 """
45 def my_system_call(args):
46 def my_system_call(args):
46 system_call_function("%s %s" % (command, args))
47 system_call_function("%s %s" % (command, args))
47
48
48 my_system_call.__doc__ = "Calls %s" % command
49 my_system_call.__doc__ = "Calls %s" % command
49 return my_system_call
50 return my_system_call
50
51
51 #-------------------------------------------------------------------------------
52 #-------------------------------------------------------------------------------
52 # Frontend class using ipython0 to do the prefiltering.
53 # Frontend class using ipython0 to do the prefiltering.
53 #-------------------------------------------------------------------------------
54 #-------------------------------------------------------------------------------
54 class PrefilterFrontEnd(LineFrontEndBase):
55 class PrefilterFrontEnd(LineFrontEndBase):
55 """ Class that uses ipython0 to do prefilter the input, do the
56 """ Class that uses ipython0 to do prefilter the input, do the
56 completion and the magics.
57 completion and the magics.
57
58
58 The core trick is to use an ipython0 instance to prefilter the
59 The core trick is to use an ipython0 instance to prefilter the
59 input, and share the namespace between the interpreter instance used
60 input, and share the namespace between the interpreter instance used
60 to execute the statements and the ipython0 used for code
61 to execute the statements and the ipython0 used for code
61 completion...
62 completion...
62 """
63 """
63
64
64 debug = False
65 debug = False
65
66
66 def __init__(self, ipython0=None, *args, **kwargs):
67 def __init__(self, ipython0=None, *args, **kwargs):
67 """ Parameters:
68 """ Parameters:
68 -----------
69 -----------
69
70
70 ipython0: an optional ipython0 instance to use for command
71 ipython0: an optional ipython0 instance to use for command
71 prefiltering and completion.
72 prefiltering and completion.
72 """
73 """
73 LineFrontEndBase.__init__(self, *args, **kwargs)
74 LineFrontEndBase.__init__(self, *args, **kwargs)
74 self.shell.output_trap = RedirectorOutputTrap(
75 self.shell.output_trap = RedirectorOutputTrap(
75 out_callback=self.write,
76 out_callback=self.write,
76 err_callback=self.write,
77 err_callback=self.write,
77 )
78 )
78 self.shell.traceback_trap = SyncTracebackTrap(
79 self.shell.traceback_trap = SyncTracebackTrap(
79 formatters=self.shell.traceback_trap.formatters,
80 formatters=self.shell.traceback_trap.formatters,
80 )
81 )
81
82
82 # Start the ipython0 instance:
83 # Start the ipython0 instance:
83 self.save_output_hooks()
84 self.save_output_hooks()
84 if ipython0 is None:
85 if ipython0 is None:
85 # Instanciate an IPython0 interpreter to be able to use the
86 # Instanciate an IPython0 interpreter to be able to use the
86 # prefiltering.
87 # prefiltering.
87 # Suppress all key input, to avoid waiting
88 # Suppress all key input, to avoid waiting
88 def my_rawinput(x=None):
89 def my_rawinput(x=None):
89 return '\n'
90 return '\n'
90 old_rawinput = __builtin__.raw_input
91 old_rawinput = __builtin__.raw_input
91 __builtin__.raw_input = my_rawinput
92 __builtin__.raw_input = my_rawinput
92 # XXX: argv=[] is a bit bold.
93 # XXX: argv=[] is a bit bold.
93 ipython0 = make_IPython(argv=[],
94 ipython0 = make_IPython(argv=[],
94 user_ns=self.shell.user_ns,
95 user_ns=self.shell.user_ns,
95 user_global_ns=self.shell.user_global_ns)
96 user_global_ns=self.shell.user_global_ns)
96 __builtin__.raw_input = old_rawinput
97 __builtin__.raw_input = old_rawinput
97 self.ipython0 = ipython0
98 self.ipython0 = ipython0
98 # Set the pager:
99 # Set the pager:
99 self.ipython0.set_hook('show_in_pager',
100 self.ipython0.set_hook('show_in_pager',
100 lambda s, string: self.write("\n" + string))
101 lambda s, string: self.write("\n" + string))
101 self.ipython0.write = self.write
102 self.ipython0.write = self.write
102 self._ip = _ip = IPApi(self.ipython0)
103 self._ip = _ip = IPApi(self.ipython0)
103 # Make sure the raw system call doesn't get called, as we don't
104 # Make sure the raw system call doesn't get called, as we don't
104 # have a stdin accessible.
105 # have a stdin accessible.
105 self._ip.system = self.system_call
106 self._ip.system = self.system_call
106 # XXX: Muck around with magics so that they work better
107 # XXX: Muck around with magics so that they work better
107 # in our environment
108 # in our environment
108 if not sys.platform.startswith('win'):
109 if not sys.platform.startswith('win'):
109 self.ipython0.magic_ls = mk_system_call(self.system_call,
110 self.ipython0.magic_ls = mk_system_call(self.system_call,
110 'ls -CF')
111 'ls -CF')
111 # And now clean up the mess created by ipython0
112 # And now clean up the mess created by ipython0
112 self.release_output()
113 self.release_output()
113
114
114
115
115 if not 'banner' in kwargs and self.banner is None:
116 if not 'banner' in kwargs and self.banner is None:
116 self.banner = self.ipython0.BANNER + """
117 self.banner = self.ipython0.BANNER
117 This is the wx frontend, by Gael Varoquaux. This is EXPERIMENTAL code."""
118
118
119 # FIXME: __init__ and start should be two different steps
119 self.start()
120 self.start()
120
121
121 #--------------------------------------------------------------------------
122 #--------------------------------------------------------------------------
122 # FrontEndBase interface
123 # FrontEndBase interface
123 #--------------------------------------------------------------------------
124 #--------------------------------------------------------------------------
124
125
125 def show_traceback(self):
126 def show_traceback(self):
126 """ Use ipython0 to capture the last traceback and display it.
127 """ Use ipython0 to capture the last traceback and display it.
127 """
128 """
128 # Don't do the capture; the except_hook has already done some
129 # Don't do the capture; the except_hook has already done some
129 # modifications to the IO streams, if we store them, we'll be
130 # modifications to the IO streams, if we store them, we'll be
130 # storing the wrong ones.
131 # storing the wrong ones.
131 #self.capture_output()
132 #self.capture_output()
132 self.ipython0.showtraceback(tb_offset=-1)
133 self.ipython0.showtraceback(tb_offset=-1)
133 self.release_output()
134 self.release_output()
134
135
135
136
136 def execute(self, python_string, raw_string=None):
137 def execute(self, python_string, raw_string=None):
137 if self.debug:
138 if self.debug:
138 print 'Executing Python code:', repr(python_string)
139 print 'Executing Python code:', repr(python_string)
139 self.capture_output()
140 self.capture_output()
140 LineFrontEndBase.execute(self, python_string,
141 LineFrontEndBase.execute(self, python_string,
141 raw_string=raw_string)
142 raw_string=raw_string)
142 self.release_output()
143 self.release_output()
143
144
144
145
145 def save_output_hooks(self):
146 def save_output_hooks(self):
146 """ Store all the output hooks we can think of, to be able to
147 """ Store all the output hooks we can think of, to be able to
147 restore them.
148 restore them.
148
149
149 We need to do this early, as starting the ipython0 instance will
150 We need to do this early, as starting the ipython0 instance will
150 screw ouput hooks.
151 screw ouput hooks.
151 """
152 """
152 self.__old_cout_write = Term.cout.write
153 self.__old_cout_write = Term.cout.write
153 self.__old_cerr_write = Term.cerr.write
154 self.__old_cerr_write = Term.cerr.write
154 self.__old_stdout = sys.stdout
155 self.__old_stdout = sys.stdout
155 self.__old_stderr= sys.stderr
156 self.__old_stderr= sys.stderr
156 self.__old_help_output = pydoc.help.output
157 self.__old_help_output = pydoc.help.output
157 self.__old_display_hook = sys.displayhook
158 self.__old_display_hook = sys.displayhook
158
159
159
160
160 def capture_output(self):
161 def capture_output(self):
161 """ Capture all the output mechanisms we can think of.
162 """ Capture all the output mechanisms we can think of.
162 """
163 """
163 self.save_output_hooks()
164 self.save_output_hooks()
164 Term.cout.write = self.write
165 Term.cout.write = self.write
165 Term.cerr.write = self.write
166 Term.cerr.write = self.write
166 sys.stdout = Term.cout
167 sys.stdout = Term.cout
167 sys.stderr = Term.cerr
168 sys.stderr = Term.cerr
168 pydoc.help.output = self.shell.output_trap.out
169 pydoc.help.output = self.shell.output_trap.out
169
170
170
171
171 def release_output(self):
172 def release_output(self):
172 """ Release all the different captures we have made.
173 """ Release all the different captures we have made.
173 """
174 """
174 Term.cout.write = self.__old_cout_write
175 Term.cout.write = self.__old_cout_write
175 Term.cerr.write = self.__old_cerr_write
176 Term.cerr.write = self.__old_cerr_write
176 sys.stdout = self.__old_stdout
177 sys.stdout = self.__old_stdout
177 sys.stderr = self.__old_stderr
178 sys.stderr = self.__old_stderr
178 pydoc.help.output = self.__old_help_output
179 pydoc.help.output = self.__old_help_output
179 sys.displayhook = self.__old_display_hook
180 sys.displayhook = self.__old_display_hook
180
181
181
182
182 def complete(self, line):
183 def complete(self, line):
183 # FIXME: This should be factored out in the linefrontendbase
184 # FIXME: This should be factored out in the linefrontendbase
184 # method.
185 # method.
186 word = self._get_completion_text(line)
185 word = line.split('\n')[-1].split(' ')[-1]
187 word = line.split('\n')[-1].split(' ')[-1]
188 print 'Completion', word
186 completions = self.ipython0.complete(word)
189 completions = self.ipython0.complete(word)
187 # FIXME: The proper sort should be done in the complete method.
190 # FIXME: The proper sort should be done in the complete method.
188 key = lambda x: x.replace('_', '')
191 key = lambda x: x.replace('_', '')
189 completions.sort(key=key)
192 completions.sort(key=key)
190 if completions:
193 if completions:
191 prefix = common_prefix(completions)
194 prefix = common_prefix(completions)
192 line = line[:-len(word)] + prefix
195 line = line[:-len(word)] + prefix
193 return line, completions
196 return line, completions
194
197
195
198
196 #--------------------------------------------------------------------------
199 #--------------------------------------------------------------------------
197 # LineFrontEndBase interface
200 # LineFrontEndBase interface
198 #--------------------------------------------------------------------------
201 #--------------------------------------------------------------------------
199
202
200 def prefilter_input(self, input_string):
203 def prefilter_input(self, input_string):
201 """ Using IPython0 to prefilter the commands to turn them
204 """ Using IPython0 to prefilter the commands to turn them
202 in executable statements that are valid Python strings.
205 in executable statements that are valid Python strings.
203 """
206 """
204 input_string = LineFrontEndBase.prefilter_input(self, input_string)
207 input_string = LineFrontEndBase.prefilter_input(self, input_string)
205 filtered_lines = []
208 filtered_lines = []
206 # The IPython0 prefilters sometime produce output. We need to
209 # The IPython0 prefilters sometime produce output. We need to
207 # capture it.
210 # capture it.
208 self.capture_output()
211 self.capture_output()
209 self.last_result = dict(number=self.prompt_number)
212 self.last_result = dict(number=self.prompt_number)
210
213
211 ## try:
214 ## try:
212 ## for line in input_string.split('\n'):
215 ## for line in input_string.split('\n'):
213 ## filtered_lines.append(
216 ## filtered_lines.append(
214 ## self.ipython0.prefilter(line, False).rstrip())
217 ## self.ipython0.prefilter(line, False).rstrip())
215 ## except:
218 ## except:
216 ## # XXX: probably not the right thing to do.
219 ## # XXX: probably not the right thing to do.
217 ## self.ipython0.showsyntaxerror()
220 ## self.ipython0.showsyntaxerror()
218 ## self.after_execute()
221 ## self.after_execute()
219 ## finally:
222 ## finally:
220 ## self.release_output()
223 ## self.release_output()
221
224
222
225
223 try:
226 try:
224 try:
227 try:
225 for line in input_string.split('\n'):
228 for line in input_string.split('\n'):
226 filtered_lines.append(
229 filtered_lines.append(
227 self.ipython0.prefilter(line, False).rstrip())
230 self.ipython0.prefilter(line, False).rstrip())
228 except:
231 except:
229 # XXX: probably not the right thing to do.
232 # XXX: probably not the right thing to do.
230 self.ipython0.showsyntaxerror()
233 self.ipython0.showsyntaxerror()
231 self.after_execute()
234 self.after_execute()
232 finally:
235 finally:
233 self.release_output()
236 self.release_output()
234
237
235
238
236
239
237 # Clean up the trailing whitespace, to avoid indentation errors
240 # Clean up the trailing whitespace, to avoid indentation errors
238 filtered_string = '\n'.join(filtered_lines)
241 filtered_string = '\n'.join(filtered_lines)
239 return filtered_string
242 return filtered_string
240
243
241
244
242 #--------------------------------------------------------------------------
245 #--------------------------------------------------------------------------
243 # PrefilterFrontEnd interface
246 # PrefilterFrontEnd interface
244 #--------------------------------------------------------------------------
247 #--------------------------------------------------------------------------
245
248
246 def system_call(self, command_string):
249 def system_call(self, command_string):
247 """ Allows for frontend to define their own system call, to be
250 """ Allows for frontend to define their own system call, to be
248 able capture output and redirect input.
251 able capture output and redirect input.
249 """
252 """
250 return os.system(command_string)
253 return os.system(command_string)
251
254
252
255
253 def do_exit(self):
256 def do_exit(self):
254 """ Exit the shell, cleanup and save the history.
257 """ Exit the shell, cleanup and save the history.
255 """
258 """
256 self.ipython0.atexit_operations()
259 self.ipython0.atexit_operations()
257
260
261
262 def _get_completion_text(self, line):
263 """ Returns the text to be completed by breaking the line at specified
264 delimiters.
265 """
266 # Break at: spaces, '=', all parentheses (except if balanced).
267 # FIXME2: In the future, we need to make the implementation similar to
268 # that in the 'pyreadline' module (modes/basemode.py) where we break at
269 # each delimiter and try to complete the residual line, until we get a
270 # successful list of completions.
271 expression = '\s|=|,|:|\((?!.*\))|\[(?!.*\])|\{(?!.*\})'
272 complete_sep = re.compile(expression)
273 text = complete_sep.split(line)[-1]
274 return text
275
@@ -1,436 +1,482 b''
1 # encoding: utf-8
1 # encoding: utf-8
2 """
2 """
3 A Wx widget to act as a console and input commands.
3 A Wx widget to act as a console and input commands.
4
4
5 This widget deals with prompts and provides an edit buffer
5 This widget deals with prompts and provides an edit buffer
6 restricted to after the last prompt.
6 restricted to after the last prompt.
7 """
7 """
8
8
9 __docformat__ = "restructuredtext en"
9 __docformat__ = "restructuredtext en"
10
10
11 #-------------------------------------------------------------------------------
11 #-------------------------------------------------------------------------------
12 # Copyright (C) 2008 The IPython Development Team
12 # Copyright (C) 2008 The IPython Development Team
13 #
13 #
14 # Distributed under the terms of the BSD License. The full license is
14 # Distributed under the terms of the BSD License. The full license is
15 # in the file COPYING, distributed as part of this software.
15 # in the file COPYING, distributed as part of this software.
16 #-------------------------------------------------------------------------------
16 #-------------------------------------------------------------------------------
17
17
18 #-------------------------------------------------------------------------------
18 #-------------------------------------------------------------------------------
19 # Imports
19 # Imports
20 #-------------------------------------------------------------------------------
20 #-------------------------------------------------------------------------------
21
21
22 import wx
22 import wx
23 import wx.stc as stc
23 import wx.stc as stc
24
24
25 from wx.py import editwindow
25 from wx.py import editwindow
26 import time
26 import time
27 import sys
27 import sys
28 LINESEP = '\n'
28 LINESEP = '\n'
29 if sys.platform == 'win32':
29 if sys.platform == 'win32':
30 LINESEP = '\n\r'
30 LINESEP = '\n\r'
31
31
32 import re
32 import re
33
33
34 # FIXME: Need to provide an API for non user-generated display on the
34 # FIXME: Need to provide an API for non user-generated display on the
35 # screen: this should not be editable by the user.
35 # screen: this should not be editable by the user.
36
36
37 _DEFAULT_SIZE = 10
37 _DEFAULT_SIZE = 10
38 if sys.platform == 'darwin':
38 if sys.platform == 'darwin':
39 _DEFAULT_SIZE = 12
39 _DEFAULT_SIZE = 12
40
40
41 _DEFAULT_STYLE = {
41 _DEFAULT_STYLE = {
42 'stdout' : 'fore:#0000FF',
42 'stdout' : 'fore:#0000FF',
43 'stderr' : 'fore:#007f00',
43 'stderr' : 'fore:#007f00',
44 'trace' : 'fore:#FF0000',
44 'trace' : 'fore:#FF0000',
45
45
46 'default' : 'size:%d' % _DEFAULT_SIZE,
46 'default' : 'size:%d' % _DEFAULT_SIZE,
47 'bracegood' : 'fore:#00AA00,back:#000000,bold',
47 'bracegood' : 'fore:#00AA00,back:#000000,bold',
48 'bracebad' : 'fore:#FF0000,back:#000000,bold',
48 'bracebad' : 'fore:#FF0000,back:#000000,bold',
49
49
50 # properties for the various Python lexer styles
50 # properties for the various Python lexer styles
51 'comment' : 'fore:#007F00',
51 'comment' : 'fore:#007F00',
52 'number' : 'fore:#007F7F',
52 'number' : 'fore:#007F7F',
53 'string' : 'fore:#7F007F,italic',
53 'string' : 'fore:#7F007F,italic',
54 'char' : 'fore:#7F007F,italic',
54 'char' : 'fore:#7F007F,italic',
55 'keyword' : 'fore:#00007F,bold',
55 'keyword' : 'fore:#00007F,bold',
56 'triple' : 'fore:#7F0000',
56 'triple' : 'fore:#7F0000',
57 'tripledouble' : 'fore:#7F0000',
57 'tripledouble' : 'fore:#7F0000',
58 'class' : 'fore:#0000FF,bold,underline',
58 'class' : 'fore:#0000FF,bold,underline',
59 'def' : 'fore:#007F7F,bold',
59 'def' : 'fore:#007F7F,bold',
60 'operator' : 'bold'
60 'operator' : 'bold'
61 }
61 }
62
62
63 # new style numbers
63 # new style numbers
64 _STDOUT_STYLE = 15
64 _STDOUT_STYLE = 15
65 _STDERR_STYLE = 16
65 _STDERR_STYLE = 16
66 _TRACE_STYLE = 17
66 _TRACE_STYLE = 17
67
67
68
68
69 # system colors
69 # system colors
70 #SYS_COLOUR_BACKGROUND = wx.SystemSettings.GetColour(wx.SYS_COLOUR_BACKGROUND)
70 #SYS_COLOUR_BACKGROUND = wx.SystemSettings.GetColour(wx.SYS_COLOUR_BACKGROUND)
71
71
72 #-------------------------------------------------------------------------------
72 #-------------------------------------------------------------------------------
73 # The console widget class
73 # The console widget class
74 #-------------------------------------------------------------------------------
74 #-------------------------------------------------------------------------------
75 class ConsoleWidget(editwindow.EditWindow):
75 class ConsoleWidget(editwindow.EditWindow):
76 """ Specialized styled text control view for console-like workflow.
76 """ Specialized styled text control view for console-like workflow.
77
77
78 This widget is mainly interested in dealing with the prompt and
78 This widget is mainly interested in dealing with the prompt and
79 keeping the cursor inside the editing line.
79 keeping the cursor inside the editing line.
80 """
80 """
81
81
82 # This is where the title captured from the ANSI escape sequences are
82 # This is where the title captured from the ANSI escape sequences are
83 # stored.
83 # stored.
84 title = 'Console'
84 title = 'Console'
85
85
86 # The buffer being edited.
86 # The buffer being edited.
87 def _set_input_buffer(self, string):
87 def _set_input_buffer(self, string):
88 self.SetSelection(self.current_prompt_pos, self.GetLength())
88 self.SetSelection(self.current_prompt_pos, self.GetLength())
89 self.ReplaceSelection(string)
89 self.ReplaceSelection(string)
90 self.GotoPos(self.GetLength())
90 self.GotoPos(self.GetLength())
91
91
92 def _get_input_buffer(self):
92 def _get_input_buffer(self):
93 """ Returns the text in current edit buffer.
93 """ Returns the text in current edit buffer.
94 """
94 """
95 input_buffer = self.GetTextRange(self.current_prompt_pos,
95 input_buffer = self.GetTextRange(self.current_prompt_pos,
96 self.GetLength())
96 self.GetLength())
97 input_buffer = input_buffer.replace(LINESEP, '\n')
97 input_buffer = input_buffer.replace(LINESEP, '\n')
98 return input_buffer
98 return input_buffer
99
99
100 input_buffer = property(_get_input_buffer, _set_input_buffer)
100 input_buffer = property(_get_input_buffer, _set_input_buffer)
101
101
102 style = _DEFAULT_STYLE.copy()
102 style = _DEFAULT_STYLE.copy()
103
103
104 # Translation table from ANSI escape sequences to color. Override
104 # Translation table from ANSI escape sequences to color. Override
105 # this to specify your colors.
105 # this to specify your colors.
106 ANSI_STYLES = {'0;30': [0, 'BLACK'], '0;31': [1, 'RED'],
106 ANSI_STYLES = {'0;30': [0, 'BLACK'], '0;31': [1, 'RED'],
107 '0;32': [2, 'GREEN'], '0;33': [3, 'BROWN'],
107 '0;32': [2, 'GREEN'], '0;33': [3, 'BROWN'],
108 '0;34': [4, 'BLUE'], '0;35': [5, 'PURPLE'],
108 '0;34': [4, 'BLUE'], '0;35': [5, 'PURPLE'],
109 '0;36': [6, 'CYAN'], '0;37': [7, 'LIGHT GREY'],
109 '0;36': [6, 'CYAN'], '0;37': [7, 'LIGHT GREY'],
110 '1;30': [8, 'DARK GREY'], '1;31': [9, 'RED'],
110 '1;30': [8, 'DARK GREY'], '1;31': [9, 'RED'],
111 '1;32': [10, 'SEA GREEN'], '1;33': [11, 'YELLOW'],
111 '1;32': [10, 'SEA GREEN'], '1;33': [11, 'YELLOW'],
112 '1;34': [12, 'LIGHT BLUE'], '1;35':
112 '1;34': [12, 'LIGHT BLUE'], '1;35':
113 [13, 'MEDIUM VIOLET RED'],
113 [13, 'MEDIUM VIOLET RED'],
114 '1;36': [14, 'LIGHT STEEL BLUE'], '1;37': [15, 'YELLOW']}
114 '1;36': [14, 'LIGHT STEEL BLUE'], '1;37': [15, 'YELLOW']}
115
115
116 # The color of the carret (call _apply_style() after setting)
116 # The color of the carret (call _apply_style() after setting)
117 carret_color = 'BLACK'
117 carret_color = 'BLACK'
118
118
119 # Store the last time a refresh was done
119 # Store the last time a refresh was done
120 _last_refresh_time = 0
120 _last_refresh_time = 0
121
121
122 #--------------------------------------------------------------------------
122 #--------------------------------------------------------------------------
123 # Public API
123 # Public API
124 #--------------------------------------------------------------------------
124 #--------------------------------------------------------------------------
125
125
126 def __init__(self, parent, id=wx.ID_ANY, pos=wx.DefaultPosition,
126 def __init__(self, parent, id=wx.ID_ANY, pos=wx.DefaultPosition,
127 size=wx.DefaultSize, style=wx.WANTS_CHARS, ):
127 size=wx.DefaultSize, style=wx.WANTS_CHARS, ):
128 editwindow.EditWindow.__init__(self, parent, id, pos, size, style)
128 editwindow.EditWindow.__init__(self, parent, id, pos, size, style)
129 self._configure_scintilla()
129 self._configure_scintilla()
130
130
131 self.Bind(wx.EVT_KEY_DOWN, self._on_key_down)
131 self.Bind(wx.EVT_KEY_DOWN, self._on_key_down)
132 self.Bind(wx.EVT_KEY_UP, self._on_key_up)
132 self.Bind(wx.EVT_KEY_UP, self._on_key_up)
133
133
134
134
135 def write(self, text, refresh=True):
135 def write(self, text, refresh=True):
136 """ Write given text to buffer, while translating the ansi escape
136 """ Write given text to buffer, while translating the ansi escape
137 sequences.
137 sequences.
138 """
138 """
139 # XXX: do not put print statements to sys.stdout/sys.stderr in
139 # XXX: do not put print statements to sys.stdout/sys.stderr in
140 # this method, the print statements will call this method, as
140 # this method, the print statements will call this method, as
141 # you will end up with an infinit loop
141 # you will end up with an infinit loop
142 title = self.title_pat.split(text)
142 title = self.title_pat.split(text)
143 if len(title)>1:
143 if len(title)>1:
144 self.title = title[-2]
144 self.title = title[-2]
145
145
146 text = self.title_pat.sub('', text)
146 text = self.title_pat.sub('', text)
147 segments = self.color_pat.split(text)
147 segments = self.color_pat.split(text)
148 segment = segments.pop(0)
148 segment = segments.pop(0)
149 self.GotoPos(self.GetLength())
149 self.GotoPos(self.GetLength())
150 self.StartStyling(self.GetLength(), 0xFF)
150 self.StartStyling(self.GetLength(), 0xFF)
151 try:
151 try:
152 self.AppendText(segment)
152 self.AppendText(segment)
153 except UnicodeDecodeError:
153 except UnicodeDecodeError:
154 # XXX: Do I really want to skip the exception?
154 # XXX: Do I really want to skip the exception?
155 pass
155 pass
156
156
157 if segments:
157 if segments:
158 for ansi_tag, text in zip(segments[::2], segments[1::2]):
158 for ansi_tag, text in zip(segments[::2], segments[1::2]):
159 self.StartStyling(self.GetLength(), 0xFF)
159 self.StartStyling(self.GetLength(), 0xFF)
160 try:
160 try:
161 self.AppendText(text)
161 self.AppendText(text)
162 except UnicodeDecodeError:
162 except UnicodeDecodeError:
163 # XXX: Do I really want to skip the exception?
163 # XXX: Do I really want to skip the exception?
164 pass
164 pass
165
165
166 if ansi_tag not in self.ANSI_STYLES:
166 if ansi_tag not in self.ANSI_STYLES:
167 style = 0
167 style = 0
168 else:
168 else:
169 style = self.ANSI_STYLES[ansi_tag][0]
169 style = self.ANSI_STYLES[ansi_tag][0]
170
170
171 self.SetStyling(len(text), style)
171 self.SetStyling(len(text), style)
172
172
173 self.GotoPos(self.GetLength())
173 self.GotoPos(self.GetLength())
174 if refresh:
174 if refresh:
175 current_time = time.time()
175 current_time = time.time()
176 if current_time - self._last_refresh_time > 0.03:
176 if current_time - self._last_refresh_time > 0.03:
177 if sys.platform == 'win32':
177 if sys.platform == 'win32':
178 wx.SafeYield()
178 wx.SafeYield()
179 else:
179 else:
180 wx.Yield()
180 wx.Yield()
181 # self.ProcessEvent(wx.PaintEvent())
181 # self.ProcessEvent(wx.PaintEvent())
182 self._last_refresh_time = current_time
182 self._last_refresh_time = current_time
183
183
184
184
185 def new_prompt(self, prompt):
185 def new_prompt(self, prompt):
186 """ Prints a prompt at start of line, and move the start of the
186 """ Prints a prompt at start of line, and move the start of the
187 current block there.
187 current block there.
188
188
189 The prompt can be given with ascii escape sequences.
189 The prompt can be given with ascii escape sequences.
190 """
190 """
191 self.write(prompt, refresh=False)
191 self.write(prompt, refresh=False)
192 # now we update our cursor giving end of prompt
192 # now we update our cursor giving end of prompt
193 self.current_prompt_pos = self.GetLength()
193 self.current_prompt_pos = self.GetLength()
194 self.current_prompt_line = self.GetCurrentLine()
194 self.current_prompt_line = self.GetCurrentLine()
195 self.EnsureCaretVisible()
195 self.EnsureCaretVisible()
196
196
197
197
198 def scroll_to_bottom(self):
198 def scroll_to_bottom(self):
199 maxrange = self.GetScrollRange(wx.VERTICAL)
199 maxrange = self.GetScrollRange(wx.VERTICAL)
200 self.ScrollLines(maxrange)
200 self.ScrollLines(maxrange)
201
201
202
202
203 def pop_completion(self, possibilities, offset=0):
203 def pop_completion(self, possibilities, offset=0):
204 """ Pops up an autocompletion menu. Offset is the offset
204 """ Pops up an autocompletion menu. Offset is the offset
205 in characters of the position at which the menu should
205 in characters of the position at which the menu should
206 appear, relativ to the cursor.
206 appear, relativ to the cursor.
207 """
207 """
208 self.AutoCompSetIgnoreCase(False)
208 self.AutoCompSetIgnoreCase(False)
209 self.AutoCompSetAutoHide(False)
209 self.AutoCompSetAutoHide(False)
210 self.AutoCompSetMaxHeight(len(possibilities))
210 self.AutoCompSetMaxHeight(len(possibilities))
211 self.AutoCompShow(offset, " ".join(possibilities))
211 self.AutoCompShow(offset, " ".join(possibilities))
212
212
213
213
214 def get_line_width(self):
214 def get_line_width(self):
215 """ Return the width of the line in characters.
215 """ Return the width of the line in characters.
216 """
216 """
217 return self.GetSize()[0]/self.GetCharWidth()
217 return self.GetSize()[0]/self.GetCharWidth()
218
218
219
220 def clear_screen(self):
221 """ Empty completely the widget.
222 """
223 self.ClearAll()
224 self.new_prompt(self.input_prompt_template.substitute(
225 number=(self.last_result['number'] + 1)))
226
227
228
219 #--------------------------------------------------------------------------
229 #--------------------------------------------------------------------------
220 # EditWindow API
230 # EditWindow API
221 #--------------------------------------------------------------------------
231 #--------------------------------------------------------------------------
222
232
223 def OnUpdateUI(self, event):
233 def OnUpdateUI(self, event):
224 """ Override the OnUpdateUI of the EditWindow class, to prevent
234 """ Override the OnUpdateUI of the EditWindow class, to prevent
225 syntax highlighting both for faster redraw, and for more
235 syntax highlighting both for faster redraw, and for more
226 consistent look and feel.
236 consistent look and feel.
227 """
237 """
228
238
229 #--------------------------------------------------------------------------
239 #--------------------------------------------------------------------------
230 # Private API
240 # Private API
231 #--------------------------------------------------------------------------
241 #--------------------------------------------------------------------------
232
242
233 def _apply_style(self):
243 def _apply_style(self):
234 """ Applies the colors for the different text elements and the
244 """ Applies the colors for the different text elements and the
235 carret.
245 carret.
236 """
246 """
237 self.SetCaretForeground(self.carret_color)
247 self.SetCaretForeground(self.carret_color)
238
248
239 #self.StyleClearAll()
249 #self.StyleClearAll()
240 self.StyleSetSpec(stc.STC_STYLE_BRACELIGHT,
250 self.StyleSetSpec(stc.STC_STYLE_BRACELIGHT,
241 "fore:#FF0000,back:#0000FF,bold")
251 "fore:#FF0000,back:#0000FF,bold")
242 self.StyleSetSpec(stc.STC_STYLE_BRACEBAD,
252 self.StyleSetSpec(stc.STC_STYLE_BRACEBAD,
243 "fore:#000000,back:#FF0000,bold")
253 "fore:#000000,back:#FF0000,bold")
244
254
245 for style in self.ANSI_STYLES.values():
255 for style in self.ANSI_STYLES.values():
246 self.StyleSetSpec(style[0], "bold,fore:%s" % style[1])
256 self.StyleSetSpec(style[0], "bold,fore:%s" % style[1])
247
257
248
258
249 def _configure_scintilla(self):
259 def _configure_scintilla(self):
250 self.SetEOLMode(stc.STC_EOL_LF)
260 self.SetEOLMode(stc.STC_EOL_LF)
251
261
252 # Ctrl"+" or Ctrl "-" can be used to zoomin/zoomout the text inside
262 # Ctrl"+" or Ctrl "-" can be used to zoomin/zoomout the text inside
253 # the widget
263 # the widget
254 self.CmdKeyAssign(ord('+'), stc.STC_SCMOD_CTRL, stc.STC_CMD_ZOOMIN)
264 self.CmdKeyAssign(ord('+'), stc.STC_SCMOD_CTRL, stc.STC_CMD_ZOOMIN)
255 self.CmdKeyAssign(ord('-'), stc.STC_SCMOD_CTRL, stc.STC_CMD_ZOOMOUT)
265 self.CmdKeyAssign(ord('-'), stc.STC_SCMOD_CTRL, stc.STC_CMD_ZOOMOUT)
256 # Also allow Ctrl Shift "=" for poor non US keyboard users.
266 # Also allow Ctrl Shift "=" for poor non US keyboard users.
257 self.CmdKeyAssign(ord('='), stc.STC_SCMOD_CTRL|stc.STC_SCMOD_SHIFT,
267 self.CmdKeyAssign(ord('='), stc.STC_SCMOD_CTRL|stc.STC_SCMOD_SHIFT,
258 stc.STC_CMD_ZOOMIN)
268 stc.STC_CMD_ZOOMIN)
259
269
260 # Keys: we need to clear some of the keys the that don't play
270 # Keys: we need to clear some of the keys the that don't play
261 # well with a console.
271 # well with a console.
262 self.CmdKeyClear(ord('D'), stc.STC_SCMOD_CTRL)
272 self.CmdKeyClear(ord('D'), stc.STC_SCMOD_CTRL)
263 self.CmdKeyClear(ord('L'), stc.STC_SCMOD_CTRL)
273 self.CmdKeyClear(ord('L'), stc.STC_SCMOD_CTRL)
264 self.CmdKeyClear(ord('T'), stc.STC_SCMOD_CTRL)
274 self.CmdKeyClear(ord('T'), stc.STC_SCMOD_CTRL)
265 self.CmdKeyClear(ord('A'), stc.STC_SCMOD_CTRL)
275 self.CmdKeyClear(ord('A'), stc.STC_SCMOD_CTRL)
266
276
267 self.SetEOLMode(stc.STC_EOL_CRLF)
277 self.SetEOLMode(stc.STC_EOL_CRLF)
268 self.SetWrapMode(stc.STC_WRAP_CHAR)
278 self.SetWrapMode(stc.STC_WRAP_CHAR)
269 self.SetWrapMode(stc.STC_WRAP_WORD)
279 self.SetWrapMode(stc.STC_WRAP_WORD)
270 self.SetBufferedDraw(True)
280 self.SetBufferedDraw(True)
271 self.SetUseAntiAliasing(True)
281 self.SetUseAntiAliasing(True)
272 self.SetLayoutCache(stc.STC_CACHE_PAGE)
282 self.SetLayoutCache(stc.STC_CACHE_PAGE)
273 self.SetUndoCollection(False)
283 self.SetUndoCollection(False)
274 self.SetUseTabs(True)
284 self.SetUseTabs(True)
275 self.SetIndent(4)
285 self.SetIndent(4)
276 self.SetTabWidth(4)
286 self.SetTabWidth(4)
277
287
278 # we don't want scintilla's autocompletion to choose
288 # we don't want scintilla's autocompletion to choose
279 # automaticaly out of a single choice list, as we pop it up
289 # automaticaly out of a single choice list, as we pop it up
280 # automaticaly
290 # automaticaly
281 self.AutoCompSetChooseSingle(False)
291 self.AutoCompSetChooseSingle(False)
282 self.AutoCompSetMaxHeight(10)
292 self.AutoCompSetMaxHeight(10)
283 # XXX: this doesn't seem to have an effect.
293 # XXX: this doesn't seem to have an effect.
284 self.AutoCompSetFillUps('\n')
294 self.AutoCompSetFillUps('\n')
285
295
286 self.SetMargins(3, 3) #text is moved away from border with 3px
296 self.SetMargins(3, 3) #text is moved away from border with 3px
287 # Suppressing Scintilla margins
297 # Suppressing Scintilla margins
288 self.SetMarginWidth(0, 0)
298 self.SetMarginWidth(0, 0)
289 self.SetMarginWidth(1, 0)
299 self.SetMarginWidth(1, 0)
290 self.SetMarginWidth(2, 0)
300 self.SetMarginWidth(2, 0)
291
301
292 self._apply_style()
302 self._apply_style()
293
303
294 # Xterm escape sequences
304 # Xterm escape sequences
295 self.color_pat = re.compile('\x01?\x1b\[(.*?)m\x02?')
305 self.color_pat = re.compile('\x01?\x1b\[(.*?)m\x02?')
296 self.title_pat = re.compile('\x1b]0;(.*?)\x07')
306 self.title_pat = re.compile('\x1b]0;(.*?)\x07')
297
307
298 #self.SetEdgeMode(stc.STC_EDGE_LINE)
308 #self.SetEdgeMode(stc.STC_EDGE_LINE)
299 #self.SetEdgeColumn(80)
309 #self.SetEdgeColumn(80)
300
310
301 # styles
311 # styles
302 p = self.style
312 p = self.style
303 self.StyleSetSpec(stc.STC_STYLE_DEFAULT, p['default'])
313 self.StyleSetSpec(stc.STC_STYLE_DEFAULT, p['default'])
304 self.StyleClearAll()
314 self.StyleClearAll()
305 self.StyleSetSpec(_STDOUT_STYLE, p['stdout'])
315 self.StyleSetSpec(_STDOUT_STYLE, p['stdout'])
306 self.StyleSetSpec(_STDERR_STYLE, p['stderr'])
316 self.StyleSetSpec(_STDERR_STYLE, p['stderr'])
307 self.StyleSetSpec(_TRACE_STYLE, p['trace'])
317 self.StyleSetSpec(_TRACE_STYLE, p['trace'])
308
318
309 self.StyleSetSpec(stc.STC_STYLE_BRACELIGHT, p['bracegood'])
319 self.StyleSetSpec(stc.STC_STYLE_BRACELIGHT, p['bracegood'])
310 self.StyleSetSpec(stc.STC_STYLE_BRACEBAD, p['bracebad'])
320 self.StyleSetSpec(stc.STC_STYLE_BRACEBAD, p['bracebad'])
311 self.StyleSetSpec(stc.STC_P_COMMENTLINE, p['comment'])
321 self.StyleSetSpec(stc.STC_P_COMMENTLINE, p['comment'])
312 self.StyleSetSpec(stc.STC_P_NUMBER, p['number'])
322 self.StyleSetSpec(stc.STC_P_NUMBER, p['number'])
313 self.StyleSetSpec(stc.STC_P_STRING, p['string'])
323 self.StyleSetSpec(stc.STC_P_STRING, p['string'])
314 self.StyleSetSpec(stc.STC_P_CHARACTER, p['char'])
324 self.StyleSetSpec(stc.STC_P_CHARACTER, p['char'])
315 self.StyleSetSpec(stc.STC_P_WORD, p['keyword'])
325 self.StyleSetSpec(stc.STC_P_WORD, p['keyword'])
316 self.StyleSetSpec(stc.STC_P_WORD2, p['keyword'])
326 self.StyleSetSpec(stc.STC_P_WORD2, p['keyword'])
317 self.StyleSetSpec(stc.STC_P_TRIPLE, p['triple'])
327 self.StyleSetSpec(stc.STC_P_TRIPLE, p['triple'])
318 self.StyleSetSpec(stc.STC_P_TRIPLEDOUBLE, p['tripledouble'])
328 self.StyleSetSpec(stc.STC_P_TRIPLEDOUBLE, p['tripledouble'])
319 self.StyleSetSpec(stc.STC_P_CLASSNAME, p['class'])
329 self.StyleSetSpec(stc.STC_P_CLASSNAME, p['class'])
320 self.StyleSetSpec(stc.STC_P_DEFNAME, p['def'])
330 self.StyleSetSpec(stc.STC_P_DEFNAME, p['def'])
321 self.StyleSetSpec(stc.STC_P_OPERATOR, p['operator'])
331 self.StyleSetSpec(stc.STC_P_OPERATOR, p['operator'])
322 self.StyleSetSpec(stc.STC_P_COMMENTBLOCK, p['comment'])
332 self.StyleSetSpec(stc.STC_P_COMMENTBLOCK, p['comment'])
323
333
324 def _on_key_down(self, event, skip=True):
334 def _on_key_down(self, event, skip=True):
325 """ Key press callback used for correcting behavior for
335 """ Key press callback used for correcting behavior for
326 console-like interfaces: the cursor is constraint to be after
336 console-like interfaces: the cursor is constraint to be after
327 the last prompt.
337 the last prompt.
328
338
329 Return True if event as been catched.
339 Return True if event as been catched.
330 """
340 """
331 catched = True
341 catched = True
332 # Intercept some specific keys.
342 # Intercept some specific keys.
333 if event.KeyCode == ord('L') and event.ControlDown() :
343 if event.KeyCode == ord('L') and event.ControlDown() :
334 self.scroll_to_bottom()
344 self.scroll_to_bottom()
335 elif event.KeyCode == ord('K') and event.ControlDown() :
345 elif event.KeyCode == ord('K') and event.ControlDown() :
336 self.input_buffer = ''
346 self.input_buffer = ''
337 elif event.KeyCode == ord('A') and event.ControlDown() :
347 elif event.KeyCode == ord('A') and event.ControlDown() :
338 self.GotoPos(self.GetLength())
348 self.GotoPos(self.GetLength())
339 self.SetSelectionStart(self.current_prompt_pos)
349 self.SetSelectionStart(self.current_prompt_pos)
340 self.SetSelectionEnd(self.GetCurrentPos())
350 self.SetSelectionEnd(self.GetCurrentPos())
341 catched = True
351 catched = True
342 elif event.KeyCode == ord('E') and event.ControlDown() :
352 elif event.KeyCode == ord('E') and event.ControlDown() :
343 self.GotoPos(self.GetLength())
353 self.GotoPos(self.GetLength())
344 catched = True
354 catched = True
345 elif event.KeyCode == wx.WXK_PAGEUP:
355 elif event.KeyCode == wx.WXK_PAGEUP:
346 self.ScrollPages(-1)
356 self.ScrollPages(-1)
347 elif event.KeyCode == wx.WXK_PAGEDOWN:
357 elif event.KeyCode == wx.WXK_PAGEDOWN:
348 self.ScrollPages(1)
358 self.ScrollPages(1)
349 elif event.KeyCode == wx.WXK_UP and event.ShiftDown():
359 elif event.KeyCode == wx.WXK_UP and event.ShiftDown():
350 self.ScrollLines(-1)
360 self.ScrollLines(-1)
351 elif event.KeyCode == wx.WXK_DOWN and event.ShiftDown():
361 elif event.KeyCode == wx.WXK_DOWN and event.ShiftDown():
352 self.ScrollLines(1)
362 self.ScrollLines(1)
353 else:
363 else:
354 catched = False
364 catched = False
355
365
356 if self.AutoCompActive():
366 if self.AutoCompActive():
357 event.Skip()
367 event.Skip()
358 else:
368 else:
359 if event.KeyCode in (13, wx.WXK_NUMPAD_ENTER) and \
369 if event.KeyCode in (13, wx.WXK_NUMPAD_ENTER) and \
360 event.Modifiers in (wx.MOD_NONE, wx.MOD_WIN):
370 event.Modifiers in (wx.MOD_NONE, wx.MOD_WIN):
361 catched = True
371 catched = True
362 self.CallTipCancel()
372 self.CallTipCancel()
363 self.write('\n', refresh=False)
373 self.write('\n', refresh=False)
364 # Under windows scintilla seems to be doing funny stuff to the
374 # Under windows scintilla seems to be doing funny stuff to the
365 # line returns here, but the getter for input_buffer filters
375 # line returns here, but the getter for input_buffer filters
366 # this out.
376 # this out.
367 if sys.platform == 'win32':
377 if sys.platform == 'win32':
368 self.input_buffer = self.input_buffer
378 self.input_buffer = self.input_buffer
369 self._on_enter()
379 self._on_enter()
370
380
371 elif event.KeyCode == wx.WXK_HOME:
381 elif event.KeyCode == wx.WXK_HOME:
372 if event.Modifiers in (wx.MOD_NONE, wx.MOD_WIN):
382 if event.Modifiers in (wx.MOD_NONE, wx.MOD_WIN):
373 self.GotoPos(self.current_prompt_pos)
383 self.GotoPos(self.current_prompt_pos)
374 catched = True
384 catched = True
375
385
376 elif event.Modifiers == wx.MOD_SHIFT:
386 elif event.Modifiers == wx.MOD_SHIFT:
377 # FIXME: This behavior is not ideal: if the selection
387 # FIXME: This behavior is not ideal: if the selection
378 # is already started, it will jump.
388 # is already started, it will jump.
379 self.SetSelectionStart(self.current_prompt_pos)
389 self.SetSelectionStart(self.current_prompt_pos)
380 self.SetSelectionEnd(self.GetCurrentPos())
390 self.SetSelectionEnd(self.GetCurrentPos())
381 catched = True
391 catched = True
382
392
383 elif event.KeyCode == wx.WXK_UP:
393 elif event.KeyCode == wx.WXK_UP:
384 if self.GetCurrentLine() > self.current_prompt_line:
394 if self.GetCurrentLine() > self.current_prompt_line:
385 if self.GetCurrentLine() == self.current_prompt_line + 1 \
395 if self.GetCurrentLine() == self.current_prompt_line + 1 \
386 and self.GetColumn(self.GetCurrentPos()) < \
396 and self.GetColumn(self.GetCurrentPos()) < \
387 self.GetColumn(self.current_prompt_pos):
397 self.GetColumn(self.current_prompt_pos):
388 self.GotoPos(self.current_prompt_pos)
398 self.GotoPos(self.current_prompt_pos)
389 else:
399 else:
390 event.Skip()
400 event.Skip()
391 catched = True
401 catched = True
392
402
393 elif event.KeyCode in (wx.WXK_LEFT, wx.WXK_BACK):
403 elif event.KeyCode in (wx.WXK_LEFT, wx.WXK_BACK):
394 if self.GetCurrentPos() > self.current_prompt_pos:
404 if not self._keep_cursor_in_buffer():
395 event.Skip()
405 event.Skip()
396 catched = True
406 catched = True
397
407
398 if skip and not catched:
408 if skip and not catched:
399 # Put the cursor back in the edit region
409 # Put the cursor back in the edit region
400 if self.GetCurrentPos() < self.current_prompt_pos:
410 if not self._keep_cursor_in_buffer():
401 self.GotoPos(self.current_prompt_pos)
411 if (self.GetCurrentPos() == self.GetLength()
402 else:
412 and event.KeyCode == wx.WXK_DELETE):
413 pass
403 event.Skip()
414 event.Skip()
404
415
405 return catched
416 return catched
406
417
407
418
408 def _on_key_up(self, event, skip=True):
419 def _on_key_up(self, event, skip=True):
409 """ If cursor is outside the editing region, put it back.
420 """ If cursor is outside the editing region, put it back.
410 """
421 """
411 event.Skip()
422 if skip:
412 if self.GetCurrentPos() < self.current_prompt_pos:
423 event.Skip()
424 self._keep_cursor_in_buffer()
425
426
427 def _keep_cursor_in_buffer(self):
428 """ Checks if the cursor is where it is allowed to be. If not,
429 put it back.
430
431 Returns
432 -------
433 cursor_moved: Boolean
434 whether or not the cursor was moved by this routine.
435
436 Notes
437 ------
438 WARNING: This does proper checks only for horizontal
439 movements.
440 """
441 current_pos = self.GetCurrentPos()
442 if current_pos < self.current_prompt_pos:
413 self.GotoPos(self.current_prompt_pos)
443 self.GotoPos(self.current_prompt_pos)
444 return True
445 line, line_pos = self.GetCurLine()
446 # Jump the continuation prompt
447 continuation_prompt = self.continuation_prompt()
448 if ( line.startswith(continuation_prompt)
449 and line_pos < len(continuation_prompt)+1):
450 if line_pos < 2:
451 # We are at the beginning of the line, trying to move
452 # forward: jump forward.
453 self.GotoPos(current_pos + 1 +
454 len(continuation_prompt) - line_pos)
455 else:
456 # Jump back up
457 self.GotoPos(self.GetLineEndPosition(self.GetCurrentLine()-1))
458 return True
459 return False
414
460
415
461
416
462
417 if __name__ == '__main__':
463 if __name__ == '__main__':
418 # Some simple code to test the console widget.
464 # Some simple code to test the console widget.
419 class MainWindow(wx.Frame):
465 class MainWindow(wx.Frame):
420 def __init__(self, parent, id, title):
466 def __init__(self, parent, id, title):
421 wx.Frame.__init__(self, parent, id, title, size=(300,250))
467 wx.Frame.__init__(self, parent, id, title, size=(300,250))
422 self._sizer = wx.BoxSizer(wx.VERTICAL)
468 self._sizer = wx.BoxSizer(wx.VERTICAL)
423 self.console_widget = ConsoleWidget(self)
469 self.console_widget = ConsoleWidget(self)
424 self._sizer.Add(self.console_widget, 1, wx.EXPAND)
470 self._sizer.Add(self.console_widget, 1, wx.EXPAND)
425 self.SetSizer(self._sizer)
471 self.SetSizer(self._sizer)
426 self.SetAutoLayout(1)
472 self.SetAutoLayout(1)
427 self.Show(True)
473 self.Show(True)
428
474
429 app = wx.PySimpleApp()
475 app = wx.PySimpleApp()
430 w = MainWindow(None, wx.ID_ANY, 'ConsoleWidget')
476 w = MainWindow(None, wx.ID_ANY, 'ConsoleWidget')
431 w.SetSize((780, 460))
477 w.SetSize((780, 460))
432 w.Show()
478 w.Show()
433
479
434 app.MainLoop()
480 app.MainLoop()
435
481
436
482
@@ -1,558 +1,564 b''
1 # encoding: utf-8 -*- test-case-name:
1 # encoding: utf-8 -*- test-case-name:
2 # FIXME: Need to add tests.
2 # FIXME: Need to add tests.
3 # ipython1.frontend.wx.tests.test_wx_frontend -*-
3 # ipython1.frontend.wx.tests.test_wx_frontend -*-
4
4
5 """Classes to provide a Wx frontend to the
5 """Classes to provide a Wx frontend to the
6 IPython.kernel.core.interpreter.
6 IPython.kernel.core.interpreter.
7
7
8 This class inherits from ConsoleWidget, that provides a console-like
8 This class inherits from ConsoleWidget, that provides a console-like
9 widget to provide a text-rendering widget suitable for a terminal.
9 widget to provide a text-rendering widget suitable for a terminal.
10 """
10 """
11
11
12 __docformat__ = "restructuredtext en"
12 __docformat__ = "restructuredtext en"
13
13
14 #-------------------------------------------------------------------------------
14 #-------------------------------------------------------------------------------
15 # Copyright (C) 2008 The IPython Development Team
15 # Copyright (C) 2008 The IPython Development Team
16 #
16 #
17 # Distributed under the terms of the BSD License. The full license is in
17 # Distributed under the terms of the BSD License. The full license is in
18 # the file COPYING, distributed as part of this software.
18 # the file COPYING, distributed as part of this software.
19 #-------------------------------------------------------------------------------
19 #-------------------------------------------------------------------------------
20
20
21 #-------------------------------------------------------------------------------
21 #-------------------------------------------------------------------------------
22 # Imports
22 # Imports
23 #-------------------------------------------------------------------------------
23 #-------------------------------------------------------------------------------
24
24
25 # Major library imports
25 # Major library imports
26 import re
26 import re
27 import __builtin__
27 import __builtin__
28 import sys
28 import sys
29 from threading import Lock
29 from threading import Lock
30 import string
30 import string
31
31
32 import wx
32 import wx
33 from wx import stc
33 from wx import stc
34
34
35 # Ipython-specific imports.
35 # Ipython-specific imports.
36 from IPython.frontend._process import PipedProcess
36 from IPython.frontend._process import PipedProcess
37 from console_widget import ConsoleWidget
37 from console_widget import ConsoleWidget
38 from IPython.frontend.prefilterfrontend import PrefilterFrontEnd
38 from IPython.frontend.prefilterfrontend import PrefilterFrontEnd
39
39
40 #-------------------------------------------------------------------------------
40 #-------------------------------------------------------------------------------
41 # Constants
41 # Constants
42 #-------------------------------------------------------------------------------
42 #-------------------------------------------------------------------------------
43
43
44 _COMPLETE_BUFFER_BG = '#FAFAF1' # Nice green
44 _COMPLETE_BUFFER_BG = '#FAFAF1' # Nice green
45 _INPUT_BUFFER_BG = '#FDFFD3' # Nice yellow
45 _INPUT_BUFFER_BG = '#FDFFD3' # Nice yellow
46 _ERROR_BG = '#FFF1F1' # Nice red
46 _ERROR_BG = '#FFF1F1' # Nice red
47
47
48 _COMPLETE_BUFFER_MARKER = 31
48 _COMPLETE_BUFFER_MARKER = 31
49 _ERROR_MARKER = 30
49 _ERROR_MARKER = 30
50 _INPUT_MARKER = 29
50 _INPUT_MARKER = 29
51
51
52 prompt_in1 = \
52 prompt_in1 = \
53 '\n\x01\x1b[0;34m\x02In [\x01\x1b[1;34m\x02$number\x01\x1b[0;34m\x02]: \x01\x1b[0m\x02'
53 '\n\x01\x1b[0;34m\x02In [\x01\x1b[1;34m\x02$number\x01\x1b[0;34m\x02]: \x01\x1b[0m\x02'
54
54
55 prompt_out = \
55 prompt_out = \
56 '\x01\x1b[0;31m\x02Out[\x01\x1b[1;31m\x02$number\x01\x1b[0;31m\x02]: \x01\x1b[0m\x02'
56 '\x01\x1b[0;31m\x02Out[\x01\x1b[1;31m\x02$number\x01\x1b[0;31m\x02]: \x01\x1b[0m\x02'
57
57
58 #-------------------------------------------------------------------------------
58 #-------------------------------------------------------------------------------
59 # Classes to implement the Wx frontend
59 # Classes to implement the Wx frontend
60 #-------------------------------------------------------------------------------
60 #-------------------------------------------------------------------------------
61 class WxController(ConsoleWidget, PrefilterFrontEnd):
61 class WxController(ConsoleWidget, PrefilterFrontEnd):
62 """Classes to provide a Wx frontend to the
62 """Classes to provide a Wx frontend to the
63 IPython.kernel.core.interpreter.
63 IPython.kernel.core.interpreter.
64
64
65 This class inherits from ConsoleWidget, that provides a console-like
65 This class inherits from ConsoleWidget, that provides a console-like
66 widget to provide a text-rendering widget suitable for a terminal.
66 widget to provide a text-rendering widget suitable for a terminal.
67 """
67 """
68
68
69 output_prompt_template = string.Template(prompt_out)
69 output_prompt_template = string.Template(prompt_out)
70
70
71 input_prompt_template = string.Template(prompt_in1)
71 input_prompt_template = string.Template(prompt_in1)
72
72
73 # Print debug info on what is happening to the console.
73 # Print debug info on what is happening to the console.
74 debug = False
74 debug = False
75
75
76 # The title of the terminal, as captured through the ANSI escape
76 # The title of the terminal, as captured through the ANSI escape
77 # sequences.
77 # sequences.
78 def _set_title(self, title):
78 def _set_title(self, title):
79 return self.Parent.SetTitle(title)
79 return self.Parent.SetTitle(title)
80
80
81 def _get_title(self):
81 def _get_title(self):
82 return self.Parent.GetTitle()
82 return self.Parent.GetTitle()
83
83
84 title = property(_get_title, _set_title)
84 title = property(_get_title, _set_title)
85
85
86
86
87 # The buffer being edited.
87 # The buffer being edited.
88 # We are duplicating the definition here because of multiple
88 # We are duplicating the definition here because of multiple
89 # inheritence
89 # inheritence
90 def _set_input_buffer(self, string):
90 def _set_input_buffer(self, string):
91 ConsoleWidget._set_input_buffer(self, string)
91 ConsoleWidget._set_input_buffer(self, string)
92 self._colorize_input_buffer()
92 self._colorize_input_buffer()
93
93
94 def _get_input_buffer(self):
94 def _get_input_buffer(self):
95 """ Returns the text in current edit buffer.
95 """ Returns the text in current edit buffer.
96 """
96 """
97 return ConsoleWidget._get_input_buffer(self)
97 return ConsoleWidget._get_input_buffer(self)
98
98
99 input_buffer = property(_get_input_buffer, _set_input_buffer)
99 input_buffer = property(_get_input_buffer, _set_input_buffer)
100
100
101
101
102 #--------------------------------------------------------------------------
102 #--------------------------------------------------------------------------
103 # Private Attributes
103 # Private Attributes
104 #--------------------------------------------------------------------------
104 #--------------------------------------------------------------------------
105
105
106 # A flag governing the behavior of the input. Can be:
106 # A flag governing the behavior of the input. Can be:
107 #
107 #
108 # 'readline' for readline-like behavior with a prompt
108 # 'readline' for readline-like behavior with a prompt
109 # and an edit buffer.
109 # and an edit buffer.
110 # 'raw_input' similar to readline, but triggered by a raw-input
110 # 'raw_input' similar to readline, but triggered by a raw-input
111 # call. Can be used by subclasses to act differently.
111 # call. Can be used by subclasses to act differently.
112 # 'subprocess' for sending the raw input directly to a
112 # 'subprocess' for sending the raw input directly to a
113 # subprocess.
113 # subprocess.
114 # 'buffering' for buffering of the input, that will be used
114 # 'buffering' for buffering of the input, that will be used
115 # when the input state switches back to another state.
115 # when the input state switches back to another state.
116 _input_state = 'readline'
116 _input_state = 'readline'
117
117
118 # Attribute to store reference to the pipes of a subprocess, if we
118 # Attribute to store reference to the pipes of a subprocess, if we
119 # are running any.
119 # are running any.
120 _running_process = False
120 _running_process = False
121
121
122 # A queue for writing fast streams to the screen without flooding the
122 # A queue for writing fast streams to the screen without flooding the
123 # event loop
123 # event loop
124 _out_buffer = []
124 _out_buffer = []
125
125
126 # A lock to lock the _out_buffer to make sure we don't empty it
126 # A lock to lock the _out_buffer to make sure we don't empty it
127 # while it is being swapped
127 # while it is being swapped
128 _out_buffer_lock = Lock()
128 _out_buffer_lock = Lock()
129
129
130 # The different line markers used to higlight the prompts.
130 # The different line markers used to higlight the prompts.
131 _markers = dict()
131 _markers = dict()
132
132
133 #--------------------------------------------------------------------------
133 #--------------------------------------------------------------------------
134 # Public API
134 # Public API
135 #--------------------------------------------------------------------------
135 #--------------------------------------------------------------------------
136
136
137 def __init__(self, parent, id=wx.ID_ANY, pos=wx.DefaultPosition,
137 def __init__(self, parent, id=wx.ID_ANY, pos=wx.DefaultPosition,
138 size=wx.DefaultSize,
138 size=wx.DefaultSize,
139 style=wx.CLIP_CHILDREN|wx.WANTS_CHARS,
139 style=wx.CLIP_CHILDREN|wx.WANTS_CHARS,
140 *args, **kwds):
140 *args, **kwds):
141 """ Create Shell instance.
141 """ Create Shell instance.
142 """
142 """
143 ConsoleWidget.__init__(self, parent, id, pos, size, style)
143 ConsoleWidget.__init__(self, parent, id, pos, size, style)
144 PrefilterFrontEnd.__init__(self, **kwds)
144 PrefilterFrontEnd.__init__(self, **kwds)
145
145
146 # Stick in our own raw_input:
146 # Stick in our own raw_input:
147 self.ipython0.raw_input = self.raw_input
147 self.ipython0.raw_input = self.raw_input
148
148
149 # Marker for complete buffer.
149 # Marker for complete buffer.
150 self.MarkerDefine(_COMPLETE_BUFFER_MARKER, stc.STC_MARK_BACKGROUND,
150 self.MarkerDefine(_COMPLETE_BUFFER_MARKER, stc.STC_MARK_BACKGROUND,
151 background=_COMPLETE_BUFFER_BG)
151 background=_COMPLETE_BUFFER_BG)
152 # Marker for current input buffer.
152 # Marker for current input buffer.
153 self.MarkerDefine(_INPUT_MARKER, stc.STC_MARK_BACKGROUND,
153 self.MarkerDefine(_INPUT_MARKER, stc.STC_MARK_BACKGROUND,
154 background=_INPUT_BUFFER_BG)
154 background=_INPUT_BUFFER_BG)
155 # Marker for tracebacks.
155 # Marker for tracebacks.
156 self.MarkerDefine(_ERROR_MARKER, stc.STC_MARK_BACKGROUND,
156 self.MarkerDefine(_ERROR_MARKER, stc.STC_MARK_BACKGROUND,
157 background=_ERROR_BG)
157 background=_ERROR_BG)
158
158
159 # A time for flushing the write buffer
159 # A time for flushing the write buffer
160 BUFFER_FLUSH_TIMER_ID = 100
160 BUFFER_FLUSH_TIMER_ID = 100
161 self._buffer_flush_timer = wx.Timer(self, BUFFER_FLUSH_TIMER_ID)
161 self._buffer_flush_timer = wx.Timer(self, BUFFER_FLUSH_TIMER_ID)
162 wx.EVT_TIMER(self, BUFFER_FLUSH_TIMER_ID, self._buffer_flush)
162 wx.EVT_TIMER(self, BUFFER_FLUSH_TIMER_ID, self._buffer_flush)
163
163
164 if 'debug' in kwds:
164 if 'debug' in kwds:
165 self.debug = kwds['debug']
165 self.debug = kwds['debug']
166 kwds.pop('debug')
166 kwds.pop('debug')
167
167
168 # Inject self in namespace, for debug
168 # Inject self in namespace, for debug
169 if self.debug:
169 if self.debug:
170 self.shell.user_ns['self'] = self
170 self.shell.user_ns['self'] = self
171 # Inject our own raw_input in namespace
171 # Inject our own raw_input in namespace
172 self.shell.user_ns['raw_input'] = self.raw_input
172 self.shell.user_ns['raw_input'] = self.raw_input
173
173
174
174
175 def raw_input(self, prompt=''):
175 def raw_input(self, prompt=''):
176 """ A replacement from python's raw_input.
176 """ A replacement from python's raw_input.
177 """
177 """
178 self.new_prompt(prompt)
178 self.new_prompt(prompt)
179 self._input_state = 'raw_input'
179 self._input_state = 'raw_input'
180 if hasattr(self, '_cursor'):
180 if hasattr(self, '_cursor'):
181 del self._cursor
181 del self._cursor
182 self.SetCursor(wx.StockCursor(wx.CURSOR_CROSS))
182 self.SetCursor(wx.StockCursor(wx.CURSOR_CROSS))
183 self.__old_on_enter = self._on_enter
183 self.__old_on_enter = self._on_enter
184 event_loop = wx.EventLoop()
184 event_loop = wx.EventLoop()
185 def my_on_enter():
185 def my_on_enter():
186 event_loop.Exit()
186 event_loop.Exit()
187 self._on_enter = my_on_enter
187 self._on_enter = my_on_enter
188 # XXX: Running a separate event_loop. Ugly.
188 # XXX: Running a separate event_loop. Ugly.
189 event_loop.Run()
189 event_loop.Run()
190 self._on_enter = self.__old_on_enter
190 self._on_enter = self.__old_on_enter
191 self._input_state = 'buffering'
191 self._input_state = 'buffering'
192 self._cursor = wx.BusyCursor()
192 self._cursor = wx.BusyCursor()
193 return self.input_buffer.rstrip('\n')
193 return self.input_buffer.rstrip('\n')
194
194
195
195
196 def system_call(self, command_string):
196 def system_call(self, command_string):
197 self._input_state = 'subprocess'
197 self._input_state = 'subprocess'
198 event_loop = wx.EventLoop()
198 event_loop = wx.EventLoop()
199 def _end_system_call():
199 def _end_system_call():
200 self._input_state = 'buffering'
200 self._input_state = 'buffering'
201 self._running_process = False
201 self._running_process = False
202 event_loop.Exit()
202 event_loop.Exit()
203
203
204 self._running_process = PipedProcess(command_string,
204 self._running_process = PipedProcess(command_string,
205 out_callback=self.buffered_write,
205 out_callback=self.buffered_write,
206 end_callback = _end_system_call)
206 end_callback = _end_system_call)
207 self._running_process.start()
207 self._running_process.start()
208 # XXX: Running a separate event_loop. Ugly.
208 # XXX: Running a separate event_loop. Ugly.
209 event_loop.Run()
209 event_loop.Run()
210 # Be sure to flush the buffer.
210 # Be sure to flush the buffer.
211 self._buffer_flush(event=None)
211 self._buffer_flush(event=None)
212
212
213
213
214 def do_calltip(self):
214 def do_calltip(self):
215 """ Analyse current and displays useful calltip for it.
215 """ Analyse current and displays useful calltip for it.
216 """
216 """
217 if self.debug:
217 if self.debug:
218 print >>sys.__stdout__, "do_calltip"
218 print >>sys.__stdout__, "do_calltip"
219 separators = re.compile('[\s\{\}\[\]\(\)\= ,:]')
219 separators = re.compile('[\s\{\}\[\]\(\)\= ,:]')
220 symbol = self.input_buffer
220 symbol = self.input_buffer
221 symbol_string = separators.split(symbol)[-1]
221 symbol_string = separators.split(symbol)[-1]
222 base_symbol_string = symbol_string.split('.')[0]
222 base_symbol_string = symbol_string.split('.')[0]
223 if base_symbol_string in self.shell.user_ns:
223 if base_symbol_string in self.shell.user_ns:
224 symbol = self.shell.user_ns[base_symbol_string]
224 symbol = self.shell.user_ns[base_symbol_string]
225 elif base_symbol_string in self.shell.user_global_ns:
225 elif base_symbol_string in self.shell.user_global_ns:
226 symbol = self.shell.user_global_ns[base_symbol_string]
226 symbol = self.shell.user_global_ns[base_symbol_string]
227 elif base_symbol_string in __builtin__.__dict__:
227 elif base_symbol_string in __builtin__.__dict__:
228 symbol = __builtin__.__dict__[base_symbol_string]
228 symbol = __builtin__.__dict__[base_symbol_string]
229 else:
229 else:
230 return False
230 return False
231 try:
231 try:
232 for name in symbol_string.split('.')[1:] + ['__doc__']:
232 for name in symbol_string.split('.')[1:] + ['__doc__']:
233 symbol = getattr(symbol, name)
233 symbol = getattr(symbol, name)
234 self.AutoCompCancel()
234 self.AutoCompCancel()
235 # Check that the symbol can indeed be converted to a string:
235 # Check that the symbol can indeed be converted to a string:
236 symbol += ''
236 symbol += ''
237 wx.CallAfter(self.CallTipShow, self.GetCurrentPos(), symbol)
237 wx.CallAfter(self.CallTipShow, self.GetCurrentPos(), symbol)
238 except:
238 except:
239 # The retrieve symbol couldn't be converted to a string
239 # The retrieve symbol couldn't be converted to a string
240 pass
240 pass
241
241
242
242
243 def _popup_completion(self, create=False):
243 def _popup_completion(self, create=False):
244 """ Updates the popup completion menu if it exists. If create is
244 """ Updates the popup completion menu if it exists. If create is
245 true, open the menu.
245 true, open the menu.
246 """
246 """
247 if self.debug:
247 if self.debug:
248 print >>sys.__stdout__, "_popup_completion"
248 print >>sys.__stdout__, "_popup_completion"
249 line = self.input_buffer
249 line = self.input_buffer
250 if (self.AutoCompActive() and line and not line[-1] == '.') \
250 if (self.AutoCompActive() and line and not line[-1] == '.') \
251 or create==True:
251 or create==True:
252 suggestion, completions = self.complete(line)
252 suggestion, completions = self.complete(line)
253 offset=0
253 offset=0
254 if completions:
254 if completions:
255 complete_sep = re.compile('[\s\{\}\[\]\(\)\= ,:]')
255 complete_sep = re.compile('[\s\{\}\[\]\(\)\= ,:]')
256 residual = complete_sep.split(line)[-1]
256 residual = complete_sep.split(line)[-1]
257 offset = len(residual)
257 offset = len(residual)
258 self.pop_completion(completions, offset=offset)
258 self.pop_completion(completions, offset=offset)
259 if self.debug:
259 if self.debug:
260 print >>sys.__stdout__, completions
260 print >>sys.__stdout__, completions
261
261
262
262
263 def buffered_write(self, text):
263 def buffered_write(self, text):
264 """ A write method for streams, that caches the stream in order
264 """ A write method for streams, that caches the stream in order
265 to avoid flooding the event loop.
265 to avoid flooding the event loop.
266
266
267 This can be called outside of the main loop, in separate
267 This can be called outside of the main loop, in separate
268 threads.
268 threads.
269 """
269 """
270 self._out_buffer_lock.acquire()
270 self._out_buffer_lock.acquire()
271 self._out_buffer.append(text)
271 self._out_buffer.append(text)
272 self._out_buffer_lock.release()
272 self._out_buffer_lock.release()
273 if not self._buffer_flush_timer.IsRunning():
273 if not self._buffer_flush_timer.IsRunning():
274 wx.CallAfter(self._buffer_flush_timer.Start,
274 wx.CallAfter(self._buffer_flush_timer.Start,
275 milliseconds=100, oneShot=True)
275 milliseconds=100, oneShot=True)
276
276
277
277
278 #--------------------------------------------------------------------------
278 #--------------------------------------------------------------------------
279 # LineFrontEnd interface
279 # LineFrontEnd interface
280 #--------------------------------------------------------------------------
280 #--------------------------------------------------------------------------
281
281
282 def execute(self, python_string, raw_string=None):
282 def execute(self, python_string, raw_string=None):
283 self._input_state = 'buffering'
283 self._input_state = 'buffering'
284 self.CallTipCancel()
284 self.CallTipCancel()
285 self._cursor = wx.BusyCursor()
285 self._cursor = wx.BusyCursor()
286 if raw_string is None:
286 if raw_string is None:
287 raw_string = python_string
287 raw_string = python_string
288 end_line = self.current_prompt_line \
288 end_line = self.current_prompt_line \
289 + max(1, len(raw_string.split('\n'))-1)
289 + max(1, len(raw_string.split('\n'))-1)
290 for i in range(self.current_prompt_line, end_line):
290 for i in range(self.current_prompt_line, end_line):
291 if i in self._markers:
291 if i in self._markers:
292 self.MarkerDeleteHandle(self._markers[i])
292 self.MarkerDeleteHandle(self._markers[i])
293 self._markers[i] = self.MarkerAdd(i, _COMPLETE_BUFFER_MARKER)
293 self._markers[i] = self.MarkerAdd(i, _COMPLETE_BUFFER_MARKER)
294 # Use a callafter to update the display robustly under windows
294 # Use a callafter to update the display robustly under windows
295 def callback():
295 def callback():
296 self.GotoPos(self.GetLength())
296 self.GotoPos(self.GetLength())
297 PrefilterFrontEnd.execute(self, python_string,
297 PrefilterFrontEnd.execute(self, python_string,
298 raw_string=raw_string)
298 raw_string=raw_string)
299 wx.CallAfter(callback)
299 wx.CallAfter(callback)
300
300
301
301
302 def execute_command(self, command, hidden=False):
302 def execute_command(self, command, hidden=False):
303 """ Execute a command, not only in the model, but also in the
303 """ Execute a command, not only in the model, but also in the
304 view.
304 view.
305 """
305 """
306 if hidden:
306 if hidden:
307 return self.shell.execute(command)
307 return self.shell.execute(command)
308 else:
308 else:
309 # XXX: we are not storing the input buffer previous to the
309 # XXX: we are not storing the input buffer previous to the
310 # execution, as this forces us to run the execution
310 # execution, as this forces us to run the execution
311 # input_buffer a yield, which is not good.
311 # input_buffer a yield, which is not good.
312 ##current_buffer = self.shell.control.input_buffer
312 ##current_buffer = self.shell.control.input_buffer
313 command = command.rstrip()
313 command = command.rstrip()
314 if len(command.split('\n')) > 1:
314 if len(command.split('\n')) > 1:
315 # The input command is several lines long, we need to
315 # The input command is several lines long, we need to
316 # force the execution to happen
316 # force the execution to happen
317 command += '\n'
317 command += '\n'
318 cleaned_command = self.prefilter_input(command)
318 cleaned_command = self.prefilter_input(command)
319 self.input_buffer = command
319 self.input_buffer = command
320 # Do not use wx.Yield() (aka GUI.process_events()) to avoid
320 # Do not use wx.Yield() (aka GUI.process_events()) to avoid
321 # recursive yields.
321 # recursive yields.
322 self.ProcessEvent(wx.PaintEvent())
322 self.ProcessEvent(wx.PaintEvent())
323 self.write('\n')
323 self.write('\n')
324 if not self.is_complete(cleaned_command + '\n'):
324 if not self.is_complete(cleaned_command + '\n'):
325 self._colorize_input_buffer()
325 self._colorize_input_buffer()
326 self.render_error('Incomplete or invalid input')
326 self.render_error('Incomplete or invalid input')
327 self.new_prompt(self.input_prompt_template.substitute(
327 self.new_prompt(self.input_prompt_template.substitute(
328 number=(self.last_result['number'] + 1)))
328 number=(self.last_result['number'] + 1)))
329 return False
329 return False
330 self._on_enter()
330 self._on_enter()
331 return True
331 return True
332
332
333
333
334 def save_output_hooks(self):
334 def save_output_hooks(self):
335 self.__old_raw_input = __builtin__.raw_input
335 self.__old_raw_input = __builtin__.raw_input
336 PrefilterFrontEnd.save_output_hooks(self)
336 PrefilterFrontEnd.save_output_hooks(self)
337
337
338 def capture_output(self):
338 def capture_output(self):
339 self.SetLexer(stc.STC_LEX_NULL)
339 self.SetLexer(stc.STC_LEX_NULL)
340 PrefilterFrontEnd.capture_output(self)
340 PrefilterFrontEnd.capture_output(self)
341 __builtin__.raw_input = self.raw_input
341 __builtin__.raw_input = self.raw_input
342
342
343
343
344 def release_output(self):
344 def release_output(self):
345 __builtin__.raw_input = self.__old_raw_input
345 __builtin__.raw_input = self.__old_raw_input
346 PrefilterFrontEnd.release_output(self)
346 PrefilterFrontEnd.release_output(self)
347 self.SetLexer(stc.STC_LEX_PYTHON)
347 self.SetLexer(stc.STC_LEX_PYTHON)
348
348
349
349
350 def after_execute(self):
350 def after_execute(self):
351 PrefilterFrontEnd.after_execute(self)
351 PrefilterFrontEnd.after_execute(self)
352 # Clear the wait cursor
352 # Clear the wait cursor
353 if hasattr(self, '_cursor'):
353 if hasattr(self, '_cursor'):
354 del self._cursor
354 del self._cursor
355 self.SetCursor(wx.StockCursor(wx.CURSOR_CHAR))
355 self.SetCursor(wx.StockCursor(wx.CURSOR_CHAR))
356
356
357
357
358 def show_traceback(self):
358 def show_traceback(self):
359 start_line = self.GetCurrentLine()
359 start_line = self.GetCurrentLine()
360 PrefilterFrontEnd.show_traceback(self)
360 PrefilterFrontEnd.show_traceback(self)
361 self.ProcessEvent(wx.PaintEvent())
361 self.ProcessEvent(wx.PaintEvent())
362 #wx.Yield()
362 #wx.Yield()
363 for i in range(start_line, self.GetCurrentLine()):
363 for i in range(start_line, self.GetCurrentLine()):
364 self._markers[i] = self.MarkerAdd(i, _ERROR_MARKER)
364 self._markers[i] = self.MarkerAdd(i, _ERROR_MARKER)
365
365
366
366
367 #--------------------------------------------------------------------------
367 #--------------------------------------------------------------------------
368 # FrontEndBase interface
368 # FrontEndBase interface
369 #--------------------------------------------------------------------------
369 #--------------------------------------------------------------------------
370
370
371 def render_error(self, e):
371 def render_error(self, e):
372 start_line = self.GetCurrentLine()
372 start_line = self.GetCurrentLine()
373 self.write('\n' + e + '\n')
373 self.write('\n' + e + '\n')
374 for i in range(start_line, self.GetCurrentLine()):
374 for i in range(start_line, self.GetCurrentLine()):
375 self._markers[i] = self.MarkerAdd(i, _ERROR_MARKER)
375 self._markers[i] = self.MarkerAdd(i, _ERROR_MARKER)
376
376
377
377
378 #--------------------------------------------------------------------------
378 #--------------------------------------------------------------------------
379 # ConsoleWidget interface
379 # ConsoleWidget interface
380 #--------------------------------------------------------------------------
380 #--------------------------------------------------------------------------
381
381
382 def new_prompt(self, prompt):
382 def new_prompt(self, prompt):
383 """ Display a new prompt, and start a new input buffer.
383 """ Display a new prompt, and start a new input buffer.
384 """
384 """
385 self._input_state = 'readline'
385 self._input_state = 'readline'
386 ConsoleWidget.new_prompt(self, prompt)
386 ConsoleWidget.new_prompt(self, prompt)
387 i = self.current_prompt_line
387 i = self.current_prompt_line
388 self._markers[i] = self.MarkerAdd(i, _INPUT_MARKER)
388 self._markers[i] = self.MarkerAdd(i, _INPUT_MARKER)
389
389
390
390
391 def write(self, *args, **kwargs):
391 def write(self, *args, **kwargs):
392 # Avoid multiple inheritence, be explicit about which
392 # Avoid multiple inheritence, be explicit about which
393 # parent method class gets called
393 # parent method class gets called
394 ConsoleWidget.write(self, *args, **kwargs)
394 ConsoleWidget.write(self, *args, **kwargs)
395
395
396
396
397 def _on_key_down(self, event, skip=True):
397 def _on_key_down(self, event, skip=True):
398 """ Capture the character events, let the parent
398 """ Capture the character events, let the parent
399 widget handle them, and put our logic afterward.
399 widget handle them, and put our logic afterward.
400 """
400 """
401 # FIXME: This method needs to be broken down in smaller ones.
401 # FIXME: This method needs to be broken down in smaller ones.
402 current_line_number = self.GetCurrentLine()
402 current_line_number = self.GetCurrentLine()
403 if event.KeyCode in (ord('c'), ord('C')) and event.ControlDown():
403 if event.KeyCode in (ord('c'), ord('C')) and event.ControlDown():
404 # Capture Control-C
404 # Capture Control-C
405 if self._input_state == 'subprocess':
405 if self._input_state == 'subprocess':
406 if self.debug:
406 if self.debug:
407 print >>sys.__stderr__, 'Killing running process'
407 print >>sys.__stderr__, 'Killing running process'
408 if hasattr(self._running_process, 'process'):
408 if hasattr(self._running_process, 'process'):
409 self._running_process.process.kill()
409 self._running_process.process.kill()
410 elif self._input_state == 'buffering':
410 elif self._input_state == 'buffering':
411 if self.debug:
411 if self.debug:
412 print >>sys.__stderr__, 'Raising KeyboardInterrupt'
412 print >>sys.__stderr__, 'Raising KeyboardInterrupt'
413 raise KeyboardInterrupt
413 raise KeyboardInterrupt
414 # XXX: We need to make really sure we
414 # XXX: We need to make really sure we
415 # get back to a prompt.
415 # get back to a prompt.
416 elif self._input_state == 'subprocess' and (
416 elif self._input_state == 'subprocess' and (
417 ( event.KeyCode<256 and
417 ( event.KeyCode<256 and
418 not event.ControlDown() )
418 not event.ControlDown() )
419 or
419 or
420 ( event.KeyCode in (ord('d'), ord('D')) and
420 ( event.KeyCode in (ord('d'), ord('D')) and
421 event.ControlDown())):
421 event.ControlDown())):
422 # We are running a process, we redirect keys.
422 # We are running a process, we redirect keys.
423 ConsoleWidget._on_key_down(self, event, skip=skip)
423 ConsoleWidget._on_key_down(self, event, skip=skip)
424 char = chr(event.KeyCode)
424 char = chr(event.KeyCode)
425 # Deal with some inconsistency in wx keycodes:
425 # Deal with some inconsistency in wx keycodes:
426 if char == '\r':
426 if char == '\r':
427 char = '\n'
427 char = '\n'
428 elif not event.ShiftDown():
428 elif not event.ShiftDown():
429 char = char.lower()
429 char = char.lower()
430 if event.ControlDown() and event.KeyCode in (ord('d'), ord('D')):
430 if event.ControlDown() and event.KeyCode in (ord('d'), ord('D')):
431 char = '\04'
431 char = '\04'
432 self._running_process.process.stdin.write(char)
432 self._running_process.process.stdin.write(char)
433 self._running_process.process.stdin.flush()
433 self._running_process.process.stdin.flush()
434 elif event.KeyCode in (ord('('), 57, 53):
434 elif event.KeyCode in (ord('('), 57, 53):
435 # Calltips
435 # Calltips
436 event.Skip()
436 event.Skip()
437 self.do_calltip()
437 self.do_calltip()
438 elif self.AutoCompActive() and not event.KeyCode == ord('\t'):
438 elif self.AutoCompActive() and not event.KeyCode == ord('\t'):
439 event.Skip()
439 event.Skip()
440 if event.KeyCode in (wx.WXK_BACK, wx.WXK_DELETE):
440 if event.KeyCode in (wx.WXK_BACK, wx.WXK_DELETE):
441 wx.CallAfter(self._popup_completion, create=True)
441 wx.CallAfter(self._popup_completion, create=True)
442 elif not event.KeyCode in (wx.WXK_UP, wx.WXK_DOWN, wx.WXK_LEFT,
442 elif not event.KeyCode in (wx.WXK_UP, wx.WXK_DOWN, wx.WXK_LEFT,
443 wx.WXK_RIGHT, wx.WXK_ESCAPE):
443 wx.WXK_RIGHT, wx.WXK_ESCAPE):
444 wx.CallAfter(self._popup_completion)
444 wx.CallAfter(self._popup_completion)
445 else:
445 else:
446 # Up history
446 # Up history
447 if event.KeyCode == wx.WXK_UP and (
447 if event.KeyCode == wx.WXK_UP and (
448 ( current_line_number == self.current_prompt_line and
448 ( current_line_number == self.current_prompt_line and
449 event.Modifiers in (wx.MOD_NONE, wx.MOD_WIN) )
449 event.Modifiers in (wx.MOD_NONE, wx.MOD_WIN) )
450 or event.ControlDown() ):
450 or event.ControlDown() ):
451 new_buffer = self.get_history_previous(
451 new_buffer = self.get_history_previous(
452 self.input_buffer)
452 self.input_buffer)
453 if new_buffer is not None:
453 if new_buffer is not None:
454 self.input_buffer = new_buffer
454 self.input_buffer = new_buffer
455 if self.GetCurrentLine() > self.current_prompt_line:
455 if self.GetCurrentLine() > self.current_prompt_line:
456 # Go to first line, for seemless history up.
456 # Go to first line, for seemless history up.
457 self.GotoPos(self.current_prompt_pos)
457 self.GotoPos(self.current_prompt_pos)
458 # Down history
458 # Down history
459 elif event.KeyCode == wx.WXK_DOWN and (
459 elif event.KeyCode == wx.WXK_DOWN and (
460 ( current_line_number == self.LineCount -1 and
460 ( current_line_number == self.LineCount -1 and
461 event.Modifiers in (wx.MOD_NONE, wx.MOD_WIN) )
461 event.Modifiers in (wx.MOD_NONE, wx.MOD_WIN) )
462 or event.ControlDown() ):
462 or event.ControlDown() ):
463 new_buffer = self.get_history_next()
463 new_buffer = self.get_history_next()
464 if new_buffer is not None:
464 if new_buffer is not None:
465 self.input_buffer = new_buffer
465 self.input_buffer = new_buffer
466 # Tab-completion
466 # Tab-completion
467 elif event.KeyCode == ord('\t'):
467 elif event.KeyCode == ord('\t'):
468 current_line, current_line_number = self.CurLine
468 current_line, current_line_number = self.CurLine
469 if not re.match(r'^\s*$', current_line):
469 if not re.match(r'^\s*$', current_line):
470 self.complete_current_input()
470 self.complete_current_input()
471 if self.AutoCompActive():
471 if self.AutoCompActive():
472 wx.CallAfter(self._popup_completion, create=True)
472 wx.CallAfter(self._popup_completion, create=True)
473 else:
473 else:
474 event.Skip()
474 event.Skip()
475 else:
475 else:
476 ConsoleWidget._on_key_down(self, event, skip=skip)
476 ConsoleWidget._on_key_down(self, event, skip=skip)
477
477
478
478
479 def _on_key_up(self, event, skip=True):
479 def _on_key_up(self, event, skip=True):
480 """ Called when any key is released.
480 """ Called when any key is released.
481 """
481 """
482 if event.KeyCode in (59, ord('.')):
482 if event.KeyCode in (59, ord('.')):
483 # Intercepting '.'
483 # Intercepting '.'
484 event.Skip()
484 event.Skip()
485 wx.CallAfter(self._popup_completion, create=True)
485 wx.CallAfter(self._popup_completion, create=True)
486 else:
486 else:
487 ConsoleWidget._on_key_up(self, event, skip=skip)
487 ConsoleWidget._on_key_up(self, event, skip=skip)
488 if (self.input_buffer.split('\n')[-1] == self.continuation_prompt()
489 and self._input_state == 'readline'):
490 # Make sure the continuation_prompt is followed by a whitespace
491 position = self.GetCurrentPos()
492 self.input_buffer += ' '
493 self.GotoPos(position)
488
494
489
495
490 def _on_enter(self):
496 def _on_enter(self):
491 """ Called on return key down, in readline input_state.
497 """ Called on return key down, in readline input_state.
492 """
498 """
493 if self.debug:
499 if self.debug:
494 print >>sys.__stdout__, repr(self.input_buffer)
500 print >>sys.__stdout__, repr(self.input_buffer)
495 PrefilterFrontEnd._on_enter(self)
501 PrefilterFrontEnd._on_enter(self)
496
502
497
503
498 #--------------------------------------------------------------------------
504 #--------------------------------------------------------------------------
499 # EditWindow API
505 # EditWindow API
500 #--------------------------------------------------------------------------
506 #--------------------------------------------------------------------------
501
507
502 def OnUpdateUI(self, event):
508 def OnUpdateUI(self, event):
503 """ Override the OnUpdateUI of the EditWindow class, to prevent
509 """ Override the OnUpdateUI of the EditWindow class, to prevent
504 syntax highlighting both for faster redraw, and for more
510 syntax highlighting both for faster redraw, and for more
505 consistent look and feel.
511 consistent look and feel.
506 """
512 """
507 if not self._input_state == 'readline':
513 if not self._input_state == 'readline':
508 ConsoleWidget.OnUpdateUI(self, event)
514 ConsoleWidget.OnUpdateUI(self, event)
509
515
510 #--------------------------------------------------------------------------
516 #--------------------------------------------------------------------------
511 # Private API
517 # Private API
512 #--------------------------------------------------------------------------
518 #--------------------------------------------------------------------------
513
519
514 def _buffer_flush(self, event):
520 def _buffer_flush(self, event):
515 """ Called by the timer to flush the write buffer.
521 """ Called by the timer to flush the write buffer.
516
522
517 This is always called in the mainloop, by the wx timer.
523 This is always called in the mainloop, by the wx timer.
518 """
524 """
519 self._out_buffer_lock.acquire()
525 self._out_buffer_lock.acquire()
520 _out_buffer = self._out_buffer
526 _out_buffer = self._out_buffer
521 self._out_buffer = []
527 self._out_buffer = []
522 self._out_buffer_lock.release()
528 self._out_buffer_lock.release()
523 self.write(''.join(_out_buffer), refresh=False)
529 self.write(''.join(_out_buffer), refresh=False)
524
530
525
531
526 def _colorize_input_buffer(self):
532 def _colorize_input_buffer(self):
527 """ Keep the input buffer lines at a bright color.
533 """ Keep the input buffer lines at a bright color.
528 """
534 """
529 if not self._input_state in ('readline', 'raw_input'):
535 if not self._input_state in ('readline', 'raw_input'):
530 return
536 return
531 end_line = self.GetCurrentLine()
537 end_line = self.GetCurrentLine()
532 if not sys.platform == 'win32':
538 if not sys.platform == 'win32':
533 end_line += 1
539 end_line += 1
534 for i in range(self.current_prompt_line, end_line):
540 for i in range(self.current_prompt_line, end_line):
535 if i in self._markers:
541 if i in self._markers:
536 self.MarkerDeleteHandle(self._markers[i])
542 self.MarkerDeleteHandle(self._markers[i])
537 self._markers[i] = self.MarkerAdd(i, _INPUT_MARKER)
543 self._markers[i] = self.MarkerAdd(i, _INPUT_MARKER)
538
544
539
545
540 if __name__ == '__main__':
546 if __name__ == '__main__':
541 class MainWindow(wx.Frame):
547 class MainWindow(wx.Frame):
542 def __init__(self, parent, id, title):
548 def __init__(self, parent, id, title):
543 wx.Frame.__init__(self, parent, id, title, size=(300,250))
549 wx.Frame.__init__(self, parent, id, title, size=(300,250))
544 self._sizer = wx.BoxSizer(wx.VERTICAL)
550 self._sizer = wx.BoxSizer(wx.VERTICAL)
545 self.shell = WxController(self)
551 self.shell = WxController(self)
546 self._sizer.Add(self.shell, 1, wx.EXPAND)
552 self._sizer.Add(self.shell, 1, wx.EXPAND)
547 self.SetSizer(self._sizer)
553 self.SetSizer(self._sizer)
548 self.SetAutoLayout(1)
554 self.SetAutoLayout(1)
549 self.Show(True)
555 self.Show(True)
550
556
551 app = wx.PySimpleApp()
557 app = wx.PySimpleApp()
552 frame = MainWindow(None, wx.ID_ANY, 'Ipython')
558 frame = MainWindow(None, wx.ID_ANY, 'Ipython')
553 frame.shell.SetFocus()
559 frame.shell.SetFocus()
554 frame.SetSize((680, 460))
560 frame.SetSize((680, 460))
555 self = frame.shell
561 self = frame.shell
556
562
557 app.MainLoop()
563 app.MainLoop()
558
564
General Comments 0
You need to be logged in to leave comments. Login now