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