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