##// END OF EJS Templates
Tweak to have windows show tracebacks.
gvaroquaux -
Show More
@@ -1,433 +1,434 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=wx.WANTS_CHARS, ):
127 size=wx.DefaultSize, style=wx.WANTS_CHARS, ):
128 editwindow.EditWindow.__init__(self, parent, id, pos, size, style)
128 editwindow.EditWindow.__init__(self, parent, id, pos, size, style)
129 self._configure_scintilla()
129 self._configure_scintilla()
130
130
131 self.Bind(wx.EVT_KEY_DOWN, self._on_key_down)
131 self.Bind(wx.EVT_KEY_DOWN, self._on_key_down)
132 self.Bind(wx.EVT_KEY_UP, self._on_key_up)
132 self.Bind(wx.EVT_KEY_UP, self._on_key_up)
133
133
134
134
135 def write(self, text, refresh=True):
135 def write(self, text, refresh=True):
136 """ Write given text to buffer, while translating the ansi escape
136 """ Write given text to buffer, while translating the ansi escape
137 sequences.
137 sequences.
138 """
138 """
139 # XXX: do not put print statements to sys.stdout/sys.stderr in
139 # XXX: do not put print statements to sys.stdout/sys.stderr in
140 # this method, the print statements will call this method, as
140 # this method, the print statements will call this method, as
141 # you will end up with an infinit loop
141 # you will end up with an infinit loop
142 title = self.title_pat.split(text)
142 title = self.title_pat.split(text)
143 if len(title)>1:
143 if len(title)>1:
144 self.title = title[-2]
144 self.title = title[-2]
145
145
146 text = self.title_pat.sub('', text)
146 text = self.title_pat.sub('', text)
147 segments = self.color_pat.split(text)
147 segments = self.color_pat.split(text)
148 segment = segments.pop(0)
148 segment = segments.pop(0)
149 self.GotoPos(self.GetLength())
149 self.GotoPos(self.GetLength())
150 self.StartStyling(self.GetLength(), 0xFF)
150 self.StartStyling(self.GetLength(), 0xFF)
151 try:
151 try:
152 self.AppendText(segment)
152 self.AppendText(segment)
153 except UnicodeDecodeError:
153 except UnicodeDecodeError:
154 # XXX: Do I really want to skip the exception?
154 # XXX: Do I really want to skip the exception?
155 pass
155 pass
156
156
157 if segments:
157 if segments:
158 for ansi_tag, text in zip(segments[::2], segments[1::2]):
158 for ansi_tag, text in zip(segments[::2], segments[1::2]):
159 self.StartStyling(self.GetLength(), 0xFF)
159 self.StartStyling(self.GetLength(), 0xFF)
160 try:
160 try:
161 self.AppendText(text)
161 self.AppendText(text)
162 except UnicodeDecodeError:
162 except UnicodeDecodeError:
163 # XXX: Do I really want to skip the exception?
163 # XXX: Do I really want to skip the exception?
164 pass
164 pass
165
165
166 if ansi_tag not in self.ANSI_STYLES:
166 if ansi_tag not in self.ANSI_STYLES:
167 style = 0
167 style = 0
168 else:
168 else:
169 style = self.ANSI_STYLES[ansi_tag][0]
169 style = self.ANSI_STYLES[ansi_tag][0]
170
170
171 self.SetStyling(len(text), style)
171 self.SetStyling(len(text), style)
172
172
173 self.GotoPos(self.GetLength())
173 self.GotoPos(self.GetLength())
174 if refresh:
174 if refresh:
175 current_time = time.time()
175 current_time = time.time()
176 if current_time - self._last_refresh_time > 0.03:
176 if current_time - self._last_refresh_time > 0.03:
177 wx.Yield()
177 wx.Yield()
178 # self.ProcessEvent(wx.PaintEvent())
178 self._last_refresh_time = current_time
179 self._last_refresh_time = current_time
179
180
180
181
181 def new_prompt(self, prompt):
182 def new_prompt(self, prompt):
182 """ Prints a prompt at start of line, and move the start of the
183 """ Prints a prompt at start of line, and move the start of the
183 current block there.
184 current block there.
184
185
185 The prompt can be given with ascii escape sequences.
186 The prompt can be given with ascii escape sequences.
186 """
187 """
187 self.write(prompt, refresh=False)
188 self.write(prompt, refresh=False)
188 # now we update our cursor giving end of prompt
189 # now we update our cursor giving end of prompt
189 self.current_prompt_pos = self.GetLength()
190 self.current_prompt_pos = self.GetLength()
190 self.current_prompt_line = self.GetCurrentLine()
191 self.current_prompt_line = self.GetCurrentLine()
191 wx.Yield()
192 wx.Yield()
192 self.EnsureCaretVisible()
193 self.EnsureCaretVisible()
193
194
194
195
195 def scroll_to_bottom(self):
196 def scroll_to_bottom(self):
196 maxrange = self.GetScrollRange(wx.VERTICAL)
197 maxrange = self.GetScrollRange(wx.VERTICAL)
197 self.ScrollLines(maxrange)
198 self.ScrollLines(maxrange)
198
199
199
200
200 def pop_completion(self, possibilities, offset=0):
201 def pop_completion(self, possibilities, offset=0):
201 """ Pops up an autocompletion menu. Offset is the offset
202 """ Pops up an autocompletion menu. Offset is the offset
202 in characters of the position at which the menu should
203 in characters of the position at which the menu should
203 appear, relativ to the cursor.
204 appear, relativ to the cursor.
204 """
205 """
205 self.AutoCompSetIgnoreCase(False)
206 self.AutoCompSetIgnoreCase(False)
206 self.AutoCompSetAutoHide(False)
207 self.AutoCompSetAutoHide(False)
207 self.AutoCompSetMaxHeight(len(possibilities))
208 self.AutoCompSetMaxHeight(len(possibilities))
208 self.AutoCompShow(offset, " ".join(possibilities))
209 self.AutoCompShow(offset, " ".join(possibilities))
209
210
210
211
211 def get_line_width(self):
212 def get_line_width(self):
212 """ Return the width of the line in characters.
213 """ Return the width of the line in characters.
213 """
214 """
214 return self.GetSize()[0]/self.GetCharWidth()
215 return self.GetSize()[0]/self.GetCharWidth()
215
216
216 #--------------------------------------------------------------------------
217 #--------------------------------------------------------------------------
217 # EditWindow API
218 # EditWindow API
218 #--------------------------------------------------------------------------
219 #--------------------------------------------------------------------------
219
220
220 def OnUpdateUI(self, event):
221 def OnUpdateUI(self, event):
221 """ Override the OnUpdateUI of the EditWindow class, to prevent
222 """ Override the OnUpdateUI of the EditWindow class, to prevent
222 syntax highlighting both for faster redraw, and for more
223 syntax highlighting both for faster redraw, and for more
223 consistent look and feel.
224 consistent look and feel.
224 """
225 """
225
226
226 #--------------------------------------------------------------------------
227 #--------------------------------------------------------------------------
227 # Private API
228 # Private API
228 #--------------------------------------------------------------------------
229 #--------------------------------------------------------------------------
229
230
230 def _apply_style(self):
231 def _apply_style(self):
231 """ Applies the colors for the different text elements and the
232 """ Applies the colors for the different text elements and the
232 carret.
233 carret.
233 """
234 """
234 self.SetCaretForeground(self.carret_color)
235 self.SetCaretForeground(self.carret_color)
235
236
236 #self.StyleClearAll()
237 #self.StyleClearAll()
237 self.StyleSetSpec(stc.STC_STYLE_BRACELIGHT,
238 self.StyleSetSpec(stc.STC_STYLE_BRACELIGHT,
238 "fore:#FF0000,back:#0000FF,bold")
239 "fore:#FF0000,back:#0000FF,bold")
239 self.StyleSetSpec(stc.STC_STYLE_BRACEBAD,
240 self.StyleSetSpec(stc.STC_STYLE_BRACEBAD,
240 "fore:#000000,back:#FF0000,bold")
241 "fore:#000000,back:#FF0000,bold")
241
242
242 for style in self.ANSI_STYLES.values():
243 for style in self.ANSI_STYLES.values():
243 self.StyleSetSpec(style[0], "bold,fore:%s" % style[1])
244 self.StyleSetSpec(style[0], "bold,fore:%s" % style[1])
244
245
245
246
246 def _configure_scintilla(self):
247 def _configure_scintilla(self):
247 self.SetEOLMode(stc.STC_EOL_LF)
248 self.SetEOLMode(stc.STC_EOL_LF)
248
249
249 # Ctrl"+" or Ctrl "-" can be used to zoomin/zoomout the text inside
250 # Ctrl"+" or Ctrl "-" can be used to zoomin/zoomout the text inside
250 # the widget
251 # the widget
251 self.CmdKeyAssign(ord('+'), stc.STC_SCMOD_CTRL, stc.STC_CMD_ZOOMIN)
252 self.CmdKeyAssign(ord('+'), stc.STC_SCMOD_CTRL, stc.STC_CMD_ZOOMIN)
252 self.CmdKeyAssign(ord('-'), stc.STC_SCMOD_CTRL, stc.STC_CMD_ZOOMOUT)
253 self.CmdKeyAssign(ord('-'), stc.STC_SCMOD_CTRL, stc.STC_CMD_ZOOMOUT)
253 # Also allow Ctrl Shift "=" for poor non US keyboard users.
254 # Also allow Ctrl Shift "=" for poor non US keyboard users.
254 self.CmdKeyAssign(ord('='), stc.STC_SCMOD_CTRL|stc.STC_SCMOD_SHIFT,
255 self.CmdKeyAssign(ord('='), stc.STC_SCMOD_CTRL|stc.STC_SCMOD_SHIFT,
255 stc.STC_CMD_ZOOMIN)
256 stc.STC_CMD_ZOOMIN)
256
257
257 # Keys: we need to clear some of the keys the that don't play
258 # Keys: we need to clear some of the keys the that don't play
258 # well with a console.
259 # well with a console.
259 self.CmdKeyClear(ord('D'), stc.STC_SCMOD_CTRL)
260 self.CmdKeyClear(ord('D'), stc.STC_SCMOD_CTRL)
260 self.CmdKeyClear(ord('L'), stc.STC_SCMOD_CTRL)
261 self.CmdKeyClear(ord('L'), stc.STC_SCMOD_CTRL)
261 self.CmdKeyClear(ord('T'), stc.STC_SCMOD_CTRL)
262 self.CmdKeyClear(ord('T'), stc.STC_SCMOD_CTRL)
262 self.CmdKeyClear(ord('A'), stc.STC_SCMOD_CTRL)
263 self.CmdKeyClear(ord('A'), stc.STC_SCMOD_CTRL)
263
264
264 self.SetEOLMode(stc.STC_EOL_CRLF)
265 self.SetEOLMode(stc.STC_EOL_CRLF)
265 self.SetWrapMode(stc.STC_WRAP_CHAR)
266 self.SetWrapMode(stc.STC_WRAP_CHAR)
266 self.SetWrapMode(stc.STC_WRAP_WORD)
267 self.SetWrapMode(stc.STC_WRAP_WORD)
267 self.SetBufferedDraw(True)
268 self.SetBufferedDraw(True)
268 self.SetUseAntiAliasing(True)
269 self.SetUseAntiAliasing(True)
269 self.SetLayoutCache(stc.STC_CACHE_PAGE)
270 self.SetLayoutCache(stc.STC_CACHE_PAGE)
270 self.SetUndoCollection(False)
271 self.SetUndoCollection(False)
271 self.SetUseTabs(True)
272 self.SetUseTabs(True)
272 self.SetIndent(4)
273 self.SetIndent(4)
273 self.SetTabWidth(4)
274 self.SetTabWidth(4)
274
275
275 # we don't want scintilla's autocompletion to choose
276 # we don't want scintilla's autocompletion to choose
276 # automaticaly out of a single choice list, as we pop it up
277 # automaticaly out of a single choice list, as we pop it up
277 # automaticaly
278 # automaticaly
278 self.AutoCompSetChooseSingle(False)
279 self.AutoCompSetChooseSingle(False)
279 self.AutoCompSetMaxHeight(10)
280 self.AutoCompSetMaxHeight(10)
280 # XXX: this doesn't seem to have an effect.
281 # XXX: this doesn't seem to have an effect.
281 self.AutoCompSetFillUps('\n')
282 self.AutoCompSetFillUps('\n')
282
283
283 self.SetMargins(3, 3) #text is moved away from border with 3px
284 self.SetMargins(3, 3) #text is moved away from border with 3px
284 # Suppressing Scintilla margins
285 # Suppressing Scintilla margins
285 self.SetMarginWidth(0, 0)
286 self.SetMarginWidth(0, 0)
286 self.SetMarginWidth(1, 0)
287 self.SetMarginWidth(1, 0)
287 self.SetMarginWidth(2, 0)
288 self.SetMarginWidth(2, 0)
288
289
289 self._apply_style()
290 self._apply_style()
290
291
291 # Xterm escape sequences
292 # Xterm escape sequences
292 self.color_pat = re.compile('\x01?\x1b\[(.*?)m\x02?')
293 self.color_pat = re.compile('\x01?\x1b\[(.*?)m\x02?')
293 self.title_pat = re.compile('\x1b]0;(.*?)\x07')
294 self.title_pat = re.compile('\x1b]0;(.*?)\x07')
294
295
295 #self.SetEdgeMode(stc.STC_EDGE_LINE)
296 #self.SetEdgeMode(stc.STC_EDGE_LINE)
296 #self.SetEdgeColumn(80)
297 #self.SetEdgeColumn(80)
297
298
298 # styles
299 # styles
299 p = self.style
300 p = self.style
300 self.StyleSetSpec(stc.STC_STYLE_DEFAULT, p['default'])
301 self.StyleSetSpec(stc.STC_STYLE_DEFAULT, p['default'])
301 self.StyleClearAll()
302 self.StyleClearAll()
302 self.StyleSetSpec(_STDOUT_STYLE, p['stdout'])
303 self.StyleSetSpec(_STDOUT_STYLE, p['stdout'])
303 self.StyleSetSpec(_STDERR_STYLE, p['stderr'])
304 self.StyleSetSpec(_STDERR_STYLE, p['stderr'])
304 self.StyleSetSpec(_TRACE_STYLE, p['trace'])
305 self.StyleSetSpec(_TRACE_STYLE, p['trace'])
305
306
306 self.StyleSetSpec(stc.STC_STYLE_BRACELIGHT, p['bracegood'])
307 self.StyleSetSpec(stc.STC_STYLE_BRACELIGHT, p['bracegood'])
307 self.StyleSetSpec(stc.STC_STYLE_BRACEBAD, p['bracebad'])
308 self.StyleSetSpec(stc.STC_STYLE_BRACEBAD, p['bracebad'])
308 self.StyleSetSpec(stc.STC_P_COMMENTLINE, p['comment'])
309 self.StyleSetSpec(stc.STC_P_COMMENTLINE, p['comment'])
309 self.StyleSetSpec(stc.STC_P_NUMBER, p['number'])
310 self.StyleSetSpec(stc.STC_P_NUMBER, p['number'])
310 self.StyleSetSpec(stc.STC_P_STRING, p['string'])
311 self.StyleSetSpec(stc.STC_P_STRING, p['string'])
311 self.StyleSetSpec(stc.STC_P_CHARACTER, p['char'])
312 self.StyleSetSpec(stc.STC_P_CHARACTER, p['char'])
312 self.StyleSetSpec(stc.STC_P_WORD, p['keyword'])
313 self.StyleSetSpec(stc.STC_P_WORD, p['keyword'])
313 self.StyleSetSpec(stc.STC_P_WORD2, p['keyword'])
314 self.StyleSetSpec(stc.STC_P_WORD2, p['keyword'])
314 self.StyleSetSpec(stc.STC_P_TRIPLE, p['triple'])
315 self.StyleSetSpec(stc.STC_P_TRIPLE, p['triple'])
315 self.StyleSetSpec(stc.STC_P_TRIPLEDOUBLE, p['tripledouble'])
316 self.StyleSetSpec(stc.STC_P_TRIPLEDOUBLE, p['tripledouble'])
316 self.StyleSetSpec(stc.STC_P_CLASSNAME, p['class'])
317 self.StyleSetSpec(stc.STC_P_CLASSNAME, p['class'])
317 self.StyleSetSpec(stc.STC_P_DEFNAME, p['def'])
318 self.StyleSetSpec(stc.STC_P_DEFNAME, p['def'])
318 self.StyleSetSpec(stc.STC_P_OPERATOR, p['operator'])
319 self.StyleSetSpec(stc.STC_P_OPERATOR, p['operator'])
319 self.StyleSetSpec(stc.STC_P_COMMENTBLOCK, p['comment'])
320 self.StyleSetSpec(stc.STC_P_COMMENTBLOCK, p['comment'])
320
321
321 def _on_key_down(self, event, skip=True):
322 def _on_key_down(self, event, skip=True):
322 """ Key press callback used for correcting behavior for
323 """ Key press callback used for correcting behavior for
323 console-like interfaces: the cursor is constraint to be after
324 console-like interfaces: the cursor is constraint to be after
324 the last prompt.
325 the last prompt.
325
326
326 Return True if event as been catched.
327 Return True if event as been catched.
327 """
328 """
328 catched = True
329 catched = True
329 # Intercept some specific keys.
330 # Intercept some specific keys.
330 if event.KeyCode == ord('L') and event.ControlDown() :
331 if event.KeyCode == ord('L') and event.ControlDown() :
331 self.scroll_to_bottom()
332 self.scroll_to_bottom()
332 elif event.KeyCode == ord('K') and event.ControlDown() :
333 elif event.KeyCode == ord('K') and event.ControlDown() :
333 self.input_buffer = ''
334 self.input_buffer = ''
334 elif event.KeyCode == ord('A') and event.ControlDown() :
335 elif event.KeyCode == ord('A') and event.ControlDown() :
335 self.GotoPos(self.GetLength())
336 self.GotoPos(self.GetLength())
336 self.SetSelectionStart(self.current_prompt_pos)
337 self.SetSelectionStart(self.current_prompt_pos)
337 self.SetSelectionEnd(self.GetCurrentPos())
338 self.SetSelectionEnd(self.GetCurrentPos())
338 catched = True
339 catched = True
339 elif event.KeyCode == ord('E') and event.ControlDown() :
340 elif event.KeyCode == ord('E') and event.ControlDown() :
340 self.GotoPos(self.GetLength())
341 self.GotoPos(self.GetLength())
341 catched = True
342 catched = True
342 elif event.KeyCode == wx.WXK_PAGEUP:
343 elif event.KeyCode == wx.WXK_PAGEUP:
343 self.ScrollPages(-1)
344 self.ScrollPages(-1)
344 elif event.KeyCode == wx.WXK_PAGEDOWN:
345 elif event.KeyCode == wx.WXK_PAGEDOWN:
345 self.ScrollPages(1)
346 self.ScrollPages(1)
346 elif event.KeyCode == wx.WXK_UP and event.ShiftDown():
347 elif event.KeyCode == wx.WXK_UP and event.ShiftDown():
347 self.ScrollLines(-1)
348 self.ScrollLines(-1)
348 elif event.KeyCode == wx.WXK_DOWN and event.ShiftDown():
349 elif event.KeyCode == wx.WXK_DOWN and event.ShiftDown():
349 self.ScrollLines(1)
350 self.ScrollLines(1)
350 else:
351 else:
351 catched = False
352 catched = False
352
353
353 if self.AutoCompActive():
354 if self.AutoCompActive():
354 event.Skip()
355 event.Skip()
355 else:
356 else:
356 if event.KeyCode in (13, wx.WXK_NUMPAD_ENTER) and \
357 if event.KeyCode in (13, wx.WXK_NUMPAD_ENTER) and \
357 event.Modifiers in (wx.MOD_NONE, wx.MOD_WIN):
358 event.Modifiers in (wx.MOD_NONE, wx.MOD_WIN):
358 catched = True
359 catched = True
359 self.CallTipCancel()
360 self.CallTipCancel()
360 self.write('\n', refresh=False)
361 self.write('\n', refresh=False)
361 # Under windows scintilla seems to be doing funny stuff to the
362 # Under windows scintilla seems to be doing funny stuff to the
362 # line returns here, but the getter for input_buffer filters
363 # line returns here, but the getter for input_buffer filters
363 # this out.
364 # this out.
364 if sys.platform == 'win32':
365 if sys.platform == 'win32':
365 self.input_buffer = self.input_buffer
366 self.input_buffer = self.input_buffer
366 self._on_enter()
367 self._on_enter()
367
368
368 elif event.KeyCode == wx.WXK_HOME:
369 elif event.KeyCode == wx.WXK_HOME:
369 if event.Modifiers in (wx.MOD_NONE, wx.MOD_WIN):
370 if event.Modifiers in (wx.MOD_NONE, wx.MOD_WIN):
370 self.GotoPos(self.current_prompt_pos)
371 self.GotoPos(self.current_prompt_pos)
371 catched = True
372 catched = True
372
373
373 elif event.Modifiers == wx.MOD_SHIFT:
374 elif event.Modifiers == wx.MOD_SHIFT:
374 # FIXME: This behavior is not ideal: if the selection
375 # FIXME: This behavior is not ideal: if the selection
375 # is already started, it will jump.
376 # is already started, it will jump.
376 self.SetSelectionStart(self.current_prompt_pos)
377 self.SetSelectionStart(self.current_prompt_pos)
377 self.SetSelectionEnd(self.GetCurrentPos())
378 self.SetSelectionEnd(self.GetCurrentPos())
378 catched = True
379 catched = True
379
380
380 elif event.KeyCode == wx.WXK_UP:
381 elif event.KeyCode == wx.WXK_UP:
381 if self.GetCurrentLine() > self.current_prompt_line:
382 if self.GetCurrentLine() > self.current_prompt_line:
382 if self.GetCurrentLine() == self.current_prompt_line + 1 \
383 if self.GetCurrentLine() == self.current_prompt_line + 1 \
383 and self.GetColumn(self.GetCurrentPos()) < \
384 and self.GetColumn(self.GetCurrentPos()) < \
384 self.GetColumn(self.current_prompt_pos):
385 self.GetColumn(self.current_prompt_pos):
385 self.GotoPos(self.current_prompt_pos)
386 self.GotoPos(self.current_prompt_pos)
386 else:
387 else:
387 event.Skip()
388 event.Skip()
388 catched = True
389 catched = True
389
390
390 elif event.KeyCode in (wx.WXK_LEFT, wx.WXK_BACK):
391 elif event.KeyCode in (wx.WXK_LEFT, wx.WXK_BACK):
391 if self.GetCurrentPos() > self.current_prompt_pos:
392 if self.GetCurrentPos() > self.current_prompt_pos:
392 event.Skip()
393 event.Skip()
393 catched = True
394 catched = True
394
395
395 if skip and not catched:
396 if skip and not catched:
396 # Put the cursor back in the edit region
397 # Put the cursor back in the edit region
397 if self.GetCurrentPos() < self.current_prompt_pos:
398 if self.GetCurrentPos() < self.current_prompt_pos:
398 self.GotoPos(self.current_prompt_pos)
399 self.GotoPos(self.current_prompt_pos)
399 else:
400 else:
400 event.Skip()
401 event.Skip()
401
402
402 return catched
403 return catched
403
404
404
405
405 def _on_key_up(self, event, skip=True):
406 def _on_key_up(self, event, skip=True):
406 """ If cursor is outside the editing region, put it back.
407 """ If cursor is outside the editing region, put it back.
407 """
408 """
408 event.Skip()
409 event.Skip()
409 if self.GetCurrentPos() < self.current_prompt_pos:
410 if self.GetCurrentPos() < self.current_prompt_pos:
410 self.GotoPos(self.current_prompt_pos)
411 self.GotoPos(self.current_prompt_pos)
411
412
412
413
413
414
414 if __name__ == '__main__':
415 if __name__ == '__main__':
415 # Some simple code to test the console widget.
416 # Some simple code to test the console widget.
416 class MainWindow(wx.Frame):
417 class MainWindow(wx.Frame):
417 def __init__(self, parent, id, title):
418 def __init__(self, parent, id, title):
418 wx.Frame.__init__(self, parent, id, title, size=(300,250))
419 wx.Frame.__init__(self, parent, id, title, size=(300,250))
419 self._sizer = wx.BoxSizer(wx.VERTICAL)
420 self._sizer = wx.BoxSizer(wx.VERTICAL)
420 self.console_widget = ConsoleWidget(self)
421 self.console_widget = ConsoleWidget(self)
421 self._sizer.Add(self.console_widget, 1, wx.EXPAND)
422 self._sizer.Add(self.console_widget, 1, wx.EXPAND)
422 self.SetSizer(self._sizer)
423 self.SetSizer(self._sizer)
423 self.SetAutoLayout(1)
424 self.SetAutoLayout(1)
424 self.Show(True)
425 self.Show(True)
425
426
426 app = wx.PySimpleApp()
427 app = wx.PySimpleApp()
427 w = MainWindow(None, wx.ID_ANY, 'ConsoleWidget')
428 w = MainWindow(None, wx.ID_ANY, 'ConsoleWidget')
428 w.SetSize((780, 460))
429 w.SetSize((780, 460))
429 w.Show()
430 w.Show()
430
431
431 app.MainLoop()
432 app.MainLoop()
432
433
433
434
@@ -1,526 +1,527 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,
139 size=wx.DefaultSize,
140 style=wx.CLIP_CHILDREN|wx.WANTS_CHARS,
140 style=wx.CLIP_CHILDREN|wx.WANTS_CHARS,
141 *args, **kwds):
141 *args, **kwds):
142 """ Create Shell instance.
142 """ Create Shell instance.
143 """
143 """
144 ConsoleWidget.__init__(self, parent, id, pos, size, style)
144 ConsoleWidget.__init__(self, parent, id, pos, size, style)
145 PrefilterFrontEnd.__init__(self, **kwds)
145 PrefilterFrontEnd.__init__(self, **kwds)
146
146
147 # Marker for complete buffer.
147 # Marker for complete buffer.
148 self.MarkerDefine(_COMPLETE_BUFFER_MARKER, stc.STC_MARK_BACKGROUND,
148 self.MarkerDefine(_COMPLETE_BUFFER_MARKER, stc.STC_MARK_BACKGROUND,
149 background=_COMPLETE_BUFFER_BG)
149 background=_COMPLETE_BUFFER_BG)
150 # Marker for current input buffer.
150 # Marker for current input buffer.
151 self.MarkerDefine(_INPUT_MARKER, stc.STC_MARK_BACKGROUND,
151 self.MarkerDefine(_INPUT_MARKER, stc.STC_MARK_BACKGROUND,
152 background=_INPUT_BUFFER_BG)
152 background=_INPUT_BUFFER_BG)
153 # Marker for tracebacks.
153 # Marker for tracebacks.
154 self.MarkerDefine(_ERROR_MARKER, stc.STC_MARK_BACKGROUND,
154 self.MarkerDefine(_ERROR_MARKER, stc.STC_MARK_BACKGROUND,
155 background=_ERROR_BG)
155 background=_ERROR_BG)
156
156
157 # A time for flushing the write buffer
157 # A time for flushing the write buffer
158 BUFFER_FLUSH_TIMER_ID = 100
158 BUFFER_FLUSH_TIMER_ID = 100
159 self._buffer_flush_timer = wx.Timer(self, BUFFER_FLUSH_TIMER_ID)
159 self._buffer_flush_timer = wx.Timer(self, BUFFER_FLUSH_TIMER_ID)
160 wx.EVT_TIMER(self, BUFFER_FLUSH_TIMER_ID, self._buffer_flush)
160 wx.EVT_TIMER(self, BUFFER_FLUSH_TIMER_ID, self._buffer_flush)
161
161
162 if 'debug' in kwds:
162 if 'debug' in kwds:
163 self.debug = kwds['debug']
163 self.debug = kwds['debug']
164 kwds.pop('debug')
164 kwds.pop('debug')
165
165
166 # Inject self in namespace, for debug
166 # Inject self in namespace, for debug
167 if self.debug:
167 if self.debug:
168 self.shell.user_ns['self'] = self
168 self.shell.user_ns['self'] = self
169
169
170
170
171 def raw_input(self, prompt):
171 def raw_input(self, prompt):
172 """ A replacement from python's raw_input.
172 """ A replacement from python's raw_input.
173 """
173 """
174 self.new_prompt(prompt)
174 self.new_prompt(prompt)
175 self._input_state = 'raw_input'
175 self._input_state = 'raw_input'
176 if hasattr(self, '_cursor'):
176 if hasattr(self, '_cursor'):
177 del self._cursor
177 del self._cursor
178 self.SetCursor(wx.StockCursor(wx.CURSOR_CROSS))
178 self.SetCursor(wx.StockCursor(wx.CURSOR_CROSS))
179 self.waiting = True
179 self.waiting = True
180 self.__old_on_enter = self._on_enter
180 self.__old_on_enter = self._on_enter
181 def my_on_enter():
181 def my_on_enter():
182 self.waiting = False
182 self.waiting = False
183 self._on_enter = my_on_enter
183 self._on_enter = my_on_enter
184 # XXX: Busy waiting, ugly.
184 # XXX: Busy waiting, ugly.
185 while self.waiting:
185 while self.waiting:
186 wx.Yield()
186 wx.Yield()
187 sleep(0.1)
187 sleep(0.1)
188 self._on_enter = self.__old_on_enter
188 self._on_enter = self.__old_on_enter
189 self._input_state = 'buffering'
189 self._input_state = 'buffering'
190 self._cursor = wx.BusyCursor()
190 self._cursor = wx.BusyCursor()
191 return self.input_buffer.rstrip('\n')
191 return self.input_buffer.rstrip('\n')
192
192
193
193
194 def system_call(self, command_string):
194 def system_call(self, command_string):
195 self._input_state = 'subprocess'
195 self._input_state = 'subprocess'
196 self._running_process = PipedProcess(command_string,
196 self._running_process = PipedProcess(command_string,
197 out_callback=self.buffered_write,
197 out_callback=self.buffered_write,
198 end_callback = self._end_system_call)
198 end_callback = self._end_system_call)
199 self._running_process.start()
199 self._running_process.start()
200 # XXX: another one of these polling loops to have a blocking
200 # XXX: another one of these polling loops to have a blocking
201 # call
201 # call
202 wx.Yield()
202 wx.Yield()
203 while self._running_process:
203 while self._running_process:
204 wx.Yield()
204 wx.Yield()
205 sleep(0.1)
205 sleep(0.1)
206 # Be sure to flush the buffer.
206 # Be sure to flush the buffer.
207 self._buffer_flush(event=None)
207 self._buffer_flush(event=None)
208
208
209
209
210 def do_calltip(self):
210 def do_calltip(self):
211 """ Analyse current and displays useful calltip for it.
211 """ Analyse current and displays useful calltip for it.
212 """
212 """
213 if self.debug:
213 if self.debug:
214 print >>sys.__stdout__, "do_calltip"
214 print >>sys.__stdout__, "do_calltip"
215 separators = re.compile('[\s\{\}\[\]\(\)\= ,:]')
215 separators = re.compile('[\s\{\}\[\]\(\)\= ,:]')
216 symbol = self.input_buffer
216 symbol = self.input_buffer
217 symbol_string = separators.split(symbol)[-1]
217 symbol_string = separators.split(symbol)[-1]
218 base_symbol_string = symbol_string.split('.')[0]
218 base_symbol_string = symbol_string.split('.')[0]
219 if base_symbol_string in self.shell.user_ns:
219 if base_symbol_string in self.shell.user_ns:
220 symbol = self.shell.user_ns[base_symbol_string]
220 symbol = self.shell.user_ns[base_symbol_string]
221 elif base_symbol_string in self.shell.user_global_ns:
221 elif base_symbol_string in self.shell.user_global_ns:
222 symbol = self.shell.user_global_ns[base_symbol_string]
222 symbol = self.shell.user_global_ns[base_symbol_string]
223 elif base_symbol_string in __builtin__.__dict__:
223 elif base_symbol_string in __builtin__.__dict__:
224 symbol = __builtin__.__dict__[base_symbol_string]
224 symbol = __builtin__.__dict__[base_symbol_string]
225 else:
225 else:
226 return False
226 return False
227 try:
227 try:
228 for name in symbol_string.split('.')[1:] + ['__doc__']:
228 for name in symbol_string.split('.')[1:] + ['__doc__']:
229 symbol = getattr(symbol, name)
229 symbol = getattr(symbol, name)
230 self.AutoCompCancel()
230 self.AutoCompCancel()
231 wx.Yield()
231 wx.Yield()
232 self.CallTipShow(self.GetCurrentPos(), symbol)
232 self.CallTipShow(self.GetCurrentPos(), symbol)
233 except:
233 except:
234 # The retrieve symbol couldn't be converted to a string
234 # The retrieve symbol couldn't be converted to a string
235 pass
235 pass
236
236
237
237
238 def _popup_completion(self, create=False):
238 def _popup_completion(self, create=False):
239 """ Updates the popup completion menu if it exists. If create is
239 """ Updates the popup completion menu if it exists. If create is
240 true, open the menu.
240 true, open the menu.
241 """
241 """
242 if self.debug:
242 if self.debug:
243 print >>sys.__stdout__, "_popup_completion"
243 print >>sys.__stdout__, "_popup_completion"
244 line = self.input_buffer
244 line = self.input_buffer
245 if (self.AutoCompActive() and line and not line[-1] == '.') \
245 if (self.AutoCompActive() and line and not line[-1] == '.') \
246 or create==True:
246 or create==True:
247 suggestion, completions = self.complete(line)
247 suggestion, completions = self.complete(line)
248 offset=0
248 offset=0
249 if completions:
249 if completions:
250 complete_sep = re.compile('[\s\{\}\[\]\(\)\= ,:]')
250 complete_sep = re.compile('[\s\{\}\[\]\(\)\= ,:]')
251 residual = complete_sep.split(line)[-1]
251 residual = complete_sep.split(line)[-1]
252 offset = len(residual)
252 offset = len(residual)
253 self.pop_completion(completions, offset=offset)
253 self.pop_completion(completions, offset=offset)
254 if self.debug:
254 if self.debug:
255 print >>sys.__stdout__, completions
255 print >>sys.__stdout__, completions
256
256
257
257
258 def buffered_write(self, text):
258 def buffered_write(self, text):
259 """ A write method for streams, that caches the stream in order
259 """ A write method for streams, that caches the stream in order
260 to avoid flooding the event loop.
260 to avoid flooding the event loop.
261
261
262 This can be called outside of the main loop, in separate
262 This can be called outside of the main loop, in separate
263 threads.
263 threads.
264 """
264 """
265 self._out_buffer_lock.acquire()
265 self._out_buffer_lock.acquire()
266 self._out_buffer.append(text)
266 self._out_buffer.append(text)
267 self._out_buffer_lock.release()
267 self._out_buffer_lock.release()
268 if not self._buffer_flush_timer.IsRunning():
268 if not self._buffer_flush_timer.IsRunning():
269 wx.CallAfter(self._buffer_flush_timer.Start,
269 wx.CallAfter(self._buffer_flush_timer.Start,
270 milliseconds=100, oneShot=True)
270 milliseconds=100, oneShot=True)
271
271
272
272
273 #--------------------------------------------------------------------------
273 #--------------------------------------------------------------------------
274 # LineFrontEnd interface
274 # LineFrontEnd interface
275 #--------------------------------------------------------------------------
275 #--------------------------------------------------------------------------
276
276
277 def execute(self, python_string, raw_string=None):
277 def execute(self, python_string, raw_string=None):
278 self._input_state = 'buffering'
278 self._input_state = 'buffering'
279 self.CallTipCancel()
279 self.CallTipCancel()
280 self._cursor = wx.BusyCursor()
280 self._cursor = wx.BusyCursor()
281 if raw_string is None:
281 if raw_string is None:
282 raw_string = python_string
282 raw_string = python_string
283 end_line = self.current_prompt_line \
283 end_line = self.current_prompt_line \
284 + max(1, len(raw_string.split('\n'))-1)
284 + max(1, len(raw_string.split('\n'))-1)
285 for i in range(self.current_prompt_line, end_line):
285 for i in range(self.current_prompt_line, end_line):
286 if i in self._markers:
286 if i in self._markers:
287 self.MarkerDeleteHandle(self._markers[i])
287 self.MarkerDeleteHandle(self._markers[i])
288 self._markers[i] = self.MarkerAdd(i, _COMPLETE_BUFFER_MARKER)
288 self._markers[i] = self.MarkerAdd(i, _COMPLETE_BUFFER_MARKER)
289 # Use a callafter to update the display robustly under windows
289 # Use a callafter to update the display robustly under windows
290 def callback():
290 def callback():
291 self.GotoPos(self.GetLength())
291 self.GotoPos(self.GetLength())
292 PrefilterFrontEnd.execute(self, python_string,
292 PrefilterFrontEnd.execute(self, python_string,
293 raw_string=raw_string)
293 raw_string=raw_string)
294 wx.CallAfter(callback)
294 wx.CallAfter(callback)
295
295
296 def save_output_hooks(self):
296 def save_output_hooks(self):
297 self.__old_raw_input = __builtin__.raw_input
297 self.__old_raw_input = __builtin__.raw_input
298 PrefilterFrontEnd.save_output_hooks(self)
298 PrefilterFrontEnd.save_output_hooks(self)
299
299
300 def capture_output(self):
300 def capture_output(self):
301 __builtin__.raw_input = self.raw_input
301 __builtin__.raw_input = self.raw_input
302 self.SetLexer(stc.STC_LEX_NULL)
302 self.SetLexer(stc.STC_LEX_NULL)
303 PrefilterFrontEnd.capture_output(self)
303 PrefilterFrontEnd.capture_output(self)
304
304
305
305
306 def release_output(self):
306 def release_output(self):
307 __builtin__.raw_input = self.__old_raw_input
307 __builtin__.raw_input = self.__old_raw_input
308 PrefilterFrontEnd.release_output(self)
308 PrefilterFrontEnd.release_output(self)
309 self.SetLexer(stc.STC_LEX_PYTHON)
309 self.SetLexer(stc.STC_LEX_PYTHON)
310
310
311
311
312 def after_execute(self):
312 def after_execute(self):
313 PrefilterFrontEnd.after_execute(self)
313 PrefilterFrontEnd.after_execute(self)
314 # Clear the wait cursor
314 # Clear the wait cursor
315 if hasattr(self, '_cursor'):
315 if hasattr(self, '_cursor'):
316 del self._cursor
316 del self._cursor
317 self.SetCursor(wx.StockCursor(wx.CURSOR_CHAR))
317 self.SetCursor(wx.StockCursor(wx.CURSOR_CHAR))
318
318
319
319
320 def show_traceback(self):
320 def show_traceback(self):
321 start_line = self.GetCurrentLine()
321 start_line = self.GetCurrentLine()
322 PrefilterFrontEnd.show_traceback(self)
322 PrefilterFrontEnd.show_traceback(self)
323 wx.Yield()
323 self.ProcessEvent(wx.PaintEvent())
324 #wx.Yield()
324 for i in range(start_line, self.GetCurrentLine()):
325 for i in range(start_line, self.GetCurrentLine()):
325 self._markers[i] = self.MarkerAdd(i, _ERROR_MARKER)
326 self._markers[i] = self.MarkerAdd(i, _ERROR_MARKER)
326
327
327
328
328 #--------------------------------------------------------------------------
329 #--------------------------------------------------------------------------
329 # FrontEndBase interface
330 # FrontEndBase interface
330 #--------------------------------------------------------------------------
331 #--------------------------------------------------------------------------
331
332
332 def render_error(self, e):
333 def render_error(self, e):
333 start_line = self.GetCurrentLine()
334 start_line = self.GetCurrentLine()
334 self.write('\n' + e + '\n')
335 self.write('\n' + e + '\n')
335 for i in range(start_line, self.GetCurrentLine()):
336 for i in range(start_line, self.GetCurrentLine()):
336 self._markers[i] = self.MarkerAdd(i, _ERROR_MARKER)
337 self._markers[i] = self.MarkerAdd(i, _ERROR_MARKER)
337
338
338
339
339 #--------------------------------------------------------------------------
340 #--------------------------------------------------------------------------
340 # ConsoleWidget interface
341 # ConsoleWidget interface
341 #--------------------------------------------------------------------------
342 #--------------------------------------------------------------------------
342
343
343 def new_prompt(self, prompt):
344 def new_prompt(self, prompt):
344 """ Display a new prompt, and start a new input buffer.
345 """ Display a new prompt, and start a new input buffer.
345 """
346 """
346 self._input_state = 'readline'
347 self._input_state = 'readline'
347 ConsoleWidget.new_prompt(self, prompt)
348 ConsoleWidget.new_prompt(self, prompt)
348 i = self.current_prompt_line
349 i = self.current_prompt_line
349 self._markers[i] = self.MarkerAdd(i, _INPUT_MARKER)
350 self._markers[i] = self.MarkerAdd(i, _INPUT_MARKER)
350
351
351
352
352 def write(self, *args, **kwargs):
353 def write(self, *args, **kwargs):
353 # Avoid multiple inheritence, be explicit about which
354 # Avoid multiple inheritence, be explicit about which
354 # parent method class gets called
355 # parent method class gets called
355 ConsoleWidget.write(self, *args, **kwargs)
356 ConsoleWidget.write(self, *args, **kwargs)
356
357
357
358
358 def _on_key_down(self, event, skip=True):
359 def _on_key_down(self, event, skip=True):
359 """ Capture the character events, let the parent
360 """ Capture the character events, let the parent
360 widget handle them, and put our logic afterward.
361 widget handle them, and put our logic afterward.
361 """
362 """
362 # FIXME: This method needs to be broken down in smaller ones.
363 # FIXME: This method needs to be broken down in smaller ones.
363 current_line_number = self.GetCurrentLine()
364 current_line_number = self.GetCurrentLine()
364 if event.KeyCode in (ord('c'), ord('C')) and event.ControlDown():
365 if event.KeyCode in (ord('c'), ord('C')) and event.ControlDown():
365 # Capture Control-C
366 # Capture Control-C
366 if self._input_state == 'subprocess':
367 if self._input_state == 'subprocess':
367 if self.debug:
368 if self.debug:
368 print >>sys.__stderr__, 'Killing running process'
369 print >>sys.__stderr__, 'Killing running process'
369 if hasattr(self._running_process, 'process'):
370 if hasattr(self._running_process, 'process'):
370 self._running_process.process.kill()
371 self._running_process.process.kill()
371 elif self._input_state == 'buffering':
372 elif self._input_state == 'buffering':
372 if self.debug:
373 if self.debug:
373 print >>sys.__stderr__, 'Raising KeyboardInterrupt'
374 print >>sys.__stderr__, 'Raising KeyboardInterrupt'
374 raise KeyboardInterrupt
375 raise KeyboardInterrupt
375 # XXX: We need to make really sure we
376 # XXX: We need to make really sure we
376 # get back to a prompt.
377 # get back to a prompt.
377 elif self._input_state == 'subprocess' and (
378 elif self._input_state == 'subprocess' and (
378 ( event.KeyCode<256 and
379 ( event.KeyCode<256 and
379 not event.ControlDown() )
380 not event.ControlDown() )
380 or
381 or
381 ( event.KeyCode in (ord('d'), ord('D')) and
382 ( event.KeyCode in (ord('d'), ord('D')) and
382 event.ControlDown())):
383 event.ControlDown())):
383 # We are running a process, we redirect keys.
384 # We are running a process, we redirect keys.
384 ConsoleWidget._on_key_down(self, event, skip=skip)
385 ConsoleWidget._on_key_down(self, event, skip=skip)
385 char = chr(event.KeyCode)
386 char = chr(event.KeyCode)
386 # Deal with some inconsistency in wx keycodes:
387 # Deal with some inconsistency in wx keycodes:
387 if char == '\r':
388 if char == '\r':
388 char = '\n'
389 char = '\n'
389 elif not event.ShiftDown():
390 elif not event.ShiftDown():
390 char = char.lower()
391 char = char.lower()
391 if event.ControlDown() and event.KeyCode in (ord('d'), ord('D')):
392 if event.ControlDown() and event.KeyCode in (ord('d'), ord('D')):
392 char = '\04'
393 char = '\04'
393 self._running_process.process.stdin.write(char)
394 self._running_process.process.stdin.write(char)
394 self._running_process.process.stdin.flush()
395 self._running_process.process.stdin.flush()
395 elif event.KeyCode in (ord('('), 57):
396 elif event.KeyCode in (ord('('), 57):
396 # Calltips
397 # Calltips
397 event.Skip()
398 event.Skip()
398 self.do_calltip()
399 self.do_calltip()
399 elif self.AutoCompActive() and not event.KeyCode == ord('\t'):
400 elif self.AutoCompActive() and not event.KeyCode == ord('\t'):
400 event.Skip()
401 event.Skip()
401 if event.KeyCode in (wx.WXK_BACK, wx.WXK_DELETE):
402 if event.KeyCode in (wx.WXK_BACK, wx.WXK_DELETE):
402 wx.CallAfter(self._popup_completion, create=True)
403 wx.CallAfter(self._popup_completion, create=True)
403 elif not event.KeyCode in (wx.WXK_UP, wx.WXK_DOWN, wx.WXK_LEFT,
404 elif not event.KeyCode in (wx.WXK_UP, wx.WXK_DOWN, wx.WXK_LEFT,
404 wx.WXK_RIGHT, wx.WXK_ESCAPE):
405 wx.WXK_RIGHT, wx.WXK_ESCAPE):
405 wx.CallAfter(self._popup_completion)
406 wx.CallAfter(self._popup_completion)
406 else:
407 else:
407 # Up history
408 # Up history
408 if event.KeyCode == wx.WXK_UP and (
409 if event.KeyCode == wx.WXK_UP and (
409 ( current_line_number == self.current_prompt_line and
410 ( current_line_number == self.current_prompt_line and
410 event.Modifiers in (wx.MOD_NONE, wx.MOD_WIN) )
411 event.Modifiers in (wx.MOD_NONE, wx.MOD_WIN) )
411 or event.ControlDown() ):
412 or event.ControlDown() ):
412 new_buffer = self.get_history_previous(
413 new_buffer = self.get_history_previous(
413 self.input_buffer)
414 self.input_buffer)
414 if new_buffer is not None:
415 if new_buffer is not None:
415 self.input_buffer = new_buffer
416 self.input_buffer = new_buffer
416 if self.GetCurrentLine() > self.current_prompt_line:
417 if self.GetCurrentLine() > self.current_prompt_line:
417 # Go to first line, for seemless history up.
418 # Go to first line, for seemless history up.
418 self.GotoPos(self.current_prompt_pos)
419 self.GotoPos(self.current_prompt_pos)
419 # Down history
420 # Down history
420 elif event.KeyCode == wx.WXK_DOWN and (
421 elif event.KeyCode == wx.WXK_DOWN and (
421 ( current_line_number == self.LineCount -1 and
422 ( current_line_number == self.LineCount -1 and
422 event.Modifiers in (wx.MOD_NONE, wx.MOD_WIN) )
423 event.Modifiers in (wx.MOD_NONE, wx.MOD_WIN) )
423 or event.ControlDown() ):
424 or event.ControlDown() ):
424 new_buffer = self.get_history_next()
425 new_buffer = self.get_history_next()
425 if new_buffer is not None:
426 if new_buffer is not None:
426 self.input_buffer = new_buffer
427 self.input_buffer = new_buffer
427 # Tab-completion
428 # Tab-completion
428 elif event.KeyCode == ord('\t'):
429 elif event.KeyCode == ord('\t'):
429 last_line = self.input_buffer.split('\n')[-1]
430 last_line = self.input_buffer.split('\n')[-1]
430 if not re.match(r'^\s*$', last_line):
431 if not re.match(r'^\s*$', last_line):
431 self.complete_current_input()
432 self.complete_current_input()
432 if self.AutoCompActive():
433 if self.AutoCompActive():
433 wx.CallAfter(self._popup_completion, create=True)
434 wx.CallAfter(self._popup_completion, create=True)
434 else:
435 else:
435 event.Skip()
436 event.Skip()
436 else:
437 else:
437 ConsoleWidget._on_key_down(self, event, skip=skip)
438 ConsoleWidget._on_key_down(self, event, skip=skip)
438
439
439
440
440 def _on_key_up(self, event, skip=True):
441 def _on_key_up(self, event, skip=True):
441 """ Called when any key is released.
442 """ Called when any key is released.
442 """
443 """
443 if event.KeyCode in (59, ord('.')):
444 if event.KeyCode in (59, ord('.')):
444 # Intercepting '.'
445 # Intercepting '.'
445 event.Skip()
446 event.Skip()
446 wx.CallAfter(self._popup_completion, create=True)
447 wx.CallAfter(self._popup_completion, create=True)
447 else:
448 else:
448 ConsoleWidget._on_key_up(self, event, skip=skip)
449 ConsoleWidget._on_key_up(self, event, skip=skip)
449
450
450
451
451 def _on_enter(self):
452 def _on_enter(self):
452 """ Called on return key down, in readline input_state.
453 """ Called on return key down, in readline input_state.
453 """
454 """
454 if self.debug:
455 if self.debug:
455 print >>sys.__stdout__, repr(self.input_buffer)
456 print >>sys.__stdout__, repr(self.input_buffer)
456 PrefilterFrontEnd._on_enter(self)
457 PrefilterFrontEnd._on_enter(self)
457
458
458
459
459 #--------------------------------------------------------------------------
460 #--------------------------------------------------------------------------
460 # EditWindow API
461 # EditWindow API
461 #--------------------------------------------------------------------------
462 #--------------------------------------------------------------------------
462
463
463 def OnUpdateUI(self, event):
464 def OnUpdateUI(self, event):
464 """ Override the OnUpdateUI of the EditWindow class, to prevent
465 """ Override the OnUpdateUI of the EditWindow class, to prevent
465 syntax highlighting both for faster redraw, and for more
466 syntax highlighting both for faster redraw, and for more
466 consistent look and feel.
467 consistent look and feel.
467 """
468 """
468 if not self._input_state == 'readline':
469 if not self._input_state == 'readline':
469 ConsoleWidget.OnUpdateUI(self, event)
470 ConsoleWidget.OnUpdateUI(self, event)
470
471
471 #--------------------------------------------------------------------------
472 #--------------------------------------------------------------------------
472 # Private API
473 # Private API
473 #--------------------------------------------------------------------------
474 #--------------------------------------------------------------------------
474
475
475 def _end_system_call(self):
476 def _end_system_call(self):
476 """ Called at the end of a system call.
477 """ Called at the end of a system call.
477 """
478 """
478 self._input_state = 'buffering'
479 self._input_state = 'buffering'
479 self._running_process = False
480 self._running_process = False
480
481
481
482
482 def _buffer_flush(self, event):
483 def _buffer_flush(self, event):
483 """ Called by the timer to flush the write buffer.
484 """ Called by the timer to flush the write buffer.
484
485
485 This is always called in the mainloop, by the wx timer.
486 This is always called in the mainloop, by the wx timer.
486 """
487 """
487 self._out_buffer_lock.acquire()
488 self._out_buffer_lock.acquire()
488 _out_buffer = self._out_buffer
489 _out_buffer = self._out_buffer
489 self._out_buffer = []
490 self._out_buffer = []
490 self._out_buffer_lock.release()
491 self._out_buffer_lock.release()
491 self.write(''.join(_out_buffer), refresh=False)
492 self.write(''.join(_out_buffer), refresh=False)
492
493
493
494
494 def _colorize_input_buffer(self):
495 def _colorize_input_buffer(self):
495 """ Keep the input buffer lines at a bright color.
496 """ Keep the input buffer lines at a bright color.
496 """
497 """
497 if not self._input_state in ('readline', 'raw_input'):
498 if not self._input_state in ('readline', 'raw_input'):
498 return
499 return
499 end_line = self.GetCurrentLine()
500 end_line = self.GetCurrentLine()
500 if not sys.platform == 'win32':
501 if not sys.platform == 'win32':
501 end_line += 1
502 end_line += 1
502 for i in range(self.current_prompt_line, end_line):
503 for i in range(self.current_prompt_line, end_line):
503 if i in self._markers:
504 if i in self._markers:
504 self.MarkerDeleteHandle(self._markers[i])
505 self.MarkerDeleteHandle(self._markers[i])
505 self._markers[i] = self.MarkerAdd(i, _INPUT_MARKER)
506 self._markers[i] = self.MarkerAdd(i, _INPUT_MARKER)
506
507
507
508
508 if __name__ == '__main__':
509 if __name__ == '__main__':
509 class MainWindow(wx.Frame):
510 class MainWindow(wx.Frame):
510 def __init__(self, parent, id, title):
511 def __init__(self, parent, id, title):
511 wx.Frame.__init__(self, parent, id, title, size=(300,250))
512 wx.Frame.__init__(self, parent, id, title, size=(300,250))
512 self._sizer = wx.BoxSizer(wx.VERTICAL)
513 self._sizer = wx.BoxSizer(wx.VERTICAL)
513 self.shell = WxController(self)
514 self.shell = WxController(self)
514 self._sizer.Add(self.shell, 1, wx.EXPAND)
515 self._sizer.Add(self.shell, 1, wx.EXPAND)
515 self.SetSizer(self._sizer)
516 self.SetSizer(self._sizer)
516 self.SetAutoLayout(1)
517 self.SetAutoLayout(1)
517 self.Show(True)
518 self.Show(True)
518
519
519 app = wx.PySimpleApp()
520 app = wx.PySimpleApp()
520 frame = MainWindow(None, wx.ID_ANY, 'Ipython')
521 frame = MainWindow(None, wx.ID_ANY, 'Ipython')
521 frame.shell.SetFocus()
522 frame.shell.SetFocus()
522 frame.SetSize((680, 460))
523 frame.SetSize((680, 460))
523 self = frame.shell
524 self = frame.shell
524
525
525 app.MainLoop()
526 app.MainLoop()
526
527
General Comments 0
You need to be logged in to leave comments. Login now