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