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