##// END OF EJS Templates
ENH: Enter adds lines at the right position
Gael Varoquaux -
Show More
@@ -1,355 +1,367 b''
1 """
1 """
2 Base front end class for all line-oriented frontends, rather than
2 Base front end class for all line-oriented frontends, rather than
3 block-oriented.
3 block-oriented.
4
4
5 Currently this focuses on synchronous frontends.
5 Currently this focuses on synchronous frontends.
6 """
6 """
7 __docformat__ = "restructuredtext en"
7 __docformat__ = "restructuredtext en"
8
8
9 #-------------------------------------------------------------------------------
9 #-------------------------------------------------------------------------------
10 # Copyright (C) 2008 The IPython Development Team
10 # Copyright (C) 2008 The IPython Development Team
11 #
11 #
12 # Distributed under the terms of the BSD License. The full license is in
12 # Distributed under the terms of the BSD License. The full license is in
13 # the file COPYING, distributed as part of this software.
13 # the file COPYING, distributed as part of this software.
14 #-------------------------------------------------------------------------------
14 #-------------------------------------------------------------------------------
15
15
16 #-------------------------------------------------------------------------------
16 #-------------------------------------------------------------------------------
17 # Imports
17 # Imports
18 #-------------------------------------------------------------------------------
18 #-------------------------------------------------------------------------------
19 import re
19 import re
20
20
21 import sys
21 import sys
22 import codeop
22 import codeop
23
23
24 from frontendbase import FrontEndBase
24 from frontendbase import FrontEndBase
25 from IPython.kernel.core.interpreter import Interpreter
25 from IPython.kernel.core.interpreter import Interpreter
26
26
27 def common_prefix(strings):
27 def common_prefix(strings):
28 """ Given a list of strings, return the common prefix between all
28 """ Given a list of strings, return the common prefix between all
29 these strings.
29 these strings.
30 """
30 """
31 ref = strings[0]
31 ref = strings[0]
32 prefix = ''
32 prefix = ''
33 for size in range(len(ref)):
33 for size in range(len(ref)):
34 test_prefix = ref[:size+1]
34 test_prefix = ref[:size+1]
35 for string in strings[1:]:
35 for string in strings[1:]:
36 if not string.startswith(test_prefix):
36 if not string.startswith(test_prefix):
37 return prefix
37 return prefix
38 prefix = test_prefix
38 prefix = test_prefix
39
39
40 return prefix
40 return prefix
41
41
42 #-------------------------------------------------------------------------------
42 #-------------------------------------------------------------------------------
43 # Base class for the line-oriented front ends
43 # Base class for the line-oriented front ends
44 #-------------------------------------------------------------------------------
44 #-------------------------------------------------------------------------------
45 class LineFrontEndBase(FrontEndBase):
45 class LineFrontEndBase(FrontEndBase):
46 """ Concrete implementation of the FrontEndBase class. This is meant
46 """ Concrete implementation of the FrontEndBase class. This is meant
47 to be the base class behind all the frontend that are line-oriented,
47 to be the base class behind all the frontend that are line-oriented,
48 rather than block-oriented.
48 rather than block-oriented.
49 """
49 """
50
50
51 # We need to keep the prompt number, to be able to increment
51 # We need to keep the prompt number, to be able to increment
52 # it when there is an exception.
52 # it when there is an exception.
53 prompt_number = 1
53 prompt_number = 1
54
54
55 # We keep a reference to the last result: it helps testing and
55 # We keep a reference to the last result: it helps testing and
56 # programatic control of the frontend.
56 # programatic control of the frontend.
57 last_result = dict(number=0)
57 last_result = dict(number=0)
58
58
59 # The last prompt displayed. Useful for continuation prompts.
59 # The last prompt displayed. Useful for continuation prompts.
60 last_prompt = ''
60 last_prompt = ''
61
61
62 # The input buffer being edited
62 # The input buffer being edited
63 input_buffer = ''
63 input_buffer = ''
64
64
65 # Set to true for debug output
65 # Set to true for debug output
66 debug = False
66 debug = False
67
67
68 # A banner to print at startup
68 # A banner to print at startup
69 banner = None
69 banner = None
70
70
71 #--------------------------------------------------------------------------
71 #--------------------------------------------------------------------------
72 # FrontEndBase interface
72 # FrontEndBase interface
73 #--------------------------------------------------------------------------
73 #--------------------------------------------------------------------------
74
74
75 def __init__(self, shell=None, history=None, banner=None, *args, **kwargs):
75 def __init__(self, shell=None, history=None, banner=None, *args, **kwargs):
76 if shell is None:
76 if shell is None:
77 shell = Interpreter()
77 shell = Interpreter()
78 FrontEndBase.__init__(self, shell=shell, history=history)
78 FrontEndBase.__init__(self, shell=shell, history=history)
79
79
80 if banner is not None:
80 if banner is not None:
81 self.banner = banner
81 self.banner = banner
82
82
83 def start(self):
83 def start(self):
84 """ Put the frontend in a state where it is ready for user
84 """ Put the frontend in a state where it is ready for user
85 interaction.
85 interaction.
86 """
86 """
87 if self.banner is not None:
87 if self.banner is not None:
88 self.write(self.banner, refresh=False)
88 self.write(self.banner, refresh=False)
89
89
90 self.new_prompt(self.input_prompt_template.substitute(number=1))
90 self.new_prompt(self.input_prompt_template.substitute(number=1))
91
91
92
92
93 def complete(self, line):
93 def complete(self, line):
94 """Complete line in engine's user_ns
94 """Complete line in engine's user_ns
95
95
96 Parameters
96 Parameters
97 ----------
97 ----------
98 line : string
98 line : string
99
99
100 Result
100 Result
101 ------
101 ------
102 The replacement for the line and the list of possible completions.
102 The replacement for the line and the list of possible completions.
103 """
103 """
104 completions = self.shell.complete(line)
104 completions = self.shell.complete(line)
105 complete_sep = re.compile('[\s\{\}\[\]\(\)\=]')
105 complete_sep = re.compile('[\s\{\}\[\]\(\)\=]')
106 if completions:
106 if completions:
107 prefix = common_prefix(completions)
107 prefix = common_prefix(completions)
108 residual = complete_sep.split(line)[:-1]
108 residual = complete_sep.split(line)[:-1]
109 line = line[:-len(residual)] + prefix
109 line = line[:-len(residual)] + prefix
110 return line, completions
110 return line, completions
111
111
112
112
113 def render_result(self, result):
113 def render_result(self, result):
114 """ Frontend-specific rendering of the result of a calculation
114 """ Frontend-specific rendering of the result of a calculation
115 that has been sent to an engine.
115 that has been sent to an engine.
116 """
116 """
117 if 'stdout' in result and result['stdout']:
117 if 'stdout' in result and result['stdout']:
118 self.write('\n' + result['stdout'])
118 self.write('\n' + result['stdout'])
119 if 'display' in result and result['display']:
119 if 'display' in result and result['display']:
120 self.write("%s%s\n" % (
120 self.write("%s%s\n" % (
121 self.output_prompt_template.substitute(
121 self.output_prompt_template.substitute(
122 number=result['number']),
122 number=result['number']),
123 result['display']['pprint']
123 result['display']['pprint']
124 ) )
124 ) )
125
125
126
126
127 def render_error(self, failure):
127 def render_error(self, failure):
128 """ Frontend-specific rendering of error.
128 """ Frontend-specific rendering of error.
129 """
129 """
130 self.write('\n\n'+str(failure)+'\n\n')
130 self.write('\n\n'+str(failure)+'\n\n')
131 return failure
131 return failure
132
132
133
133
134 def is_complete(self, string):
134 def is_complete(self, string):
135 """ Check if a string forms a complete, executable set of
135 """ Check if a string forms a complete, executable set of
136 commands.
136 commands.
137
137
138 For the line-oriented frontend, multi-line code is not executed
138 For the line-oriented frontend, multi-line code is not executed
139 as soon as it is complete: the users has to enter two line
139 as soon as it is complete: the users has to enter two line
140 returns.
140 returns.
141 """
141 """
142 if string in ('', '\n'):
142 if string in ('', '\n'):
143 # Prefiltering, eg through ipython0, may return an empty
143 # Prefiltering, eg through ipython0, may return an empty
144 # string although some operations have been accomplished. We
144 # string although some operations have been accomplished. We
145 # thus want to consider an empty string as a complete
145 # thus want to consider an empty string as a complete
146 # statement.
146 # statement.
147 return True
147 return True
148 elif ( len(self.input_buffer.split('\n'))>2
148 elif ( len(self.input_buffer.split('\n'))>2
149 and not re.findall(r"\n[\t ]*\n[\t ]*$", string)):
149 and not re.findall(r"\n[\t ]*\n[\t ]*$", string)):
150 return False
150 return False
151 else:
151 else:
152 self.capture_output()
152 self.capture_output()
153 try:
153 try:
154 # Add line returns here, to make sure that the statement is
154 # Add line returns here, to make sure that the statement is
155 # complete (except if '\' was used).
155 # complete (except if '\' was used).
156 # This should probably be done in a different place (like
156 # This should probably be done in a different place (like
157 # maybe 'prefilter_input' method? For now, this works.
157 # maybe 'prefilter_input' method? For now, this works.
158 clean_string = string.rstrip('\n')
158 clean_string = string.rstrip('\n')
159 if not clean_string.endswith('\\'): clean_string +='\n\n'
159 if not clean_string.endswith('\\'): clean_string +='\n\n'
160 is_complete = codeop.compile_command(clean_string,
160 is_complete = codeop.compile_command(clean_string,
161 "<string>", "exec")
161 "<string>", "exec")
162 self.release_output()
162 self.release_output()
163 except Exception, e:
163 except Exception, e:
164 # XXX: Hack: return True so that the
164 # XXX: Hack: return True so that the
165 # code gets executed and the error captured.
165 # code gets executed and the error captured.
166 is_complete = True
166 is_complete = True
167 return is_complete
167 return is_complete
168
168
169
169
170 def write(self, string, refresh=True):
170 def write(self, string, refresh=True):
171 """ Write some characters to the display.
171 """ Write some characters to the display.
172
172
173 Subclass should overide this method.
173 Subclass should overide this method.
174
174
175 The refresh keyword argument is used in frontends with an
175 The refresh keyword argument is used in frontends with an
176 event loop, to choose whether the write should trigget an UI
176 event loop, to choose whether the write should trigget an UI
177 refresh, and thus be syncrhonous, or not.
177 refresh, and thus be syncrhonous, or not.
178 """
178 """
179 print >>sys.__stderr__, string
179 print >>sys.__stderr__, string
180
180
181
181
182 def execute(self, python_string, raw_string=None):
182 def execute(self, python_string, raw_string=None):
183 """ Stores the raw_string in the history, and sends the
183 """ Stores the raw_string in the history, and sends the
184 python string to the interpreter.
184 python string to the interpreter.
185 """
185 """
186 if raw_string is None:
186 if raw_string is None:
187 raw_string = python_string
187 raw_string = python_string
188 # Create a false result, in case there is an exception
188 # Create a false result, in case there is an exception
189 self.last_result = dict(number=self.prompt_number)
189 self.last_result = dict(number=self.prompt_number)
190
190
191 ## try:
192 ## self.history.input_cache[-1] = raw_string.rstrip()
193 ## result = self.shell.execute(python_string)
194 ## self.last_result = result
195 ## self.render_result(result)
196 ## except:
197 ## self.show_traceback()
198 ## finally:
199 ## self.after_execute()
200
201 try:
191 try:
202 try:
192 try:
203 self.history.input_cache[-1] = raw_string.rstrip()
193 self.history.input_cache[-1] = raw_string.rstrip()
204 result = self.shell.execute(python_string)
194 result = self.shell.execute(python_string)
205 self.last_result = result
195 self.last_result = result
206 self.render_result(result)
196 self.render_result(result)
207 except:
197 except:
208 self.show_traceback()
198 self.show_traceback()
209 finally:
199 finally:
210 self.after_execute()
200 self.after_execute()
211
201
212
202
213 #--------------------------------------------------------------------------
203 #--------------------------------------------------------------------------
214 # LineFrontEndBase interface
204 # LineFrontEndBase interface
215 #--------------------------------------------------------------------------
205 #--------------------------------------------------------------------------
216
206
217 def prefilter_input(self, string):
207 def prefilter_input(self, string):
218 """ Prefilter the input to turn it in valid python.
208 """ Prefilter the input to turn it in valid python.
219 """
209 """
220 string = string.replace('\r\n', '\n')
210 string = string.replace('\r\n', '\n')
221 string = string.replace('\t', 4*' ')
211 string = string.replace('\t', 4*' ')
222 # Clean the trailing whitespace
212 # Clean the trailing whitespace
223 string = '\n'.join(l.rstrip() for l in string.split('\n'))
213 string = '\n'.join(l.rstrip() for l in string.split('\n'))
224 return string
214 return string
225
215
226
216
227 def after_execute(self):
217 def after_execute(self):
228 """ All the operations required after an execution to put the
218 """ All the operations required after an execution to put the
229 terminal back in a shape where it is usable.
219 terminal back in a shape where it is usable.
230 """
220 """
231 self.prompt_number += 1
221 self.prompt_number += 1
232 self.new_prompt(self.input_prompt_template.substitute(
222 self.new_prompt(self.input_prompt_template.substitute(
233 number=(self.last_result['number'] + 1)))
223 number=(self.last_result['number'] + 1)))
234 # Start a new empty history entry
224 # Start a new empty history entry
235 self._add_history(None, '')
225 self._add_history(None, '')
236 self.history_cursor = len(self.history.input_cache) - 1
226 self.history_cursor = len(self.history.input_cache) - 1
237
227
238
228
239 def complete_current_input(self):
229 def complete_current_input(self):
240 """ Do code completion on current line.
230 """ Do code completion on current line.
241 """
231 """
242 if self.debug:
232 if self.debug:
243 print >>sys.__stdout__, "complete_current_input",
233 print >>sys.__stdout__, "complete_current_input",
244 line = self.input_buffer
234 line = self.input_buffer
245 new_line, completions = self.complete(line)
235 new_line, completions = self.complete(line)
246 if len(completions)>1:
236 if len(completions)>1:
247 self.write_completion(completions, new_line=new_line)
237 self.write_completion(completions, new_line=new_line)
248 elif not line == new_line:
238 elif not line == new_line:
249 self.input_buffer = new_line
239 self.input_buffer = new_line
250 if self.debug:
240 if self.debug:
251 print >>sys.__stdout__, 'line', line
241 print >>sys.__stdout__, 'line', line
252 print >>sys.__stdout__, 'new_line', new_line
242 print >>sys.__stdout__, 'new_line', new_line
253 print >>sys.__stdout__, completions
243 print >>sys.__stdout__, completions
254
244
255
245
256 def get_line_width(self):
246 def get_line_width(self):
257 """ Return the width of the line in characters.
247 """ Return the width of the line in characters.
258 """
248 """
259 return 80
249 return 80
260
250
261
251
262 def write_completion(self, possibilities, new_line=None):
252 def write_completion(self, possibilities, new_line=None):
263 """ Write the list of possible completions.
253 """ Write the list of possible completions.
264
254
265 new_line is the completed input line that should be displayed
255 new_line is the completed input line that should be displayed
266 after the completion are writen. If None, the input_buffer
256 after the completion are writen. If None, the input_buffer
267 before the completion is used.
257 before the completion is used.
268 """
258 """
269 if new_line is None:
259 if new_line is None:
270 new_line = self.input_buffer
260 new_line = self.input_buffer
271
261
272 self.write('\n')
262 self.write('\n')
273 max_len = len(max(possibilities, key=len)) + 1
263 max_len = len(max(possibilities, key=len)) + 1
274
264
275 # Now we check how much symbol we can put on a line...
265 # Now we check how much symbol we can put on a line...
276 chars_per_line = self.get_line_width()
266 chars_per_line = self.get_line_width()
277 symbols_per_line = max(1, chars_per_line/max_len)
267 symbols_per_line = max(1, chars_per_line/max_len)
278
268
279 pos = 1
269 pos = 1
280 completion_string = []
270 completion_string = []
281 for symbol in possibilities:
271 for symbol in possibilities:
282 if pos < symbols_per_line:
272 if pos < symbols_per_line:
283 completion_string.append(symbol.ljust(max_len))
273 completion_string.append(symbol.ljust(max_len))
284 pos += 1
274 pos += 1
285 else:
275 else:
286 completion_string.append(symbol.rstrip() + '\n')
276 completion_string.append(symbol.rstrip() + '\n')
287 pos = 1
277 pos = 1
288 self.write(''.join(completion_string))
278 self.write(''.join(completion_string))
289 self.new_prompt(self.input_prompt_template.substitute(
279 self.new_prompt(self.input_prompt_template.substitute(
290 number=self.last_result['number'] + 1))
280 number=self.last_result['number'] + 1))
291 self.input_buffer = new_line
281 self.input_buffer = new_line
292
282
293
283
294 def new_prompt(self, prompt):
284 def new_prompt(self, prompt):
295 """ Prints a prompt and starts a new editing buffer.
285 """ Prints a prompt and starts a new editing buffer.
296
286
297 Subclasses should use this method to make sure that the
287 Subclasses should use this method to make sure that the
298 terminal is put in a state favorable for a new line
288 terminal is put in a state favorable for a new line
299 input.
289 input.
300 """
290 """
301 self.input_buffer = ''
291 self.input_buffer = ''
302 self.write(prompt)
292 self.write(prompt)
303
293
304
294
305 def continuation_prompt(self):
295 def continuation_prompt(self):
306 """Returns the current continuation prompt.
296 """Returns the current continuation prompt.
307 """
297 """
308 return ("."*(len(self.last_prompt)-2) + ': ')
298 return ("."*(len(self.last_prompt)-2) + ': ')
309
299
310
300
311 def execute_command(self, command, hidden=False):
301 def execute_command(self, command, hidden=False):
312 """ Execute a command, not only in the model, but also in the
302 """ Execute a command, not only in the model, but also in the
313 view, if any.
303 view, if any.
314 """
304 """
315 return self.shell.execute(command)
305 return self.shell.execute(command)
316
306
317 #--------------------------------------------------------------------------
307 #--------------------------------------------------------------------------
318 # Private API
308 # Private API
319 #--------------------------------------------------------------------------
309 #--------------------------------------------------------------------------
320
310
321 def _on_enter(self):
311 def _on_enter(self, new_line_pos=0):
322 """ Called when the return key is pressed in a line editing
312 """ Called when the return key is pressed in a line editing
323 buffer.
313 buffer.
314
315 Parameters
316 ----------
317 new_line_pos : integer, optional
318 Position of the new line to add, starting from the
319 end (0 adds a new line after the last line, -1 before
320 the last line...)
321
322 Returns
323 -------
324 True if execution is triggered
324 """
325 """
325 current_buffer = self.input_buffer
326 current_buffer = self.input_buffer
326 # XXX: This string replace is ugly, but there should be no way it
327 # XXX: This string replace is ugly, but there should be no way it
327 # fails.
328 # fails.
328 prompt_less_buffer = re.sub('^' + self.continuation_prompt(),
329 prompt_less_buffer = re.sub('^' + self.continuation_prompt(),
329 '', current_buffer).replace('\n' + self.continuation_prompt(),
330 '', current_buffer).replace('\n' + self.continuation_prompt(),
330 '\n')
331 '\n')
331 cleaned_buffer = self.prefilter_input(prompt_less_buffer)
332 cleaned_buffer = self.prefilter_input(prompt_less_buffer)
332 if self.is_complete(cleaned_buffer):
333 if self.is_complete(cleaned_buffer):
333 self.execute(cleaned_buffer, raw_string=current_buffer)
334 self.execute(cleaned_buffer, raw_string=current_buffer)
335 return True
334 else:
336 else:
335 self.input_buffer += self.continuation_prompt() + \
337 new_line_pos = -new_line_pos
336 self._get_indent_string(prompt_less_buffer[:-1])
338 lines = current_buffer.split('\n')[:-1]
337 if len(current_buffer.split('\n')) == 2:
339 prompt_less_lines = prompt_less_buffer.split('\n')
338 self.input_buffer += '\t'
340 new_line = self.continuation_prompt() + \
341 self._get_indent_string('\n'.join(
342 prompt_less_lines[:new_line_pos-1]))
343 if len(lines) == 2:
344 new_line += '\t'
339 if current_buffer[:-1].split('\n')[-1].rstrip().endswith(':'):
345 if current_buffer[:-1].split('\n')[-1].rstrip().endswith(':'):
340 self.input_buffer += '\t'
346 new_line += '\t'
341
347
348 if new_line_pos == 0:
349 lines.append(new_line)
350 else:
351 lines.insert(new_line_pos, new_line)
352 self.input_buffer = '\n'.join(lines)
353
342
354
343 def _get_indent_string(self, string):
355 def _get_indent_string(self, string):
344 """ Return the string of whitespace that prefixes a line. Used to
356 """ Return the string of whitespace that prefixes a line. Used to
345 add the right amount of indendation when creating a new line.
357 add the right amount of indendation when creating a new line.
346 """
358 """
347 string = string.replace('\t', ' '*4)
359 string = string.replace('\t', ' '*4)
348 string = string.split('\n')[-1]
360 string = string.split('\n')[-1]
349 indent_chars = len(string) - len(string.lstrip())
361 indent_chars = len(string) - len(string.lstrip())
350 indent_string = '\t'*(indent_chars // 4) + \
362 indent_string = '\t'*(indent_chars // 4) + \
351 ' '*(indent_chars % 4)
363 ' '*(indent_chars % 4)
352
364
353 return indent_string
365 return indent_string
354
366
355
367
@@ -1,644 +1,655 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 import string
28 import string
29
29
30 LINESEP = '\n'
30 LINESEP = '\n'
31 if sys.platform == 'win32':
31 if sys.platform == 'win32':
32 LINESEP = '\n\r'
32 LINESEP = '\n\r'
33
33
34 import re
34 import re
35
35
36 # FIXME: Need to provide an API for non user-generated display on the
36 # FIXME: Need to provide an API for non user-generated display on the
37 # screen: this should not be editable by the user.
37 # screen: this should not be editable by the user.
38 #-------------------------------------------------------------------------------
38 #-------------------------------------------------------------------------------
39 # Constants
39 # Constants
40 #-------------------------------------------------------------------------------
40 #-------------------------------------------------------------------------------
41 _COMPLETE_BUFFER_MARKER = 31
41 _COMPLETE_BUFFER_MARKER = 31
42 _ERROR_MARKER = 30
42 _ERROR_MARKER = 30
43 _INPUT_MARKER = 29
43 _INPUT_MARKER = 29
44
44
45 _DEFAULT_SIZE = 10
45 _DEFAULT_SIZE = 10
46 if sys.platform == 'darwin':
46 if sys.platform == 'darwin':
47 _DEFAULT_SIZE = 12
47 _DEFAULT_SIZE = 12
48
48
49 _DEFAULT_STYLE = {
49 _DEFAULT_STYLE = {
50 #background definition
50 #background definition
51 'default' : 'size:%d' % _DEFAULT_SIZE,
51 'default' : 'size:%d' % _DEFAULT_SIZE,
52 'bracegood' : 'fore:#00AA00,back:#000000,bold',
52 'bracegood' : 'fore:#00AA00,back:#000000,bold',
53 'bracebad' : 'fore:#FF0000,back:#000000,bold',
53 'bracebad' : 'fore:#FF0000,back:#000000,bold',
54
54
55 # Edge column: a number of None
55 # Edge column: a number of None
56 'edge_column' : -1,
56 'edge_column' : -1,
57
57
58 # properties for the various Python lexer styles
58 # properties for the various Python lexer styles
59 'comment' : 'fore:#007F00',
59 'comment' : 'fore:#007F00',
60 'number' : 'fore:#007F7F',
60 'number' : 'fore:#007F7F',
61 'string' : 'fore:#7F007F,italic',
61 'string' : 'fore:#7F007F,italic',
62 'char' : 'fore:#7F007F,italic',
62 'char' : 'fore:#7F007F,italic',
63 'keyword' : 'fore:#00007F,bold',
63 'keyword' : 'fore:#00007F,bold',
64 'triple' : 'fore:#7F0000',
64 'triple' : 'fore:#7F0000',
65 'tripledouble' : 'fore:#7F0000',
65 'tripledouble' : 'fore:#7F0000',
66 'class' : 'fore:#0000FF,bold,underline',
66 'class' : 'fore:#0000FF,bold,underline',
67 'def' : 'fore:#007F7F,bold',
67 'def' : 'fore:#007F7F,bold',
68 'operator' : 'bold'
68 'operator' : 'bold'
69 }
69 }
70
70
71 # new style numbers
71 # new style numbers
72 _STDOUT_STYLE = 15
72 _STDOUT_STYLE = 15
73 _STDERR_STYLE = 16
73 _STDERR_STYLE = 16
74 _TRACE_STYLE = 17
74 _TRACE_STYLE = 17
75
75
76
76
77 # system colors
77 # system colors
78 #SYS_COLOUR_BACKGROUND = wx.SystemSettings.GetColour(wx.SYS_COLOUR_BACKGROUND)
78 #SYS_COLOUR_BACKGROUND = wx.SystemSettings.GetColour(wx.SYS_COLOUR_BACKGROUND)
79
79
80 # Translation table from ANSI escape sequences to color.
80 # Translation table from ANSI escape sequences to color.
81 ANSI_STYLES = {'0;30': [0, 'BLACK'], '0;31': [1, 'RED'],
81 ANSI_STYLES = {'0;30': [0, 'BLACK'], '0;31': [1, 'RED'],
82 '0;32': [2, 'GREEN'], '0;33': [3, 'BROWN'],
82 '0;32': [2, 'GREEN'], '0;33': [3, 'BROWN'],
83 '0;34': [4, 'BLUE'], '0;35': [5, 'PURPLE'],
83 '0;34': [4, 'BLUE'], '0;35': [5, 'PURPLE'],
84 '0;36': [6, 'CYAN'], '0;37': [7, 'LIGHT GREY'],
84 '0;36': [6, 'CYAN'], '0;37': [7, 'LIGHT GREY'],
85 '1;30': [8, 'DARK GREY'], '1;31': [9, 'RED'],
85 '1;30': [8, 'DARK GREY'], '1;31': [9, 'RED'],
86 '1;32': [10, 'SEA GREEN'], '1;33': [11, 'YELLOW'],
86 '1;32': [10, 'SEA GREEN'], '1;33': [11, 'YELLOW'],
87 '1;34': [12, 'LIGHT BLUE'], '1;35':
87 '1;34': [12, 'LIGHT BLUE'], '1;35':
88 [13, 'MEDIUM VIOLET RED'],
88 [13, 'MEDIUM VIOLET RED'],
89 '1;36': [14, 'LIGHT STEEL BLUE'], '1;37': [15, 'YELLOW']}
89 '1;36': [14, 'LIGHT STEEL BLUE'], '1;37': [15, 'YELLOW']}
90
90
91 #we define platform specific fonts
91 #we define platform specific fonts
92 if wx.Platform == '__WXMSW__':
92 if wx.Platform == '__WXMSW__':
93 FACES = { 'times': 'Times New Roman',
93 FACES = { 'times': 'Times New Roman',
94 'mono' : 'Courier New',
94 'mono' : 'Courier New',
95 'helv' : 'Arial',
95 'helv' : 'Arial',
96 'other': 'Comic Sans MS',
96 'other': 'Comic Sans MS',
97 'size' : 10,
97 'size' : 10,
98 'size2': 8,
98 'size2': 8,
99 }
99 }
100 elif wx.Platform == '__WXMAC__':
100 elif wx.Platform == '__WXMAC__':
101 FACES = { 'times': 'Times New Roman',
101 FACES = { 'times': 'Times New Roman',
102 'mono' : 'Monaco',
102 'mono' : 'Monaco',
103 'helv' : 'Arial',
103 'helv' : 'Arial',
104 'other': 'Comic Sans MS',
104 'other': 'Comic Sans MS',
105 'size' : 10,
105 'size' : 10,
106 'size2': 8,
106 'size2': 8,
107 }
107 }
108 else:
108 else:
109 FACES = { 'times': 'Times',
109 FACES = { 'times': 'Times',
110 'mono' : 'Courier',
110 'mono' : 'Courier',
111 'helv' : 'Helvetica',
111 'helv' : 'Helvetica',
112 'other': 'new century schoolbook',
112 'other': 'new century schoolbook',
113 'size' : 10,
113 'size' : 10,
114 'size2': 8,
114 'size2': 8,
115 }
115 }
116
116
117
117
118 #-------------------------------------------------------------------------------
118 #-------------------------------------------------------------------------------
119 # The console widget class
119 # The console widget class
120 #-------------------------------------------------------------------------------
120 #-------------------------------------------------------------------------------
121 class ConsoleWidget(editwindow.EditWindow):
121 class ConsoleWidget(editwindow.EditWindow):
122 """ Specialized styled text control view for console-like workflow.
122 """ Specialized styled text control view for console-like workflow.
123
123
124 This widget is mainly interested in dealing with the prompt and
124 This widget is mainly interested in dealing with the prompt and
125 keeping the cursor inside the editing line.
125 keeping the cursor inside the editing line.
126 """
126 """
127
127
128 # This is where the title captured from the ANSI escape sequences are
128 # This is where the title captured from the ANSI escape sequences are
129 # stored.
129 # stored.
130 title = 'Console'
130 title = 'Console'
131
131
132 # Last prompt printed
132 # Last prompt printed
133 last_prompt = ''
133 last_prompt = ''
134
134
135 # The buffer being edited.
135 # The buffer being edited.
136 def _set_input_buffer(self, string):
136 def _set_input_buffer(self, string):
137 self.SetSelection(self.current_prompt_pos, self.GetLength())
137 self.SetSelection(self.current_prompt_pos, self.GetLength())
138 self.ReplaceSelection(string)
138 self.ReplaceSelection(string)
139 self.GotoPos(self.GetLength())
139 self.GotoPos(self.GetLength())
140
140
141 def _get_input_buffer(self):
141 def _get_input_buffer(self):
142 """ Returns the text in current edit buffer.
142 """ Returns the text in current edit buffer.
143 """
143 """
144 input_buffer = self.GetTextRange(self.current_prompt_pos,
144 input_buffer = self.GetTextRange(self.current_prompt_pos,
145 self.GetLength())
145 self.GetLength())
146 input_buffer = input_buffer.replace(LINESEP, '\n')
146 input_buffer = input_buffer.replace(LINESEP, '\n')
147 return input_buffer
147 return input_buffer
148
148
149 input_buffer = property(_get_input_buffer, _set_input_buffer)
149 input_buffer = property(_get_input_buffer, _set_input_buffer)
150
150
151 style = _DEFAULT_STYLE.copy()
151 style = _DEFAULT_STYLE.copy()
152
152
153 # Translation table from ANSI escape sequences to color. Override
153 # Translation table from ANSI escape sequences to color. Override
154 # this to specify your colors.
154 # this to specify your colors.
155 ANSI_STYLES = ANSI_STYLES.copy()
155 ANSI_STYLES = ANSI_STYLES.copy()
156
156
157 # Font faces
157 # Font faces
158 faces = FACES.copy()
158 faces = FACES.copy()
159
159
160 # Store the last time a refresh was done
160 # Store the last time a refresh was done
161 _last_refresh_time = 0
161 _last_refresh_time = 0
162
162
163 #--------------------------------------------------------------------------
163 #--------------------------------------------------------------------------
164 # Public API
164 # Public API
165 #--------------------------------------------------------------------------
165 #--------------------------------------------------------------------------
166
166
167 def __init__(self, parent, id=wx.ID_ANY, pos=wx.DefaultPosition,
167 def __init__(self, parent, id=wx.ID_ANY, pos=wx.DefaultPosition,
168 size=wx.DefaultSize, style=wx.WANTS_CHARS, ):
168 size=wx.DefaultSize, style=wx.WANTS_CHARS, ):
169 editwindow.EditWindow.__init__(self, parent, id, pos, size, style)
169 editwindow.EditWindow.__init__(self, parent, id, pos, size, style)
170 self.configure_scintilla()
170 self.configure_scintilla()
171 # Track if 'enter' key as ever been processed
171 # Track if 'enter' key as ever been processed
172 # This variable will only be reallowed until key goes up
172 # This variable will only be reallowed until key goes up
173 self.enter_catched = False
173 self.enter_catched = False
174 self.current_prompt_pos = 0
174 self.current_prompt_pos = 0
175
175
176 self.Bind(wx.EVT_KEY_DOWN, self._on_key_down)
176 self.Bind(wx.EVT_KEY_DOWN, self._on_key_down)
177 self.Bind(wx.EVT_KEY_UP, self._on_key_up)
177 self.Bind(wx.EVT_KEY_UP, self._on_key_up)
178
178
179
179
180 def write(self, text, refresh=True):
180 def write(self, text, refresh=True):
181 """ Write given text to buffer, while translating the ansi escape
181 """ Write given text to buffer, while translating the ansi escape
182 sequences.
182 sequences.
183 """
183 """
184 # XXX: do not put print statements to sys.stdout/sys.stderr in
184 # XXX: do not put print statements to sys.stdout/sys.stderr in
185 # this method, the print statements will call this method, as
185 # this method, the print statements will call this method, as
186 # you will end up with an infinit loop
186 # you will end up with an infinit loop
187 title = self.title_pat.split(text)
187 title = self.title_pat.split(text)
188 if len(title)>1:
188 if len(title)>1:
189 self.title = title[-2]
189 self.title = title[-2]
190
190
191 text = self.title_pat.sub('', text)
191 text = self.title_pat.sub('', text)
192 segments = self.color_pat.split(text)
192 segments = self.color_pat.split(text)
193 segment = segments.pop(0)
193 segment = segments.pop(0)
194 self.GotoPos(self.GetLength())
194 self.GotoPos(self.GetLength())
195 self.StartStyling(self.GetLength(), 0xFF)
195 self.StartStyling(self.GetLength(), 0xFF)
196 try:
196 try:
197 self.AppendText(segment)
197 self.AppendText(segment)
198 except UnicodeDecodeError:
198 except UnicodeDecodeError:
199 # XXX: Do I really want to skip the exception?
199 # XXX: Do I really want to skip the exception?
200 pass
200 pass
201
201
202 if segments:
202 if segments:
203 for ansi_tag, text in zip(segments[::2], segments[1::2]):
203 for ansi_tag, text in zip(segments[::2], segments[1::2]):
204 self.StartStyling(self.GetLength(), 0xFF)
204 self.StartStyling(self.GetLength(), 0xFF)
205 try:
205 try:
206 self.AppendText(text)
206 self.AppendText(text)
207 except UnicodeDecodeError:
207 except UnicodeDecodeError:
208 # XXX: Do I really want to skip the exception?
208 # XXX: Do I really want to skip the exception?
209 pass
209 pass
210
210
211 if ansi_tag not in self.ANSI_STYLES:
211 if ansi_tag not in self.ANSI_STYLES:
212 style = 0
212 style = 0
213 else:
213 else:
214 style = self.ANSI_STYLES[ansi_tag][0]
214 style = self.ANSI_STYLES[ansi_tag][0]
215
215
216 self.SetStyling(len(text), style)
216 self.SetStyling(len(text), style)
217
217
218 self.GotoPos(self.GetLength())
218 self.GotoPos(self.GetLength())
219 if refresh:
219 if refresh:
220 current_time = time.time()
220 current_time = time.time()
221 if current_time - self._last_refresh_time > 0.03:
221 if current_time - self._last_refresh_time > 0.03:
222 if sys.platform == 'win32':
222 if sys.platform == 'win32':
223 wx.SafeYield()
223 wx.SafeYield()
224 else:
224 else:
225 wx.Yield()
225 wx.Yield()
226 # self.ProcessEvent(wx.PaintEvent())
226 # self.ProcessEvent(wx.PaintEvent())
227 self._last_refresh_time = current_time
227 self._last_refresh_time = current_time
228
228
229
229
230 def new_prompt(self, prompt):
230 def new_prompt(self, prompt):
231 """ Prints a prompt at start of line, and move the start of the
231 """ Prints a prompt at start of line, and move the start of the
232 current block there.
232 current block there.
233
233
234 The prompt can be given with ascii escape sequences.
234 The prompt can be given with ascii escape sequences.
235 """
235 """
236 self.write(prompt, refresh=False)
236 self.write(prompt, refresh=False)
237 # now we update our cursor giving end of prompt
237 # now we update our cursor giving end of prompt
238 self.current_prompt_pos = self.GetLength()
238 self.current_prompt_pos = self.GetLength()
239 self.current_prompt_line = self.GetCurrentLine()
239 self.current_prompt_line = self.GetCurrentLine()
240 self.EnsureCaretVisible()
240 self.EnsureCaretVisible()
241 self.last_prompt = prompt
241 self.last_prompt = prompt
242
242
243
243
244 def continuation_prompt(self):
244 def continuation_prompt(self):
245 """ Returns the current continuation prompt.
245 """ Returns the current continuation prompt.
246 We need to implement this method here to deal with the
246 We need to implement this method here to deal with the
247 ascii escape sequences cleaning up.
247 ascii escape sequences cleaning up.
248 """
248 """
249 # ASCII-less prompt
249 # ASCII-less prompt
250 ascii_less = ''.join(self.color_pat.split(self.last_prompt)[2::2])
250 ascii_less = ''.join(self.color_pat.split(self.last_prompt)[2::2])
251 return "."*(len(ascii_less)-2) + ': '
251 return "."*(len(ascii_less)-2) + ': '
252
252
253
253
254 def scroll_to_bottom(self):
254 def scroll_to_bottom(self):
255 maxrange = self.GetScrollRange(wx.VERTICAL)
255 maxrange = self.GetScrollRange(wx.VERTICAL)
256 self.ScrollLines(maxrange)
256 self.ScrollLines(maxrange)
257
257
258
258
259 def pop_completion(self, possibilities, offset=0):
259 def pop_completion(self, possibilities, offset=0):
260 """ Pops up an autocompletion menu. Offset is the offset
260 """ Pops up an autocompletion menu. Offset is the offset
261 in characters of the position at which the menu should
261 in characters of the position at which the menu should
262 appear, relativ to the cursor.
262 appear, relativ to the cursor.
263 """
263 """
264 self.AutoCompSetIgnoreCase(False)
264 self.AutoCompSetIgnoreCase(False)
265 self.AutoCompSetAutoHide(False)
265 self.AutoCompSetAutoHide(False)
266 self.AutoCompSetMaxHeight(len(possibilities))
266 self.AutoCompSetMaxHeight(len(possibilities))
267 self.AutoCompShow(offset, " ".join(possibilities))
267 self.AutoCompShow(offset, " ".join(possibilities))
268
268
269
269
270 def get_line_width(self):
270 def get_line_width(self):
271 """ Return the width of the line in characters.
271 """ Return the width of the line in characters.
272 """
272 """
273 return self.GetSize()[0]/self.GetCharWidth()
273 return self.GetSize()[0]/self.GetCharWidth()
274
274
275
275
276 def configure_scintilla(self):
276 def configure_scintilla(self):
277
277
278 p = self.style
278 p = self.style
279
279
280 #First we define the special background colors
280 #First we define the special background colors
281 if 'trace' in p:
281 if 'trace' in p:
282 _COMPLETE_BUFFER_BG = p['trace']
282 _COMPLETE_BUFFER_BG = p['trace']
283 else:
283 else:
284 _COMPLETE_BUFFER_BG = '#FAFAF1' # Nice green
284 _COMPLETE_BUFFER_BG = '#FAFAF1' # Nice green
285
285
286 if 'stdout' in p:
286 if 'stdout' in p:
287 _INPUT_BUFFER_BG = p['stdout']
287 _INPUT_BUFFER_BG = p['stdout']
288 else:
288 else:
289 _INPUT_BUFFER_BG = '#FDFFD3' # Nice yellow
289 _INPUT_BUFFER_BG = '#FDFFD3' # Nice yellow
290
290
291 if 'stderr' in p:
291 if 'stderr' in p:
292 _ERROR_BG = p['stderr']
292 _ERROR_BG = p['stderr']
293 else:
293 else:
294 _ERROR_BG = '#FFF1F1' # Nice red
294 _ERROR_BG = '#FFF1F1' # Nice red
295
295
296 # Marker for complete buffer.
296 # Marker for complete buffer.
297 self.MarkerDefine(_COMPLETE_BUFFER_MARKER, stc.STC_MARK_BACKGROUND,
297 self.MarkerDefine(_COMPLETE_BUFFER_MARKER, stc.STC_MARK_BACKGROUND,
298 background = _COMPLETE_BUFFER_BG)
298 background = _COMPLETE_BUFFER_BG)
299
299
300 # Marker for current input buffer.
300 # Marker for current input buffer.
301 self.MarkerDefine(_INPUT_MARKER, stc.STC_MARK_BACKGROUND,
301 self.MarkerDefine(_INPUT_MARKER, stc.STC_MARK_BACKGROUND,
302 background = _INPUT_BUFFER_BG)
302 background = _INPUT_BUFFER_BG)
303 # Marker for tracebacks.
303 # Marker for tracebacks.
304 self.MarkerDefine(_ERROR_MARKER, stc.STC_MARK_BACKGROUND,
304 self.MarkerDefine(_ERROR_MARKER, stc.STC_MARK_BACKGROUND,
305 background = _ERROR_BG)
305 background = _ERROR_BG)
306
306
307 self.SetEOLMode(stc.STC_EOL_LF)
307 self.SetEOLMode(stc.STC_EOL_LF)
308
308
309 # Ctrl"+" or Ctrl "-" can be used to zoomin/zoomout the text inside
309 # Ctrl"+" or Ctrl "-" can be used to zoomin/zoomout the text inside
310 # the widget
310 # the widget
311 self.CmdKeyAssign(ord('+'), stc.STC_SCMOD_CTRL, stc.STC_CMD_ZOOMIN)
311 self.CmdKeyAssign(ord('+'), stc.STC_SCMOD_CTRL, stc.STC_CMD_ZOOMIN)
312 self.CmdKeyAssign(ord('-'), stc.STC_SCMOD_CTRL, stc.STC_CMD_ZOOMOUT)
312 self.CmdKeyAssign(ord('-'), stc.STC_SCMOD_CTRL, stc.STC_CMD_ZOOMOUT)
313 # Also allow Ctrl Shift "=" for poor non US keyboard users.
313 # Also allow Ctrl Shift "=" for poor non US keyboard users.
314 self.CmdKeyAssign(ord('='), stc.STC_SCMOD_CTRL|stc.STC_SCMOD_SHIFT,
314 self.CmdKeyAssign(ord('='), stc.STC_SCMOD_CTRL|stc.STC_SCMOD_SHIFT,
315 stc.STC_CMD_ZOOMIN)
315 stc.STC_CMD_ZOOMIN)
316
316
317 # Keys: we need to clear some of the keys the that don't play
317 # Keys: we need to clear some of the keys the that don't play
318 # well with a console.
318 # well with a console.
319 self.CmdKeyClear(ord('D'), stc.STC_SCMOD_CTRL)
319 self.CmdKeyClear(ord('D'), stc.STC_SCMOD_CTRL)
320 self.CmdKeyClear(ord('L'), stc.STC_SCMOD_CTRL)
320 self.CmdKeyClear(ord('L'), stc.STC_SCMOD_CTRL)
321 self.CmdKeyClear(ord('T'), stc.STC_SCMOD_CTRL)
321 self.CmdKeyClear(ord('T'), stc.STC_SCMOD_CTRL)
322 self.CmdKeyClear(ord('A'), stc.STC_SCMOD_CTRL)
322 self.CmdKeyClear(ord('A'), stc.STC_SCMOD_CTRL)
323
323
324 self.SetEOLMode(stc.STC_EOL_CRLF)
324 self.SetEOLMode(stc.STC_EOL_CRLF)
325 self.SetWrapMode(stc.STC_WRAP_CHAR)
325 self.SetWrapMode(stc.STC_WRAP_CHAR)
326 self.SetWrapMode(stc.STC_WRAP_WORD)
326 self.SetWrapMode(stc.STC_WRAP_WORD)
327 self.SetBufferedDraw(True)
327 self.SetBufferedDraw(True)
328
328
329 if 'antialiasing' in p:
329 if 'antialiasing' in p:
330 self.SetUseAntiAliasing(p['antialiasing'])
330 self.SetUseAntiAliasing(p['antialiasing'])
331 else:
331 else:
332 self.SetUseAntiAliasing(True)
332 self.SetUseAntiAliasing(True)
333
333
334 self.SetLayoutCache(stc.STC_CACHE_PAGE)
334 self.SetLayoutCache(stc.STC_CACHE_PAGE)
335 self.SetUndoCollection(False)
335 self.SetUndoCollection(False)
336 self.SetUseTabs(True)
336 self.SetUseTabs(True)
337 self.SetIndent(4)
337 self.SetIndent(4)
338 self.SetTabWidth(4)
338 self.SetTabWidth(4)
339
339
340 # we don't want scintilla's autocompletion to choose
340 # we don't want scintilla's autocompletion to choose
341 # automaticaly out of a single choice list, as we pop it up
341 # automaticaly out of a single choice list, as we pop it up
342 # automaticaly
342 # automaticaly
343 self.AutoCompSetChooseSingle(False)
343 self.AutoCompSetChooseSingle(False)
344 self.AutoCompSetMaxHeight(10)
344 self.AutoCompSetMaxHeight(10)
345 # XXX: this doesn't seem to have an effect.
345 # XXX: this doesn't seem to have an effect.
346 self.AutoCompSetFillUps('\n')
346 self.AutoCompSetFillUps('\n')
347
347
348 self.SetMargins(3, 3) #text is moved away from border with 3px
348 self.SetMargins(3, 3) #text is moved away from border with 3px
349 # Suppressing Scintilla margins
349 # Suppressing Scintilla margins
350 self.SetMarginWidth(0, 0)
350 self.SetMarginWidth(0, 0)
351 self.SetMarginWidth(1, 0)
351 self.SetMarginWidth(1, 0)
352 self.SetMarginWidth(2, 0)
352 self.SetMarginWidth(2, 0)
353
353
354 # Xterm escape sequences
354 # Xterm escape sequences
355 self.color_pat = re.compile('\x01?\x1b\[(.*?)m\x02?')
355 self.color_pat = re.compile('\x01?\x1b\[(.*?)m\x02?')
356 self.title_pat = re.compile('\x1b]0;(.*?)\x07')
356 self.title_pat = re.compile('\x1b]0;(.*?)\x07')
357
357
358 # styles
358 # styles
359
359
360 if 'carret_color' in p:
360 if 'carret_color' in p:
361 self.SetCaretForeground(p['carret_color'])
361 self.SetCaretForeground(p['carret_color'])
362 else:
362 else:
363 self.SetCaretForeground('BLACK')
363 self.SetCaretForeground('BLACK')
364
364
365 if 'background_color' in p:
365 if 'background_color' in p:
366 background_color = p['background_color']
366 background_color = p['background_color']
367 else:
367 else:
368 background_color = 'WHITE'
368 background_color = 'WHITE'
369
369
370 if 'default' in p:
370 if 'default' in p:
371 if 'back' not in p['default']:
371 if 'back' not in p['default']:
372 p['default'] += ',back:%s' % background_color
372 p['default'] += ',back:%s' % background_color
373 if 'size' not in p['default']:
373 if 'size' not in p['default']:
374 p['default'] += ',size:%s' % self.faces['size']
374 p['default'] += ',size:%s' % self.faces['size']
375 if 'face' not in p['default']:
375 if 'face' not in p['default']:
376 p['default'] += ',face:%s' % self.faces['mono']
376 p['default'] += ',face:%s' % self.faces['mono']
377
377
378 self.StyleSetSpec(stc.STC_STYLE_DEFAULT, p['default'])
378 self.StyleSetSpec(stc.STC_STYLE_DEFAULT, p['default'])
379 else:
379 else:
380 self.StyleSetSpec(stc.STC_STYLE_DEFAULT,
380 self.StyleSetSpec(stc.STC_STYLE_DEFAULT,
381 "fore:%s,back:%s,size:%d,face:%s"
381 "fore:%s,back:%s,size:%d,face:%s"
382 % (self.ANSI_STYLES['0;30'][1],
382 % (self.ANSI_STYLES['0;30'][1],
383 background_color,
383 background_color,
384 self.faces['size'], self.faces['mono']))
384 self.faces['size'], self.faces['mono']))
385
385
386 #all styles = default one
386 #all styles = default one
387 self.StyleClearAll()
387 self.StyleClearAll()
388
388
389 # XXX: two lines below are usefull if not using the lexer
389 # XXX: two lines below are usefull if not using the lexer
390 #for style in self.ANSI_STYLES.values():
390 #for style in self.ANSI_STYLES.values():
391 # self.StyleSetSpec(style[0], "bold,fore:%s" % style[1])
391 # self.StyleSetSpec(style[0], "bold,fore:%s" % style[1])
392
392
393 #prompt definition
393 #prompt definition
394 if 'prompt_in1' in p:
394 if 'prompt_in1' in p:
395 self.prompt_in1 = p['prompt_in1']
395 self.prompt_in1 = p['prompt_in1']
396 else:
396 else:
397 self.prompt_in1 = \
397 self.prompt_in1 = \
398 '\n\x01\x1b[0;34m\x02In [\x01\x1b[1;34m\x02$number\x01\x1b[0;34m\x02]: \x01\x1b[0m\x02'
398 '\n\x01\x1b[0;34m\x02In [\x01\x1b[1;34m\x02$number\x01\x1b[0;34m\x02]: \x01\x1b[0m\x02'
399
399
400 if 'prompt_out' in p:
400 if 'prompt_out' in p:
401 self.prompt_out = p['prompt_out']
401 self.prompt_out = p['prompt_out']
402 else:
402 else:
403 self.prompt_out = \
403 self.prompt_out = \
404 '\x01\x1b[0;31m\x02Out[\x01\x1b[1;31m\x02$number\x01\x1b[0;31m\x02]: \x01\x1b[0m\x02'
404 '\x01\x1b[0;31m\x02Out[\x01\x1b[1;31m\x02$number\x01\x1b[0;31m\x02]: \x01\x1b[0m\x02'
405
405
406 self.output_prompt_template = string.Template(self.prompt_out)
406 self.output_prompt_template = string.Template(self.prompt_out)
407 self.input_prompt_template = string.Template(self.prompt_in1)
407 self.input_prompt_template = string.Template(self.prompt_in1)
408
408
409 if 'stdout' in p:
409 if 'stdout' in p:
410 self.StyleSetSpec(_STDOUT_STYLE, p['stdout'])
410 self.StyleSetSpec(_STDOUT_STYLE, p['stdout'])
411 if 'stderr' in p:
411 if 'stderr' in p:
412 self.StyleSetSpec(_STDERR_STYLE, p['stderr'])
412 self.StyleSetSpec(_STDERR_STYLE, p['stderr'])
413 if 'trace' in p:
413 if 'trace' in p:
414 self.StyleSetSpec(_TRACE_STYLE, p['trace'])
414 self.StyleSetSpec(_TRACE_STYLE, p['trace'])
415 if 'bracegood' in p:
415 if 'bracegood' in p:
416 self.StyleSetSpec(stc.STC_STYLE_BRACELIGHT, p['bracegood'])
416 self.StyleSetSpec(stc.STC_STYLE_BRACELIGHT, p['bracegood'])
417 if 'bracebad' in p:
417 if 'bracebad' in p:
418 self.StyleSetSpec(stc.STC_STYLE_BRACEBAD, p['bracebad'])
418 self.StyleSetSpec(stc.STC_STYLE_BRACEBAD, p['bracebad'])
419 if 'comment' in p:
419 if 'comment' in p:
420 self.StyleSetSpec(stc.STC_P_COMMENTLINE, p['comment'])
420 self.StyleSetSpec(stc.STC_P_COMMENTLINE, p['comment'])
421 if 'number' in p:
421 if 'number' in p:
422 self.StyleSetSpec(stc.STC_P_NUMBER, p['number'])
422 self.StyleSetSpec(stc.STC_P_NUMBER, p['number'])
423 if 'string' in p:
423 if 'string' in p:
424 self.StyleSetSpec(stc.STC_P_STRING, p['string'])
424 self.StyleSetSpec(stc.STC_P_STRING, p['string'])
425 if 'char' in p:
425 if 'char' in p:
426 self.StyleSetSpec(stc.STC_P_CHARACTER, p['char'])
426 self.StyleSetSpec(stc.STC_P_CHARACTER, p['char'])
427 if 'keyword' in p:
427 if 'keyword' in p:
428 self.StyleSetSpec(stc.STC_P_WORD, p['keyword'])
428 self.StyleSetSpec(stc.STC_P_WORD, p['keyword'])
429 if 'keyword' in p:
429 if 'keyword' in p:
430 self.StyleSetSpec(stc.STC_P_WORD2, p['keyword'])
430 self.StyleSetSpec(stc.STC_P_WORD2, p['keyword'])
431 if 'triple' in p:
431 if 'triple' in p:
432 self.StyleSetSpec(stc.STC_P_TRIPLE, p['triple'])
432 self.StyleSetSpec(stc.STC_P_TRIPLE, p['triple'])
433 if 'tripledouble' in p:
433 if 'tripledouble' in p:
434 self.StyleSetSpec(stc.STC_P_TRIPLEDOUBLE, p['tripledouble'])
434 self.StyleSetSpec(stc.STC_P_TRIPLEDOUBLE, p['tripledouble'])
435 if 'class' in p:
435 if 'class' in p:
436 self.StyleSetSpec(stc.STC_P_CLASSNAME, p['class'])
436 self.StyleSetSpec(stc.STC_P_CLASSNAME, p['class'])
437 if 'def' in p:
437 if 'def' in p:
438 self.StyleSetSpec(stc.STC_P_DEFNAME, p['def'])
438 self.StyleSetSpec(stc.STC_P_DEFNAME, p['def'])
439 if 'operator' in p:
439 if 'operator' in p:
440 self.StyleSetSpec(stc.STC_P_OPERATOR, p['operator'])
440 self.StyleSetSpec(stc.STC_P_OPERATOR, p['operator'])
441 if 'comment' in p:
441 if 'comment' in p:
442 self.StyleSetSpec(stc.STC_P_COMMENTBLOCK, p['comment'])
442 self.StyleSetSpec(stc.STC_P_COMMENTBLOCK, p['comment'])
443
443
444 if 'edge_column' in p:
444 if 'edge_column' in p:
445 edge_column = p['edge_column']
445 edge_column = p['edge_column']
446 if edge_column is not None and edge_column > 0:
446 if edge_column is not None and edge_column > 0:
447 #we add a vertical line to console widget
447 #we add a vertical line to console widget
448 self.SetEdgeMode(stc.STC_EDGE_LINE)
448 self.SetEdgeMode(stc.STC_EDGE_LINE)
449 self.SetEdgeColumn(88)
449 self.SetEdgeColumn(88)
450
450
451
451
452 #--------------------------------------------------------------------------
452 #--------------------------------------------------------------------------
453 # EditWindow API
453 # EditWindow API
454 #--------------------------------------------------------------------------
454 #--------------------------------------------------------------------------
455
455
456 def OnUpdateUI(self, event):
456 def OnUpdateUI(self, event):
457 """ Override the OnUpdateUI of the EditWindow class, to prevent
457 """ Override the OnUpdateUI of the EditWindow class, to prevent
458 syntax highlighting both for faster redraw, and for more
458 syntax highlighting both for faster redraw, and for more
459 consistent look and feel.
459 consistent look and feel.
460 """
460 """
461
461
462
462
463 #--------------------------------------------------------------------------
463 #--------------------------------------------------------------------------
464 # Private API
464 # Private API
465 #--------------------------------------------------------------------------
465 #--------------------------------------------------------------------------
466
466
467 def _on_key_down(self, event, skip=True):
467 def _on_key_down(self, event, skip=True):
468 """ Key press callback used for correcting behavior for
468 """ Key press callback used for correcting behavior for
469 console-like interfaces: the cursor is constraint to be after
469 console-like interfaces: the cursor is constraint to be after
470 the last prompt.
470 the last prompt.
471
471
472 Return True if event as been catched.
472 Return True if event as been catched.
473 """
473 """
474 catched = True
474 catched = True
475 # XXX: Would the right way to do this be to have a
476 # dictionary at the instance level associating keys with
477 # callbacks? How would we deal with inheritance? And Do the
478 # different callbacks share local variables?
479
475 # Intercept some specific keys.
480 # Intercept some specific keys.
476 if event.KeyCode == ord('L') and event.ControlDown() :
481 if event.KeyCode == ord('L') and event.ControlDown() :
477 self.scroll_to_bottom()
482 self.scroll_to_bottom()
478 elif event.KeyCode == ord('K') and event.ControlDown() :
483 elif event.KeyCode == ord('K') and event.ControlDown() :
479 self.input_buffer = ''
484 self.input_buffer = ''
480 elif event.KeyCode == ord('A') and event.ControlDown() :
485 elif event.KeyCode == ord('A') and event.ControlDown() :
481 self.GotoPos(self.GetLength())
486 self.GotoPos(self.GetLength())
482 self.SetSelectionStart(self.current_prompt_pos)
487 self.SetSelectionStart(self.current_prompt_pos)
483 self.SetSelectionEnd(self.GetCurrentPos())
488 self.SetSelectionEnd(self.GetCurrentPos())
484 catched = True
489 catched = True
485 elif event.KeyCode == ord('E') and event.ControlDown() :
490 elif event.KeyCode == ord('E') and event.ControlDown() :
486 self.GotoPos(self.GetLength())
491 self.GotoPos(self.GetLength())
487 catched = True
492 catched = True
488 elif event.KeyCode == wx.WXK_PAGEUP:
493 elif event.KeyCode == wx.WXK_PAGEUP:
489 self.ScrollPages(-1)
494 self.ScrollPages(-1)
490 elif event.KeyCode == wx.WXK_PAGEDOWN:
495 elif event.KeyCode == wx.WXK_PAGEDOWN:
491 self.ScrollPages(1)
496 self.ScrollPages(1)
497 elif event.KeyCode == wx.WXK_HOME:
498 self.GotoPos(self.GetLength())
499 elif event.KeyCode == wx.WXK_END:
500 self.GotoPos(self.GetLength())
492 elif event.KeyCode == wx.WXK_UP and event.ShiftDown():
501 elif event.KeyCode == wx.WXK_UP and event.ShiftDown():
493 self.ScrollLines(-1)
502 self.ScrollLines(-1)
494 elif event.KeyCode == wx.WXK_DOWN and event.ShiftDown():
503 elif event.KeyCode == wx.WXK_DOWN and event.ShiftDown():
495 self.ScrollLines(1)
504 self.ScrollLines(1)
496 else:
505 else:
497 catched = False
506 catched = False
498
507
499 if self.AutoCompActive():
508 if self.AutoCompActive():
500 event.Skip()
509 event.Skip()
501 else:
510 else:
502 if event.KeyCode in (13, wx.WXK_NUMPAD_ENTER) and \
511 if event.KeyCode in (13, wx.WXK_NUMPAD_ENTER) and \
503 event.Modifiers in (wx.MOD_NONE, wx.MOD_WIN):
512 event.Modifiers in (wx.MOD_NONE, wx.MOD_WIN,
513 wx.MOD_SHIFT):
504 catched = True
514 catched = True
505 if not self.enter_catched:
515 if not self.enter_catched:
506 self.CallTipCancel()
516 self.CallTipCancel()
507 self.write('\n', refresh=False)
517 if event.Modifiers == wx.MOD_SHIFT:
508 # Under windows scintilla seems to be doing funny
518 # Try to force execution
509 # stuff to the line returns here, but the getter for
519 self.GotoPos(self.GetLength())
510 # input_buffer filters this out.
520 self.write('\n' + self.continuation_prompt(),
511 if sys.platform == 'win32':
521 refresh=False)
512 self.input_buffer = self.input_buffer
522 self._on_enter()
513 self._on_enter()
523 else:
524 self._on_enter()
514 self.enter_catched = True
525 self.enter_catched = True
515
526
516 elif event.KeyCode == wx.WXK_HOME:
527 elif event.KeyCode == wx.WXK_HOME:
517 if event.Modifiers in (wx.MOD_NONE, wx.MOD_WIN):
528 if event.Modifiers in (wx.MOD_NONE, wx.MOD_WIN):
518 self.GotoPos(self.current_prompt_pos)
529 self.GotoPos(self.current_prompt_pos)
519 catched = True
530 catched = True
520
531
521 elif event.Modifiers == wx.MOD_SHIFT:
532 elif event.Modifiers == wx.MOD_SHIFT:
522 # FIXME: This behavior is not ideal: if the selection
533 # FIXME: This behavior is not ideal: if the selection
523 # is already started, it will jump.
534 # is already started, it will jump.
524 self.SetSelectionStart(self.current_prompt_pos)
535 self.SetSelectionStart(self.current_prompt_pos)
525 self.SetSelectionEnd(self.GetCurrentPos())
536 self.SetSelectionEnd(self.GetCurrentPos())
526 catched = True
537 catched = True
527
538
528 elif event.KeyCode == wx.WXK_UP:
539 elif event.KeyCode == wx.WXK_UP:
529 if self.GetCurrentLine() > self.current_prompt_line:
540 if self.GetCurrentLine() > self.current_prompt_line:
530 if self.GetCurrentLine() == self.current_prompt_line + 1 \
541 if self.GetCurrentLine() == self.current_prompt_line + 1 \
531 and self.GetColumn(self.GetCurrentPos()) < \
542 and self.GetColumn(self.GetCurrentPos()) < \
532 self.GetColumn(self.current_prompt_pos):
543 self.GetColumn(self.current_prompt_pos):
533 self.GotoPos(self.current_prompt_pos)
544 self.GotoPos(self.current_prompt_pos)
534 else:
545 else:
535 event.Skip()
546 event.Skip()
536 catched = True
547 catched = True
537
548
538 elif event.KeyCode in (wx.WXK_LEFT, wx.WXK_BACK):
549 elif event.KeyCode in (wx.WXK_LEFT, wx.WXK_BACK):
539 if not self._keep_cursor_in_buffer(self.GetCurrentPos() - 1):
550 if not self._keep_cursor_in_buffer(self.GetCurrentPos() - 1):
540 event.Skip()
551 event.Skip()
541 catched = True
552 catched = True
542
553
543 elif event.KeyCode == wx.WXK_RIGHT:
554 elif event.KeyCode == wx.WXK_RIGHT:
544 if not self._keep_cursor_in_buffer(self.GetCurrentPos() + 1):
555 if not self._keep_cursor_in_buffer(self.GetCurrentPos() + 1):
545 event.Skip()
556 event.Skip()
546 catched = True
557 catched = True
547
558
548
559
549 elif event.KeyCode == wx.WXK_DELETE:
560 elif event.KeyCode == wx.WXK_DELETE:
550 if not self._keep_cursor_in_buffer(self.GetCurrentPos() - 1):
561 if not self._keep_cursor_in_buffer(self.GetCurrentPos() - 1):
551 event.Skip()
562 event.Skip()
552 catched = True
563 catched = True
553
564
554 if skip and not catched:
565 if skip and not catched:
555 # Put the cursor back in the edit region
566 # Put the cursor back in the edit region
556 if not self._keep_cursor_in_buffer():
567 if not self._keep_cursor_in_buffer():
557 if not (self.GetCurrentPos() == self.GetLength()
568 if not (self.GetCurrentPos() == self.GetLength()
558 and event.KeyCode == wx.WXK_DELETE):
569 and event.KeyCode == wx.WXK_DELETE):
559 event.Skip()
570 event.Skip()
560 catched = True
571 catched = True
561
572
562 return catched
573 return catched
563
574
564
575
565 def _on_key_up(self, event, skip=True):
576 def _on_key_up(self, event, skip=True):
566 """ If cursor is outside the editing region, put it back.
577 """ If cursor is outside the editing region, put it back.
567 """
578 """
568 if skip:
579 if skip:
569 event.Skip()
580 event.Skip()
570 self._keep_cursor_in_buffer()
581 self._keep_cursor_in_buffer()
571
582
572
583
573 # XXX: I need to avoid the problem of having an empty glass;
584 # XXX: I need to avoid the problem of having an empty glass;
574 def _keep_cursor_in_buffer(self, pos=None):
585 def _keep_cursor_in_buffer(self, pos=None):
575 """ Checks if the cursor is where it is allowed to be. If not,
586 """ Checks if the cursor is where it is allowed to be. If not,
576 put it back.
587 put it back.
577
588
578 Returns
589 Returns
579 -------
590 -------
580 cursor_moved: Boolean
591 cursor_moved: Boolean
581 whether or not the cursor was moved by this routine.
592 whether or not the cursor was moved by this routine.
582
593
583 Notes
594 Notes
584 ------
595 ------
585 WARNING: This does proper checks only for horizontal
596 WARNING: This does proper checks only for horizontal
586 movements.
597 movements.
587 """
598 """
588 if pos is None:
599 if pos is None:
589 current_pos = self.GetCurrentPos()
600 current_pos = self.GetCurrentPos()
590 else:
601 else:
591 current_pos = pos
602 current_pos = pos
592 if current_pos < self.current_prompt_pos:
603 if current_pos < self.current_prompt_pos:
593 self.GotoPos(self.current_prompt_pos)
604 self.GotoPos(self.current_prompt_pos)
594 return True
605 return True
595 line_num = self.LineFromPosition(current_pos)
606 line_num = self.LineFromPosition(current_pos)
596 if not current_pos > self.GetLength():
607 if not current_pos > self.GetLength():
597 line_pos = self.GetColumn(current_pos)
608 line_pos = self.GetColumn(current_pos)
598 else:
609 else:
599 line_pos = self.GetColumn(self.GetLength())
610 line_pos = self.GetColumn(self.GetLength())
600 line = self.GetLine(line_num)
611 line = self.GetLine(line_num)
601 # Jump the continuation prompt
612 # Jump the continuation prompt
602 continuation_prompt = self.continuation_prompt()
613 continuation_prompt = self.continuation_prompt()
603 if ( line.startswith(continuation_prompt)
614 if ( line.startswith(continuation_prompt)
604 and line_pos < len(continuation_prompt)):
615 and line_pos < len(continuation_prompt)):
605 if line_pos < 2:
616 if line_pos < 2:
606 # We are at the beginning of the line, trying to move
617 # We are at the beginning of the line, trying to move
607 # forward: jump forward.
618 # forward: jump forward.
608 self.GotoPos(current_pos + 1 +
619 self.GotoPos(current_pos + 1 +
609 len(continuation_prompt) - line_pos)
620 len(continuation_prompt) - line_pos)
610 else:
621 else:
611 # Jump back up
622 # Jump back up
612 self.GotoPos(self.GetLineEndPosition(line_num-1))
623 self.GotoPos(self.GetLineEndPosition(line_num-1))
613 return True
624 return True
614 elif ( current_pos > self.GetLineEndPosition(line_num)
625 elif ( current_pos > self.GetLineEndPosition(line_num)
615 and not current_pos == self.GetLength()):
626 and not current_pos == self.GetLength()):
616 # Jump to next line
627 # Jump to next line
617 self.GotoPos(current_pos + 1 +
628 self.GotoPos(current_pos + 1 +
618 len(continuation_prompt))
629 len(continuation_prompt))
619 return True
630 return True
620
631
621 self.enter_catched = False #we re-allow enter event processing
632 self.enter_catched = False #we re-allow enter event processing
622 return False
633 return False
623
634
624
635
625 if __name__ == '__main__':
636 if __name__ == '__main__':
626 # Some simple code to test the console widget.
637 # Some simple code to test the console widget.
627 class MainWindow(wx.Frame):
638 class MainWindow(wx.Frame):
628 def __init__(self, parent, id, title):
639 def __init__(self, parent, id, title):
629 wx.Frame.__init__(self, parent, id, title, size=(300, 250))
640 wx.Frame.__init__(self, parent, id, title, size=(300, 250))
630 self._sizer = wx.BoxSizer(wx.VERTICAL)
641 self._sizer = wx.BoxSizer(wx.VERTICAL)
631 self.console_widget = ConsoleWidget(self)
642 self.console_widget = ConsoleWidget(self)
632 self._sizer.Add(self.console_widget, 1, wx.EXPAND)
643 self._sizer.Add(self.console_widget, 1, wx.EXPAND)
633 self.SetSizer(self._sizer)
644 self.SetSizer(self._sizer)
634 self.SetAutoLayout(1)
645 self.SetAutoLayout(1)
635 self.Show(True)
646 self.Show(True)
636
647
637 app = wx.PySimpleApp()
648 app = wx.PySimpleApp()
638 w = MainWindow(None, wx.ID_ANY, 'ConsoleWidget')
649 w = MainWindow(None, wx.ID_ANY, 'ConsoleWidget')
639 w.SetSize((780, 460))
650 w.SetSize((780, 460))
640 w.Show()
651 w.Show()
641
652
642 app.MainLoop()
653 app.MainLoop()
643
654
644
655
@@ -1,583 +1,598 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
30
31 import wx
31 import wx
32 from wx import stc
32 from wx import stc
33
33
34 # Ipython-specific imports.
34 # Ipython-specific imports.
35 from IPython.frontend._process import PipedProcess
35 from IPython.frontend._process import PipedProcess
36 from console_widget import ConsoleWidget, _COMPLETE_BUFFER_MARKER, \
36 from console_widget import ConsoleWidget, _COMPLETE_BUFFER_MARKER, \
37 _ERROR_MARKER, _INPUT_MARKER
37 _ERROR_MARKER, _INPUT_MARKER
38 from IPython.frontend.prefilterfrontend import PrefilterFrontEnd
38 from IPython.frontend.prefilterfrontend import PrefilterFrontEnd
39
39
40 #-------------------------------------------------------------------------------
40 #-------------------------------------------------------------------------------
41 # Classes to implement the Wx frontend
41 # Classes to implement the Wx frontend
42 #-------------------------------------------------------------------------------
42 #-------------------------------------------------------------------------------
43 class WxController(ConsoleWidget, PrefilterFrontEnd):
43 class WxController(ConsoleWidget, PrefilterFrontEnd):
44 """Classes to provide a Wx frontend to the
44 """Classes to provide a Wx frontend to the
45 IPython.kernel.core.interpreter.
45 IPython.kernel.core.interpreter.
46
46
47 This class inherits from ConsoleWidget, that provides a console-like
47 This class inherits from ConsoleWidget, that provides a console-like
48 widget to provide a text-rendering widget suitable for a terminal.
48 widget to provide a text-rendering widget suitable for a terminal.
49 """
49 """
50
50
51 # Print debug info on what is happening to the console.
51 # Print debug info on what is happening to the console.
52 debug = False
52 debug = False
53
53
54 # The title of the terminal, as captured through the ANSI escape
54 # The title of the terminal, as captured through the ANSI escape
55 # sequences.
55 # sequences.
56 def _set_title(self, title):
56 def _set_title(self, title):
57 return self.Parent.SetTitle(title)
57 return self.Parent.SetTitle(title)
58
58
59 def _get_title(self):
59 def _get_title(self):
60 return self.Parent.GetTitle()
60 return self.Parent.GetTitle()
61
61
62 title = property(_get_title, _set_title)
62 title = property(_get_title, _set_title)
63
63
64
64
65 # The buffer being edited.
65 # The buffer being edited.
66 # We are duplicating the definition here because of multiple
66 # We are duplicating the definition here because of multiple
67 # inheritence
67 # inheritence
68 def _set_input_buffer(self, string):
68 def _set_input_buffer(self, string):
69 ConsoleWidget._set_input_buffer(self, string)
69 ConsoleWidget._set_input_buffer(self, string)
70 self._colorize_input_buffer()
70 self._colorize_input_buffer()
71
71
72 def _get_input_buffer(self):
72 def _get_input_buffer(self):
73 """ Returns the text in current edit buffer.
73 """ Returns the text in current edit buffer.
74 """
74 """
75 return ConsoleWidget._get_input_buffer(self)
75 return ConsoleWidget._get_input_buffer(self)
76
76
77 input_buffer = property(_get_input_buffer, _set_input_buffer)
77 input_buffer = property(_get_input_buffer, _set_input_buffer)
78
78
79
79
80 #--------------------------------------------------------------------------
80 #--------------------------------------------------------------------------
81 # Private Attributes
81 # Private Attributes
82 #--------------------------------------------------------------------------
82 #--------------------------------------------------------------------------
83
83
84 # A flag governing the behavior of the input. Can be:
84 # A flag governing the behavior of the input. Can be:
85 #
85 #
86 # 'readline' for readline-like behavior with a prompt
86 # 'readline' for readline-like behavior with a prompt
87 # and an edit buffer.
87 # and an edit buffer.
88 # 'raw_input' similar to readline, but triggered by a raw-input
88 # 'raw_input' similar to readline, but triggered by a raw-input
89 # call. Can be used by subclasses to act differently.
89 # call. Can be used by subclasses to act differently.
90 # 'subprocess' for sending the raw input directly to a
90 # 'subprocess' for sending the raw input directly to a
91 # subprocess.
91 # subprocess.
92 # 'buffering' for buffering of the input, that will be used
92 # 'buffering' for buffering of the input, that will be used
93 # when the input state switches back to another state.
93 # when the input state switches back to another state.
94 _input_state = 'readline'
94 _input_state = 'readline'
95
95
96 # Attribute to store reference to the pipes of a subprocess, if we
96 # Attribute to store reference to the pipes of a subprocess, if we
97 # are running any.
97 # are running any.
98 _running_process = False
98 _running_process = False
99
99
100 # A queue for writing fast streams to the screen without flooding the
100 # A queue for writing fast streams to the screen without flooding the
101 # event loop
101 # event loop
102 _out_buffer = []
102 _out_buffer = []
103
103
104 # A lock to lock the _out_buffer to make sure we don't empty it
104 # A lock to lock the _out_buffer to make sure we don't empty it
105 # while it is being swapped
105 # while it is being swapped
106 _out_buffer_lock = Lock()
106 _out_buffer_lock = Lock()
107
107
108 # The different line markers used to higlight the prompts.
108 # The different line markers used to higlight the prompts.
109 _markers = dict()
109 _markers = dict()
110
110
111 #--------------------------------------------------------------------------
111 #--------------------------------------------------------------------------
112 # Public API
112 # Public API
113 #--------------------------------------------------------------------------
113 #--------------------------------------------------------------------------
114
114
115 def __init__(self, parent, id=wx.ID_ANY, pos=wx.DefaultPosition,
115 def __init__(self, parent, id=wx.ID_ANY, pos=wx.DefaultPosition,
116 size=wx.DefaultSize,
116 size=wx.DefaultSize,
117 style=wx.CLIP_CHILDREN|wx.WANTS_CHARS,
117 style=wx.CLIP_CHILDREN|wx.WANTS_CHARS,
118 styledef=None,
118 styledef=None,
119 *args, **kwds):
119 *args, **kwds):
120 """ Create Shell instance.
120 """ Create Shell instance.
121
121
122 Parameters
122 Parameters
123 -----------
123 -----------
124 styledef : dict, optional
124 styledef : dict, optional
125 styledef is the dictionary of options used to define the
125 styledef is the dictionary of options used to define the
126 style.
126 style.
127 """
127 """
128 if styledef is not None:
128 if styledef is not None:
129 self.style = styledef
129 self.style = styledef
130 ConsoleWidget.__init__(self, parent, id, pos, size, style)
130 ConsoleWidget.__init__(self, parent, id, pos, size, style)
131 PrefilterFrontEnd.__init__(self, **kwds)
131 PrefilterFrontEnd.__init__(self, **kwds)
132
132
133 # Stick in our own raw_input:
133 # Stick in our own raw_input:
134 self.ipython0.raw_input = self.raw_input
134 self.ipython0.raw_input = self.raw_input
135
135
136 # A time for flushing the write buffer
136 # A time for flushing the write buffer
137 BUFFER_FLUSH_TIMER_ID = 100
137 BUFFER_FLUSH_TIMER_ID = 100
138 self._buffer_flush_timer = wx.Timer(self, BUFFER_FLUSH_TIMER_ID)
138 self._buffer_flush_timer = wx.Timer(self, BUFFER_FLUSH_TIMER_ID)
139 wx.EVT_TIMER(self, BUFFER_FLUSH_TIMER_ID, self._buffer_flush)
139 wx.EVT_TIMER(self, BUFFER_FLUSH_TIMER_ID, self._buffer_flush)
140
140
141 if 'debug' in kwds:
141 if 'debug' in kwds:
142 self.debug = kwds['debug']
142 self.debug = kwds['debug']
143 kwds.pop('debug')
143 kwds.pop('debug')
144
144
145 # Inject self in namespace, for debug
145 # Inject self in namespace, for debug
146 if self.debug:
146 if self.debug:
147 self.shell.user_ns['self'] = self
147 self.shell.user_ns['self'] = self
148 # Inject our own raw_input in namespace
148 # Inject our own raw_input in namespace
149 self.shell.user_ns['raw_input'] = self.raw_input
149 self.shell.user_ns['raw_input'] = self.raw_input
150
150
151 def raw_input(self, prompt=''):
151 def raw_input(self, prompt=''):
152 """ A replacement from python's raw_input.
152 """ A replacement from python's raw_input.
153 """
153 """
154 self.new_prompt(prompt)
154 self.new_prompt(prompt)
155 self._input_state = 'raw_input'
155 self._input_state = 'raw_input'
156 if hasattr(self, '_cursor'):
156 if hasattr(self, '_cursor'):
157 del self._cursor
157 del self._cursor
158 self.SetCursor(wx.StockCursor(wx.CURSOR_CROSS))
158 self.SetCursor(wx.StockCursor(wx.CURSOR_CROSS))
159 self.__old_on_enter = self._on_enter
159 self.__old_on_enter = self._on_enter
160 event_loop = wx.EventLoop()
160 event_loop = wx.EventLoop()
161 def my_on_enter():
161 def my_on_enter():
162 event_loop.Exit()
162 event_loop.Exit()
163 self._on_enter = my_on_enter
163 self._on_enter = my_on_enter
164 # XXX: Running a separate event_loop. Ugly.
164 # XXX: Running a separate event_loop. Ugly.
165 event_loop.Run()
165 event_loop.Run()
166 self._on_enter = self.__old_on_enter
166 self._on_enter = self.__old_on_enter
167 self._input_state = 'buffering'
167 self._input_state = 'buffering'
168 self._cursor = wx.BusyCursor()
168 self._cursor = wx.BusyCursor()
169 return self.input_buffer.rstrip('\n')
169 return self.input_buffer.rstrip('\n')
170
170
171
171
172 def system_call(self, command_string):
172 def system_call(self, command_string):
173 self._input_state = 'subprocess'
173 self._input_state = 'subprocess'
174 event_loop = wx.EventLoop()
174 event_loop = wx.EventLoop()
175 def _end_system_call():
175 def _end_system_call():
176 self._input_state = 'buffering'
176 self._input_state = 'buffering'
177 self._running_process = False
177 self._running_process = False
178 event_loop.Exit()
178 event_loop.Exit()
179
179
180 self._running_process = PipedProcess(command_string,
180 self._running_process = PipedProcess(command_string,
181 out_callback=self.buffered_write,
181 out_callback=self.buffered_write,
182 end_callback = _end_system_call)
182 end_callback = _end_system_call)
183 self._running_process.start()
183 self._running_process.start()
184 # XXX: Running a separate event_loop. Ugly.
184 # XXX: Running a separate event_loop. Ugly.
185 event_loop.Run()
185 event_loop.Run()
186 # Be sure to flush the buffer.
186 # Be sure to flush the buffer.
187 self._buffer_flush(event=None)
187 self._buffer_flush(event=None)
188
188
189
189
190 def do_calltip(self):
190 def do_calltip(self):
191 """ Analyse current and displays useful calltip for it.
191 """ Analyse current and displays useful calltip for it.
192 """
192 """
193 if self.debug:
193 if self.debug:
194 print >>sys.__stdout__, "do_calltip"
194 print >>sys.__stdout__, "do_calltip"
195 separators = re.compile('[\s\{\}\[\]\(\)\= ,:]')
195 separators = re.compile('[\s\{\}\[\]\(\)\= ,:]')
196 symbol = self.input_buffer
196 symbol = self.input_buffer
197 symbol_string = separators.split(symbol)[-1]
197 symbol_string = separators.split(symbol)[-1]
198 base_symbol_string = symbol_string.split('.')[0]
198 base_symbol_string = symbol_string.split('.')[0]
199 if base_symbol_string in self.shell.user_ns:
199 if base_symbol_string in self.shell.user_ns:
200 symbol = self.shell.user_ns[base_symbol_string]
200 symbol = self.shell.user_ns[base_symbol_string]
201 elif base_symbol_string in self.shell.user_global_ns:
201 elif base_symbol_string in self.shell.user_global_ns:
202 symbol = self.shell.user_global_ns[base_symbol_string]
202 symbol = self.shell.user_global_ns[base_symbol_string]
203 elif base_symbol_string in __builtin__.__dict__:
203 elif base_symbol_string in __builtin__.__dict__:
204 symbol = __builtin__.__dict__[base_symbol_string]
204 symbol = __builtin__.__dict__[base_symbol_string]
205 else:
205 else:
206 return False
206 return False
207 try:
207 try:
208 for name in symbol_string.split('.')[1:] + ['__doc__']:
208 for name in symbol_string.split('.')[1:] + ['__doc__']:
209 symbol = getattr(symbol, name)
209 symbol = getattr(symbol, name)
210 self.AutoCompCancel()
210 self.AutoCompCancel()
211 # Check that the symbol can indeed be converted to a string:
211 # Check that the symbol can indeed be converted to a string:
212 symbol += ''
212 symbol += ''
213 wx.CallAfter(self.CallTipShow, self.GetCurrentPos(), symbol)
213 wx.CallAfter(self.CallTipShow, self.GetCurrentPos(), symbol)
214 except:
214 except:
215 # The retrieve symbol couldn't be converted to a string
215 # The retrieve symbol couldn't be converted to a string
216 pass
216 pass
217
217
218
218
219 def _popup_completion(self, create=False):
219 def _popup_completion(self, create=False):
220 """ Updates the popup completion menu if it exists. If create is
220 """ Updates the popup completion menu if it exists. If create is
221 true, open the menu.
221 true, open the menu.
222 """
222 """
223 if self.debug:
223 if self.debug:
224 print >>sys.__stdout__, "_popup_completion"
224 print >>sys.__stdout__, "_popup_completion"
225 line = self.input_buffer
225 line = self.input_buffer
226 if (self.AutoCompActive() and line and not line[-1] == '.') \
226 if (self.AutoCompActive() and line and not line[-1] == '.') \
227 or create==True:
227 or create==True:
228 suggestion, completions = self.complete(line)
228 suggestion, completions = self.complete(line)
229 if completions:
229 if completions:
230 offset = len(self._get_completion_text(line))
230 offset = len(self._get_completion_text(line))
231 self.pop_completion(completions, offset=offset)
231 self.pop_completion(completions, offset=offset)
232 if self.debug:
232 if self.debug:
233 print >>sys.__stdout__, completions
233 print >>sys.__stdout__, completions
234
234
235
235
236 def buffered_write(self, text):
236 def buffered_write(self, text):
237 """ A write method for streams, that caches the stream in order
237 """ A write method for streams, that caches the stream in order
238 to avoid flooding the event loop.
238 to avoid flooding the event loop.
239
239
240 This can be called outside of the main loop, in separate
240 This can be called outside of the main loop, in separate
241 threads.
241 threads.
242 """
242 """
243 self._out_buffer_lock.acquire()
243 self._out_buffer_lock.acquire()
244 self._out_buffer.append(text)
244 self._out_buffer.append(text)
245 self._out_buffer_lock.release()
245 self._out_buffer_lock.release()
246 if not self._buffer_flush_timer.IsRunning():
246 if not self._buffer_flush_timer.IsRunning():
247 wx.CallAfter(self._buffer_flush_timer.Start,
247 wx.CallAfter(self._buffer_flush_timer.Start,
248 milliseconds=100, oneShot=True)
248 milliseconds=100, oneShot=True)
249
249
250
250
251 def clear_screen(self):
251 def clear_screen(self):
252 """ Empty completely the widget.
252 """ Empty completely the widget.
253 """
253 """
254 self.ClearAll()
254 self.ClearAll()
255 self.new_prompt(self.input_prompt_template.substitute(
255 self.new_prompt(self.input_prompt_template.substitute(
256 number=(self.last_result['number'] + 1)))
256 number=(self.last_result['number'] + 1)))
257
257
258
258
259 #--------------------------------------------------------------------------
259 #--------------------------------------------------------------------------
260 # LineFrontEnd interface
260 # LineFrontEnd interface
261 #--------------------------------------------------------------------------
261 #--------------------------------------------------------------------------
262
262
263 def execute(self, python_string, raw_string=None):
263 def execute(self, python_string, raw_string=None):
264 self._input_state = 'buffering'
264 self._input_state = 'buffering'
265 self.CallTipCancel()
265 self.CallTipCancel()
266 self._cursor = wx.BusyCursor()
266 self._cursor = wx.BusyCursor()
267 if raw_string is None:
267 if raw_string is None:
268 raw_string = python_string
268 raw_string = python_string
269 end_line = self.current_prompt_line \
269 end_line = self.current_prompt_line \
270 + max(1, len(raw_string.split('\n'))-1)
270 + max(1, len(raw_string.split('\n'))-1)
271 for i in range(self.current_prompt_line, end_line):
271 for i in range(self.current_prompt_line, end_line):
272 if i in self._markers:
272 if i in self._markers:
273 self.MarkerDeleteHandle(self._markers[i])
273 self.MarkerDeleteHandle(self._markers[i])
274 self._markers[i] = self.MarkerAdd(i, _COMPLETE_BUFFER_MARKER)
274 self._markers[i] = self.MarkerAdd(i, _COMPLETE_BUFFER_MARKER)
275 # Use a callafter to update the display robustly under windows
275 # Use a callafter to update the display robustly under windows
276 def callback():
276 def callback():
277 self.GotoPos(self.GetLength())
277 self.GotoPos(self.GetLength())
278 PrefilterFrontEnd.execute(self, python_string,
278 PrefilterFrontEnd.execute(self, python_string,
279 raw_string=raw_string)
279 raw_string=raw_string)
280 wx.CallAfter(callback)
280 wx.CallAfter(callback)
281
281
282
282
283 def execute_command(self, command, hidden=False):
283 def execute_command(self, command, hidden=False):
284 """ Execute a command, not only in the model, but also in the
284 """ Execute a command, not only in the model, but also in the
285 view.
285 view.
286 """
286 """
287 # XXX: This method needs to be integrated in the base fronted
288 # interface
287 if hidden:
289 if hidden:
288 return self.shell.execute(command)
290 return self.shell.execute(command)
289 else:
291 else:
290 # XXX: we are not storing the input buffer previous to the
292 # XXX: we are not storing the input buffer previous to the
291 # execution, as this forces us to run the execution
293 # execution, as this forces us to run the execution
292 # input_buffer a yield, which is not good.
294 # input_buffer a yield, which is not good.
293 ##current_buffer = self.shell.control.input_buffer
295 ##current_buffer = self.shell.control.input_buffer
294 command = command.rstrip()
296 command = command.rstrip()
295 if len(command.split('\n')) > 1:
297 if len(command.split('\n')) > 1:
296 # The input command is several lines long, we need to
298 # The input command is several lines long, we need to
297 # force the execution to happen
299 # force the execution to happen
298 command += '\n'
300 command += '\n'
299 cleaned_command = self.prefilter_input(command)
301 cleaned_command = self.prefilter_input(command)
300 self.input_buffer = command
302 self.input_buffer = command
301 # Do not use wx.Yield() (aka GUI.process_events()) to avoid
303 # Do not use wx.Yield() (aka GUI.process_events()) to avoid
302 # recursive yields.
304 # recursive yields.
303 self.ProcessEvent(wx.PaintEvent())
305 self.ProcessEvent(wx.PaintEvent())
304 self.write('\n')
306 self.write('\n')
305 if not self.is_complete(cleaned_command + '\n'):
307 if not self.is_complete(cleaned_command + '\n'):
306 self._colorize_input_buffer()
308 self._colorize_input_buffer()
307 self.render_error('Incomplete or invalid input')
309 self.render_error('Incomplete or invalid input')
308 self.new_prompt(self.input_prompt_template.substitute(
310 self.new_prompt(self.input_prompt_template.substitute(
309 number=(self.last_result['number'] + 1)))
311 number=(self.last_result['number'] + 1)))
310 return False
312 return False
311 self._on_enter()
313 self._on_enter()
312 return True
314 return True
313
315
314
316
315 def save_output_hooks(self):
317 def save_output_hooks(self):
316 self.__old_raw_input = __builtin__.raw_input
318 self.__old_raw_input = __builtin__.raw_input
317 PrefilterFrontEnd.save_output_hooks(self)
319 PrefilterFrontEnd.save_output_hooks(self)
318
320
319 def capture_output(self):
321 def capture_output(self):
320 self.SetLexer(stc.STC_LEX_NULL)
322 self.SetLexer(stc.STC_LEX_NULL)
321 PrefilterFrontEnd.capture_output(self)
323 PrefilterFrontEnd.capture_output(self)
322 __builtin__.raw_input = self.raw_input
324 __builtin__.raw_input = self.raw_input
323
325
324
326
325 def release_output(self):
327 def release_output(self):
326 __builtin__.raw_input = self.__old_raw_input
328 __builtin__.raw_input = self.__old_raw_input
327 PrefilterFrontEnd.release_output(self)
329 PrefilterFrontEnd.release_output(self)
328 self.SetLexer(stc.STC_LEX_PYTHON)
330 self.SetLexer(stc.STC_LEX_PYTHON)
329
331
330
332
331 def after_execute(self):
333 def after_execute(self):
332 PrefilterFrontEnd.after_execute(self)
334 PrefilterFrontEnd.after_execute(self)
333 # Clear the wait cursor
335 # Clear the wait cursor
334 if hasattr(self, '_cursor'):
336 if hasattr(self, '_cursor'):
335 del self._cursor
337 del self._cursor
336 self.SetCursor(wx.StockCursor(wx.CURSOR_CHAR))
338 self.SetCursor(wx.StockCursor(wx.CURSOR_CHAR))
337
339
338
340
339 def show_traceback(self):
341 def show_traceback(self):
340 start_line = self.GetCurrentLine()
342 start_line = self.GetCurrentLine()
341 PrefilterFrontEnd.show_traceback(self)
343 PrefilterFrontEnd.show_traceback(self)
342 self.ProcessEvent(wx.PaintEvent())
344 self.ProcessEvent(wx.PaintEvent())
343 #wx.Yield()
345 #wx.Yield()
344 for i in range(start_line, self.GetCurrentLine()):
346 for i in range(start_line, self.GetCurrentLine()):
345 self._markers[i] = self.MarkerAdd(i, _ERROR_MARKER)
347 self._markers[i] = self.MarkerAdd(i, _ERROR_MARKER)
346
348
347
349
348 #--------------------------------------------------------------------------
350 #--------------------------------------------------------------------------
349 # FrontEndBase interface
351 # FrontEndBase interface
350 #--------------------------------------------------------------------------
352 #--------------------------------------------------------------------------
351
353
352 def render_error(self, e):
354 def render_error(self, e):
353 start_line = self.GetCurrentLine()
355 start_line = self.GetCurrentLine()
354 self.write('\n' + e + '\n')
356 self.write('\n' + e + '\n')
355 for i in range(start_line, self.GetCurrentLine()):
357 for i in range(start_line, self.GetCurrentLine()):
356 self._markers[i] = self.MarkerAdd(i, _ERROR_MARKER)
358 self._markers[i] = self.MarkerAdd(i, _ERROR_MARKER)
357
359
358
360
359 #--------------------------------------------------------------------------
361 #--------------------------------------------------------------------------
360 # ConsoleWidget interface
362 # ConsoleWidget interface
361 #--------------------------------------------------------------------------
363 #--------------------------------------------------------------------------
362
364
363 def new_prompt(self, prompt):
365 def new_prompt(self, prompt):
364 """ Display a new prompt, and start a new input buffer.
366 """ Display a new prompt, and start a new input buffer.
365 """
367 """
366 self._input_state = 'readline'
368 self._input_state = 'readline'
367 ConsoleWidget.new_prompt(self, prompt)
369 ConsoleWidget.new_prompt(self, prompt)
368 i = self.current_prompt_line
370 i = self.current_prompt_line
369 self._markers[i] = self.MarkerAdd(i, _INPUT_MARKER)
371 self._markers[i] = self.MarkerAdd(i, _INPUT_MARKER)
370
372
371
373
372 def continuation_prompt(self, *args, **kwargs):
374 def continuation_prompt(self, *args, **kwargs):
373 # Avoid multiple inheritence, be explicit about which
375 # Avoid multiple inheritence, be explicit about which
374 # parent method class gets called
376 # parent method class gets called
375 return ConsoleWidget.continuation_prompt(self, *args, **kwargs)
377 return ConsoleWidget.continuation_prompt(self, *args, **kwargs)
376
378
377
379
378 def write(self, *args, **kwargs):
380 def write(self, *args, **kwargs):
379 # Avoid multiple inheritence, be explicit about which
381 # Avoid multiple inheritence, be explicit about which
380 # parent method class gets called
382 # parent method class gets called
381 return ConsoleWidget.write(self, *args, **kwargs)
383 return ConsoleWidget.write(self, *args, **kwargs)
382
384
383
385
384 def _on_key_down(self, event, skip=True):
386 def _on_key_down(self, event, skip=True):
385 """ Capture the character events, let the parent
387 """ Capture the character events, let the parent
386 widget handle them, and put our logic afterward.
388 widget handle them, and put our logic afterward.
387 """
389 """
388 # FIXME: This method needs to be broken down in smaller ones.
390 # FIXME: This method needs to be broken down in smaller ones.
389 current_line_number = self.GetCurrentLine()
391 current_line_num = self.GetCurrentLine()
390 if event.KeyCode in (ord('c'), ord('C')) and event.ControlDown():
392 if event.KeyCode in (ord('c'), ord('C')) and event.ControlDown():
391 # Capture Control-C
393 # Capture Control-C
392 if self._input_state == 'subprocess':
394 if self._input_state == 'subprocess':
393 if self.debug:
395 if self.debug:
394 print >>sys.__stderr__, 'Killing running process'
396 print >>sys.__stderr__, 'Killing running process'
395 if hasattr(self._running_process, 'process'):
397 if hasattr(self._running_process, 'process'):
396 self._running_process.process.kill()
398 self._running_process.process.kill()
397 elif self._input_state == 'buffering':
399 elif self._input_state == 'buffering':
398 if self.debug:
400 if self.debug:
399 print >>sys.__stderr__, 'Raising KeyboardInterrupt'
401 print >>sys.__stderr__, 'Raising KeyboardInterrupt'
400 raise KeyboardInterrupt
402 raise KeyboardInterrupt
401 # XXX: We need to make really sure we
403 # XXX: We need to make really sure we
402 # get back to a prompt.
404 # get back to a prompt.
403 elif self._input_state == 'subprocess' and (
405 elif self._input_state == 'subprocess' and (
404 ( event.KeyCode<256 and
406 ( event.KeyCode<256 and
405 not event.ControlDown() )
407 not event.ControlDown() )
406 or
408 or
407 ( event.KeyCode in (ord('d'), ord('D')) and
409 ( event.KeyCode in (ord('d'), ord('D')) and
408 event.ControlDown())):
410 event.ControlDown())):
409 # We are running a process, we redirect keys.
411 # We are running a process, we redirect keys.
410 ConsoleWidget._on_key_down(self, event, skip=skip)
412 ConsoleWidget._on_key_down(self, event, skip=skip)
411 char = chr(event.KeyCode)
413 char = chr(event.KeyCode)
412 # Deal with some inconsistency in wx keycodes:
414 # Deal with some inconsistency in wx keycodes:
413 if char == '\r':
415 if char == '\r':
414 char = '\n'
416 char = '\n'
415 elif not event.ShiftDown():
417 elif not event.ShiftDown():
416 char = char.lower()
418 char = char.lower()
417 if event.ControlDown() and event.KeyCode in (ord('d'), ord('D')):
419 if event.ControlDown() and event.KeyCode in (ord('d'), ord('D')):
418 char = '\04'
420 char = '\04'
419 self._running_process.process.stdin.write(char)
421 self._running_process.process.stdin.write(char)
420 self._running_process.process.stdin.flush()
422 self._running_process.process.stdin.flush()
421 elif event.KeyCode in (ord('('), 57, 53):
423 elif event.KeyCode in (ord('('), 57, 53):
422 # Calltips
424 # Calltips
423 event.Skip()
425 event.Skip()
424 self.do_calltip()
426 self.do_calltip()
425 elif self.AutoCompActive() and not event.KeyCode == ord('\t'):
427 elif self.AutoCompActive() and not event.KeyCode == ord('\t'):
426 event.Skip()
428 event.Skip()
427 if event.KeyCode in (wx.WXK_BACK, wx.WXK_DELETE):
429 if event.KeyCode in (wx.WXK_BACK, wx.WXK_DELETE):
428 wx.CallAfter(self._popup_completion, create=True)
430 wx.CallAfter(self._popup_completion, create=True)
429 elif not event.KeyCode in (wx.WXK_UP, wx.WXK_DOWN, wx.WXK_LEFT,
431 elif not event.KeyCode in (wx.WXK_UP, wx.WXK_DOWN, wx.WXK_LEFT,
430 wx.WXK_RIGHT, wx.WXK_ESCAPE):
432 wx.WXK_RIGHT, wx.WXK_ESCAPE):
431 wx.CallAfter(self._popup_completion)
433 wx.CallAfter(self._popup_completion)
432 else:
434 else:
433 # Up history
435 # Up history
434 if event.KeyCode == wx.WXK_UP and (
436 if event.KeyCode == wx.WXK_UP and (
435 ( current_line_number == self.current_prompt_line and
437 ( current_line_num == self.current_prompt_line and
436 event.Modifiers in (wx.MOD_NONE, wx.MOD_WIN) )
438 event.Modifiers in (wx.MOD_NONE, wx.MOD_WIN) )
437 or event.ControlDown() ):
439 or event.ControlDown() ):
438 new_buffer = self.get_history_previous(
440 new_buffer = self.get_history_previous(
439 self.input_buffer)
441 self.input_buffer)
440 if new_buffer is not None:
442 if new_buffer is not None:
441 self.input_buffer = new_buffer
443 self.input_buffer = new_buffer
442 if self.GetCurrentLine() > self.current_prompt_line:
444 if self.GetCurrentLine() > self.current_prompt_line:
443 # Go to first line, for seemless history up.
445 # Go to first line, for seemless history up.
444 self.GotoPos(self.current_prompt_pos)
446 self.GotoPos(self.current_prompt_pos)
445 # Down history
447 # Down history
446 elif event.KeyCode == wx.WXK_DOWN and (
448 elif event.KeyCode == wx.WXK_DOWN and (
447 ( current_line_number == self.LineCount -1 and
449 ( current_line_num == self.LineCount -1 and
448 event.Modifiers in (wx.MOD_NONE, wx.MOD_WIN) )
450 event.Modifiers in (wx.MOD_NONE, wx.MOD_WIN) )
449 or event.ControlDown() ):
451 or event.ControlDown() ):
450 new_buffer = self.get_history_next()
452 new_buffer = self.get_history_next()
451 if new_buffer is not None:
453 if new_buffer is not None:
452 self.input_buffer = new_buffer
454 self.input_buffer = new_buffer
453 # Tab-completion
455 # Tab-completion
454 elif event.KeyCode == ord('\t'):
456 elif event.KeyCode == ord('\t'):
455 current_line, current_line_number = self.CurLine
457 current_line, current_line_num = self.CurLine
456 if not re.match(r'^\s*$', current_line):
458 if not re.match(r'^\s*$', current_line):
457 self.complete_current_input()
459 self.complete_current_input()
458 if self.AutoCompActive():
460 if self.AutoCompActive():
459 wx.CallAfter(self._popup_completion, create=True)
461 wx.CallAfter(self._popup_completion, create=True)
460 else:
462 else:
461 event.Skip()
463 event.Skip()
462 elif event.KeyCode == wx.WXK_BACK:
464 elif event.KeyCode == wx.WXK_BACK:
463 # If characters where erased, check if we have to
465 # If characters where erased, check if we have to
464 # remove a line.
466 # remove a line.
465 # XXX: What about DEL?
467 # XXX: What about DEL?
466 # FIXME: This logics should be in ConsoleWidget, as it is
468 # FIXME: This logics should be in ConsoleWidget, as it is
467 # independant of IPython
469 # independant of IPython
468 current_line, _ = self.CurLine
470 current_line, _ = self.CurLine
469 current_pos = self.GetCurrentPos()
471 current_pos = self.GetCurrentPos()
470 current_line_number = self.LineFromPosition(current_pos)
472 current_line_num = self.LineFromPosition(current_pos)
471 current_col = self.GetColumn(current_pos)
473 current_col = self.GetColumn(current_pos)
472 len_prompt = len(self.continuation_prompt())
474 len_prompt = len(self.continuation_prompt())
473 if ( current_line.startswith(self.continuation_prompt())
475 if ( current_line.startswith(self.continuation_prompt())
474 and current_col == len_prompt):
476 and current_col == len_prompt):
475 new_lines = []
477 new_lines = []
476 for line_num, line in enumerate(
478 for line_num, line in enumerate(
477 self.input_buffer.split('\n')):
479 self.input_buffer.split('\n')):
478 if (line_num + self.current_prompt_line ==
480 if (line_num + self.current_prompt_line ==
479 current_line_number):
481 current_line_num):
480 new_lines.append(line[len_prompt:])
482 new_lines.append(line[len_prompt:])
481 else:
483 else:
482 new_lines.append('\n'+line)
484 new_lines.append('\n'+line)
483 # The first character is '\n', due to the above
485 # The first character is '\n', due to the above
484 # code:
486 # code:
485 self.input_buffer = ''.join(new_lines)[1:]
487 self.input_buffer = ''.join(new_lines)[1:]
486 self.GotoPos(current_pos - 1 - len_prompt)
488 self.GotoPos(current_pos - 1 - len_prompt)
487 else:
489 else:
488 ConsoleWidget._on_key_down(self, event, skip=skip)
490 ConsoleWidget._on_key_down(self, event, skip=skip)
489 else:
491 else:
490 ConsoleWidget._on_key_down(self, event, skip=skip)
492 ConsoleWidget._on_key_down(self, event, skip=skip)
491
493
492
494
493
495
494 def _on_key_up(self, event, skip=True):
496 def _on_key_up(self, event, skip=True):
495 """ Called when any key is released.
497 """ Called when any key is released.
496 """
498 """
497 if event.KeyCode in (59, ord('.')):
499 if event.KeyCode in (59, ord('.')):
498 # Intercepting '.'
500 # Intercepting '.'
499 event.Skip()
501 event.Skip()
500 wx.CallAfter(self._popup_completion, create=True)
502 wx.CallAfter(self._popup_completion, create=True)
501 else:
503 else:
502 ConsoleWidget._on_key_up(self, event, skip=skip)
504 ConsoleWidget._on_key_up(self, event, skip=skip)
503 # Make sure the continuation_prompts are always followed by a
505 # Make sure the continuation_prompts are always followed by a
504 # whitespace
506 # whitespace
505 new_lines = []
507 new_lines = []
506 if self._input_state == 'readline':
508 if self._input_state == 'readline':
507 position = self.GetCurrentPos()
509 position = self.GetCurrentPos()
508 for line in self.input_buffer.split('\n'):
510 for line in self.input_buffer.split('\n'):
509 if not line == self.continuation_prompt()[:-1]:
511 if not line == self.continuation_prompt()[:-1]:
510 new_lines.append(line)
512 new_lines.append(line)
511 self.input_buffer = '\n'.join(new_lines)
513 self.input_buffer = '\n'.join(new_lines)
512 self.GotoPos(position)
514 self.GotoPos(position)
513
515
514
516
515 def _on_enter(self):
517 def _on_enter(self):
516 """ Called on return key down, in readline input_state.
518 """ Called on return key down, in readline input_state.
517 """
519 """
520 last_line_num = self.LineFromPosition(self.GetLength())
521 current_line_num = self.LineFromPosition(self.GetCurrentPos())
522 new_line_pos = (last_line_num - current_line_num)
518 if self.debug:
523 if self.debug:
519 print >>sys.__stdout__, repr(self.input_buffer)
524 print >>sys.__stdout__, repr(self.input_buffer)
520 PrefilterFrontEnd._on_enter(self)
525 self.write('\n', refresh=False)
526 # Under windows scintilla seems to be doing funny
527 # stuff to the line returns here, but the getter for
528 # input_buffer filters this out.
529 if sys.platform == 'win32':
530 self.input_buffer = self.input_buffer
531 has_executed = PrefilterFrontEnd._on_enter(self,
532 new_line_pos=new_line_pos)
533 if not has_executed:
534 self.GotoPos(self.GetLineEndPosition(current_line_num + 1))
535 return has_executed
521
536
522
537
523 #--------------------------------------------------------------------------
538 #--------------------------------------------------------------------------
524 # EditWindow API
539 # EditWindow API
525 #--------------------------------------------------------------------------
540 #--------------------------------------------------------------------------
526
541
527 def OnUpdateUI(self, event):
542 def OnUpdateUI(self, event):
528 """ Override the OnUpdateUI of the EditWindow class, to prevent
543 """ Override the OnUpdateUI of the EditWindow class, to prevent
529 syntax highlighting both for faster redraw, and for more
544 syntax highlighting both for faster redraw, and for more
530 consistent look and feel.
545 consistent look and feel.
531 """
546 """
532 if not self._input_state == 'readline':
547 if not self._input_state == 'readline':
533 ConsoleWidget.OnUpdateUI(self, event)
548 ConsoleWidget.OnUpdateUI(self, event)
534
549
535 #--------------------------------------------------------------------------
550 #--------------------------------------------------------------------------
536 # Private API
551 # Private API
537 #--------------------------------------------------------------------------
552 #--------------------------------------------------------------------------
538
553
539 def _buffer_flush(self, event):
554 def _buffer_flush(self, event):
540 """ Called by the timer to flush the write buffer.
555 """ Called by the timer to flush the write buffer.
541
556
542 This is always called in the mainloop, by the wx timer.
557 This is always called in the mainloop, by the wx timer.
543 """
558 """
544 self._out_buffer_lock.acquire()
559 self._out_buffer_lock.acquire()
545 _out_buffer = self._out_buffer
560 _out_buffer = self._out_buffer
546 self._out_buffer = []
561 self._out_buffer = []
547 self._out_buffer_lock.release()
562 self._out_buffer_lock.release()
548 self.write(''.join(_out_buffer), refresh=False)
563 self.write(''.join(_out_buffer), refresh=False)
549
564
550
565
551 def _colorize_input_buffer(self):
566 def _colorize_input_buffer(self):
552 """ Keep the input buffer lines at a bright color.
567 """ Keep the input buffer lines at a bright color.
553 """
568 """
554 if not self._input_state in ('readline', 'raw_input'):
569 if not self._input_state in ('readline', 'raw_input'):
555 return
570 return
556 end_line = self.GetCurrentLine()
571 end_line = self.GetCurrentLine()
557 if not sys.platform == 'win32':
572 if not sys.platform == 'win32':
558 end_line += 1
573 end_line += 1
559 for i in range(self.current_prompt_line, end_line):
574 for i in range(self.current_prompt_line, end_line):
560 if i in self._markers:
575 if i in self._markers:
561 self.MarkerDeleteHandle(self._markers[i])
576 self.MarkerDeleteHandle(self._markers[i])
562 self._markers[i] = self.MarkerAdd(i, _INPUT_MARKER)
577 self._markers[i] = self.MarkerAdd(i, _INPUT_MARKER)
563
578
564
579
565 if __name__ == '__main__':
580 if __name__ == '__main__':
566 class MainWindow(wx.Frame):
581 class MainWindow(wx.Frame):
567 def __init__(self, parent, id, title):
582 def __init__(self, parent, id, title):
568 wx.Frame.__init__(self, parent, id, title, size=(300,250))
583 wx.Frame.__init__(self, parent, id, title, size=(300,250))
569 self._sizer = wx.BoxSizer(wx.VERTICAL)
584 self._sizer = wx.BoxSizer(wx.VERTICAL)
570 self.shell = WxController(self)
585 self.shell = WxController(self)
571 self._sizer.Add(self.shell, 1, wx.EXPAND)
586 self._sizer.Add(self.shell, 1, wx.EXPAND)
572 self.SetSizer(self._sizer)
587 self.SetSizer(self._sizer)
573 self.SetAutoLayout(1)
588 self.SetAutoLayout(1)
574 self.Show(True)
589 self.Show(True)
575
590
576 app = wx.PySimpleApp()
591 app = wx.PySimpleApp()
577 frame = MainWindow(None, wx.ID_ANY, 'Ipython')
592 frame = MainWindow(None, wx.ID_ANY, 'Ipython')
578 frame.shell.SetFocus()
593 frame.shell.SetFocus()
579 frame.SetSize((680, 460))
594 frame.SetSize((680, 460))
580 self = frame.shell
595 self = frame.shell
581
596
582 app.MainLoop()
597 app.MainLoop()
583
598
General Comments 0
You need to be logged in to leave comments. Login now