##// END OF EJS Templates
Calltips are now working.
Gael Varoquaux -
Show More
@@ -1,406 +1,405 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
26
27 import re
27 import re
28
28
29 # FIXME: Need to provide an API for non user-generated display on the
29 # FIXME: Need to provide an API for non user-generated display on the
30 # screen: this should not be editable by the user.
30 # screen: this should not be editable by the user.
31
31
32 if wx.Platform == '__WXMSW__':
32 if wx.Platform == '__WXMSW__':
33 _DEFAULT_SIZE = 80
33 _DEFAULT_SIZE = 80
34 else:
34 else:
35 _DEFAULT_SIZE = 10
35 _DEFAULT_SIZE = 10
36
36
37 _DEFAULT_STYLE = {
37 _DEFAULT_STYLE = {
38 'stdout' : 'fore:#0000FF',
38 'stdout' : 'fore:#0000FF',
39 'stderr' : 'fore:#007f00',
39 'stderr' : 'fore:#007f00',
40 'trace' : 'fore:#FF0000',
40 'trace' : 'fore:#FF0000',
41
41
42 'default' : 'size:%d' % _DEFAULT_SIZE,
42 'default' : 'size:%d' % _DEFAULT_SIZE,
43 'bracegood' : 'fore:#FFFFFF,back:#0000FF,bold',
43 'bracegood' : 'fore:#FFFFFF,back:#0000FF,bold',
44 'bracebad' : 'fore:#000000,back:#FF0000,bold',
44 'bracebad' : 'fore:#000000,back:#FF0000,bold',
45
45
46 # properties for the various Python lexer styles
46 # properties for the various Python lexer styles
47 'comment' : 'fore:#007F00',
47 'comment' : 'fore:#007F00',
48 'number' : 'fore:#007F7F',
48 'number' : 'fore:#007F7F',
49 'string' : 'fore:#7F007F,italic',
49 'string' : 'fore:#7F007F,italic',
50 'char' : 'fore:#7F007F,italic',
50 'char' : 'fore:#7F007F,italic',
51 'keyword' : 'fore:#00007F,bold',
51 'keyword' : 'fore:#00007F,bold',
52 'triple' : 'fore:#7F0000',
52 'triple' : 'fore:#7F0000',
53 'tripledouble' : 'fore:#7F0000',
53 'tripledouble' : 'fore:#7F0000',
54 'class' : 'fore:#0000FF,bold,underline',
54 'class' : 'fore:#0000FF,bold,underline',
55 'def' : 'fore:#007F7F,bold',
55 'def' : 'fore:#007F7F,bold',
56 'operator' : 'bold'
56 'operator' : 'bold'
57 }
57 }
58
58
59 # new style numbers
59 # new style numbers
60 _STDOUT_STYLE = 15
60 _STDOUT_STYLE = 15
61 _STDERR_STYLE = 16
61 _STDERR_STYLE = 16
62 _TRACE_STYLE = 17
62 _TRACE_STYLE = 17
63
63
64
64
65 # system colors
65 # system colors
66 SYS_COLOUR_BACKGROUND = wx.SystemSettings.GetColour(wx.SYS_COLOUR_BACKGROUND)
66 SYS_COLOUR_BACKGROUND = wx.SystemSettings.GetColour(wx.SYS_COLOUR_BACKGROUND)
67
67
68 #-------------------------------------------------------------------------------
68 #-------------------------------------------------------------------------------
69 # The console widget class
69 # The console widget class
70 #-------------------------------------------------------------------------------
70 #-------------------------------------------------------------------------------
71 class ConsoleWidget(editwindow.EditWindow):
71 class ConsoleWidget(editwindow.EditWindow):
72 """ Specialized styled text control view for console-like workflow.
72 """ Specialized styled text control view for console-like workflow.
73
73
74 This widget is mainly interested in dealing with the prompt and
74 This widget is mainly interested in dealing with the prompt and
75 keeping the cursor inside the editing line.
75 keeping the cursor inside the editing line.
76 """
76 """
77
77
78 title = 'Console'
78 title = 'Console'
79
79
80 style = _DEFAULT_STYLE.copy()
80 style = _DEFAULT_STYLE.copy()
81
81
82 # Translation table from ANSI escape sequences to color. Override
82 # Translation table from ANSI escape sequences to color. Override
83 # this to specify your colors.
83 # this to specify your colors.
84 ANSI_STYLES = {'0;30': [0, 'BLACK'], '0;31': [1, 'RED'],
84 ANSI_STYLES = {'0;30': [0, 'BLACK'], '0;31': [1, 'RED'],
85 '0;32': [2, 'GREEN'], '0;33': [3, 'BROWN'],
85 '0;32': [2, 'GREEN'], '0;33': [3, 'BROWN'],
86 '0;34': [4, 'BLUE'], '0;35': [5, 'PURPLE'],
86 '0;34': [4, 'BLUE'], '0;35': [5, 'PURPLE'],
87 '0;36': [6, 'CYAN'], '0;37': [7, 'LIGHT GREY'],
87 '0;36': [6, 'CYAN'], '0;37': [7, 'LIGHT GREY'],
88 '1;30': [8, 'DARK GREY'], '1;31': [9, 'RED'],
88 '1;30': [8, 'DARK GREY'], '1;31': [9, 'RED'],
89 '1;32': [10, 'SEA GREEN'], '1;33': [11, 'YELLOW'],
89 '1;32': [10, 'SEA GREEN'], '1;33': [11, 'YELLOW'],
90 '1;34': [12, 'LIGHT BLUE'], '1;35':
90 '1;34': [12, 'LIGHT BLUE'], '1;35':
91 [13, 'MEDIUM VIOLET RED'],
91 [13, 'MEDIUM VIOLET RED'],
92 '1;36': [14, 'LIGHT STEEL BLUE'], '1;37': [15, 'YELLOW']}
92 '1;36': [14, 'LIGHT STEEL BLUE'], '1;37': [15, 'YELLOW']}
93
93
94 # The color of the carret (call _apply_style() after setting)
94 # The color of the carret (call _apply_style() after setting)
95 carret_color = 'BLACK'
95 carret_color = 'BLACK'
96
96
97
97
98 #--------------------------------------------------------------------------
98 #--------------------------------------------------------------------------
99 # Public API
99 # Public API
100 #--------------------------------------------------------------------------
100 #--------------------------------------------------------------------------
101
101
102 def __init__(self, parent, id=wx.ID_ANY, pos=wx.DefaultPosition,
102 def __init__(self, parent, id=wx.ID_ANY, pos=wx.DefaultPosition,
103 size=wx.DefaultSize, style=0,
103 size=wx.DefaultSize, style=0,
104 autocomplete_mode='popup'):
104 autocomplete_mode='popup'):
105 """ Autocomplete_mode: Can be 'popup' or 'text'
105 """ Autocomplete_mode: Can be 'popup' or 'text'
106 'text' show autocompletion in the text buffer
106 'text' show autocompletion in the text buffer
107 'popup' show it with a dropdown popup
107 'popup' show it with a dropdown popup
108 """
108 """
109 editwindow.EditWindow.__init__(self, parent, id, pos, size, style)
109 editwindow.EditWindow.__init__(self, parent, id, pos, size, style)
110 self.configure_scintilla()
110 self.configure_scintilla()
111
111
112 # FIXME: we need to retrieve this from the interpreter.
112 # FIXME: we need to retrieve this from the interpreter.
113 self.prompt = \
113 self.prompt = \
114 '\n\x01\x1b[0;34m\x02In [\x01\x1b[1;34m\x02%i\x01\x1b[0;34m\x02]: \x01\x1b[0m\x02'
114 '\n\x01\x1b[0;34m\x02In [\x01\x1b[1;34m\x02%i\x01\x1b[0;34m\x02]: \x01\x1b[0m\x02'
115 self.new_prompt(self.prompt % 1)
115 self.new_prompt(self.prompt % 1)
116
116
117 self.autocomplete_mode = autocomplete_mode
117 self.autocomplete_mode = autocomplete_mode
118
118
119 self.Bind(wx.EVT_KEY_DOWN, self._on_key_down)
119 self.Bind(wx.EVT_KEY_DOWN, self._on_key_down)
120 self.Bind(wx.EVT_KEY_UP, self._on_key_up)
120 self.Bind(wx.EVT_KEY_UP, self._on_key_up)
121
121
122
122
123 def configure_scintilla(self):
123 def configure_scintilla(self):
124 # Ctrl"+" or Ctrl "-" can be used to zoomin/zoomout the text inside
124 # Ctrl"+" or Ctrl "-" can be used to zoomin/zoomout the text inside
125 # the widget
125 # the widget
126 self.CmdKeyAssign(ord('+'), stc.STC_SCMOD_CTRL, stc.STC_CMD_ZOOMIN)
126 self.CmdKeyAssign(ord('+'), stc.STC_SCMOD_CTRL, stc.STC_CMD_ZOOMIN)
127 self.CmdKeyAssign(ord('-'), stc.STC_SCMOD_CTRL, stc.STC_CMD_ZOOMOUT)
127 self.CmdKeyAssign(ord('-'), stc.STC_SCMOD_CTRL, stc.STC_CMD_ZOOMOUT)
128 # Also allow Ctrl Shift "=" for poor non US keyboard users.
128 # Also allow Ctrl Shift "=" for poor non US keyboard users.
129 self.CmdKeyAssign(ord('='), stc.STC_SCMOD_CTRL|stc.STC_SCMOD_SHIFT,
129 self.CmdKeyAssign(ord('='), stc.STC_SCMOD_CTRL|stc.STC_SCMOD_SHIFT,
130 stc.STC_CMD_ZOOMIN)
130 stc.STC_CMD_ZOOMIN)
131
131
132 #self.CmdKeyAssign(stc.STC_KEY_PRIOR, stc.STC_SCMOD_SHIFT,
132 #self.CmdKeyAssign(stc.STC_KEY_PRIOR, stc.STC_SCMOD_SHIFT,
133 # stc.STC_CMD_PAGEUP)
133 # stc.STC_CMD_PAGEUP)
134
134
135 #self.CmdKeyAssign(stc.STC_KEY_NEXT, stc.STC_SCMOD_SHIFT,
135 #self.CmdKeyAssign(stc.STC_KEY_NEXT, stc.STC_SCMOD_SHIFT,
136 # stc.STC_CMD_PAGEDOWN)
136 # stc.STC_CMD_PAGEDOWN)
137
137
138 # Keys: we need to clear some of the keys the that don't play
138 # Keys: we need to clear some of the keys the that don't play
139 # well with a console.
139 # well with a console.
140 self.CmdKeyClear(ord('D'), stc.STC_SCMOD_CTRL)
140 self.CmdKeyClear(ord('D'), stc.STC_SCMOD_CTRL)
141 self.CmdKeyClear(ord('L'), stc.STC_SCMOD_CTRL)
141 self.CmdKeyClear(ord('L'), stc.STC_SCMOD_CTRL)
142 self.CmdKeyClear(ord('T'), stc.STC_SCMOD_CTRL)
142 self.CmdKeyClear(ord('T'), stc.STC_SCMOD_CTRL)
143
143
144
144
145 self.SetEOLMode(stc.STC_EOL_CRLF)
145 self.SetEOLMode(stc.STC_EOL_CRLF)
146 self.SetWrapMode(stc.STC_WRAP_CHAR)
146 self.SetWrapMode(stc.STC_WRAP_CHAR)
147 self.SetWrapMode(stc.STC_WRAP_WORD)
147 self.SetWrapMode(stc.STC_WRAP_WORD)
148 self.SetBufferedDraw(True)
148 self.SetBufferedDraw(True)
149 self.SetUseAntiAliasing(True)
149 self.SetUseAntiAliasing(True)
150 self.SetLayoutCache(stc.STC_CACHE_PAGE)
150 self.SetLayoutCache(stc.STC_CACHE_PAGE)
151 self.SetUndoCollection(False)
151 self.SetUndoCollection(False)
152 self.SetUseTabs(True)
152 self.SetUseTabs(True)
153 self.SetIndent(4)
153 self.SetIndent(4)
154 self.SetTabWidth(4)
154 self.SetTabWidth(4)
155
155
156 self.EnsureCaretVisible()
156 self.EnsureCaretVisible()
157 # Tell autocompletion to choose automaticaly out of a single
157 # Tell autocompletion to choose automaticaly out of a single
158 # choice list
158 # choice list
159 self.AutoCompSetChooseSingle(True)
159 self.AutoCompSetChooseSingle(True)
160 self.AutoCompSetMaxHeight(10)
160 self.AutoCompSetMaxHeight(10)
161
161
162 self.SetMargins(3, 3) #text is moved away from border with 3px
162 self.SetMargins(3, 3) #text is moved away from border with 3px
163 # Suppressing Scintilla margins
163 # Suppressing Scintilla margins
164 self.SetMarginWidth(0, 0)
164 self.SetMarginWidth(0, 0)
165 self.SetMarginWidth(1, 0)
165 self.SetMarginWidth(1, 0)
166 self.SetMarginWidth(2, 0)
166 self.SetMarginWidth(2, 0)
167
167
168 self._apply_style()
168 self._apply_style()
169
169
170 # Xterm escape sequences
170 # Xterm escape sequences
171 self.color_pat = re.compile('\x01?\x1b\[(.*?)m\x02?')
171 self.color_pat = re.compile('\x01?\x1b\[(.*?)m\x02?')
172 self.title_pat = re.compile('\x1b]0;(.*?)\x07')
172 self.title_pat = re.compile('\x1b]0;(.*?)\x07')
173
173
174 #self.SetEdgeMode(stc.STC_EDGE_LINE)
174 #self.SetEdgeMode(stc.STC_EDGE_LINE)
175 #self.SetEdgeColumn(80)
175 #self.SetEdgeColumn(80)
176
176
177 # styles
177 # styles
178 p = self.style
178 p = self.style
179 self.StyleSetSpec(stc.STC_STYLE_DEFAULT, p['default'])
179 self.StyleSetSpec(stc.STC_STYLE_DEFAULT, p['default'])
180 self.StyleClearAll()
180 self.StyleClearAll()
181 self.StyleSetSpec(_STDOUT_STYLE, p['stdout'])
181 self.StyleSetSpec(_STDOUT_STYLE, p['stdout'])
182 self.StyleSetSpec(_STDERR_STYLE, p['stderr'])
182 self.StyleSetSpec(_STDERR_STYLE, p['stderr'])
183 self.StyleSetSpec(_TRACE_STYLE, p['trace'])
183 self.StyleSetSpec(_TRACE_STYLE, p['trace'])
184
184
185 self.StyleSetSpec(stc.STC_STYLE_BRACELIGHT, p['bracegood'])
185 self.StyleSetSpec(stc.STC_STYLE_BRACELIGHT, p['bracegood'])
186 self.StyleSetSpec(stc.STC_STYLE_BRACEBAD, p['bracebad'])
186 self.StyleSetSpec(stc.STC_STYLE_BRACEBAD, p['bracebad'])
187 self.StyleSetSpec(stc.STC_P_COMMENTLINE, p['comment'])
187 self.StyleSetSpec(stc.STC_P_COMMENTLINE, p['comment'])
188 self.StyleSetSpec(stc.STC_P_NUMBER, p['number'])
188 self.StyleSetSpec(stc.STC_P_NUMBER, p['number'])
189 self.StyleSetSpec(stc.STC_P_STRING, p['string'])
189 self.StyleSetSpec(stc.STC_P_STRING, p['string'])
190 self.StyleSetSpec(stc.STC_P_CHARACTER, p['char'])
190 self.StyleSetSpec(stc.STC_P_CHARACTER, p['char'])
191 self.StyleSetSpec(stc.STC_P_WORD, p['keyword'])
191 self.StyleSetSpec(stc.STC_P_WORD, p['keyword'])
192 self.StyleSetSpec(stc.STC_P_WORD2, p['keyword'])
192 self.StyleSetSpec(stc.STC_P_WORD2, p['keyword'])
193 self.StyleSetSpec(stc.STC_P_TRIPLE, p['triple'])
193 self.StyleSetSpec(stc.STC_P_TRIPLE, p['triple'])
194 self.StyleSetSpec(stc.STC_P_TRIPLEDOUBLE, p['tripledouble'])
194 self.StyleSetSpec(stc.STC_P_TRIPLEDOUBLE, p['tripledouble'])
195 self.StyleSetSpec(stc.STC_P_CLASSNAME, p['class'])
195 self.StyleSetSpec(stc.STC_P_CLASSNAME, p['class'])
196 self.StyleSetSpec(stc.STC_P_DEFNAME, p['def'])
196 self.StyleSetSpec(stc.STC_P_DEFNAME, p['def'])
197 self.StyleSetSpec(stc.STC_P_OPERATOR, p['operator'])
197 self.StyleSetSpec(stc.STC_P_OPERATOR, p['operator'])
198 self.StyleSetSpec(stc.STC_P_COMMENTBLOCK, p['comment'])
198 self.StyleSetSpec(stc.STC_P_COMMENTBLOCK, p['comment'])
199
199
200
200
201 def write(self, text):
201 def write(self, text):
202 """ Write given text to buffer, while translating the ansi escape
202 """ Write given text to buffer, while translating the ansi escape
203 sequences.
203 sequences.
204 """
204 """
205 title = self.title_pat.split(text)
205 title = self.title_pat.split(text)
206 if len(title)>0:
206 if len(title)>0:
207 self.title = title[-1]
207 self.title = title[-1]
208
208
209 text = self.title_pat.sub('', text)
209 text = self.title_pat.sub('', text)
210 segments = self.color_pat.split(text)
210 segments = self.color_pat.split(text)
211 segment = segments.pop(0)
211 segment = segments.pop(0)
212 self.StartStyling(self.GetLength(), 0xFF)
212 self.StartStyling(self.GetLength(), 0xFF)
213 self.AppendText(segment)
213 self.AppendText(segment)
214
214
215 if segments:
215 if segments:
216 ansi_tags = self.color_pat.findall(text)
216 ansi_tags = self.color_pat.findall(text)
217
217
218 for tag in ansi_tags:
218 for tag in ansi_tags:
219 i = segments.index(tag)
219 i = segments.index(tag)
220 self.StartStyling(self.GetLength(), 0xFF)
220 self.StartStyling(self.GetLength(), 0xFF)
221 self.AppendText(segments[i+1])
221 self.AppendText(segments[i+1])
222
222
223 if tag != '0':
223 if tag != '0':
224 self.SetStyling(len(segments[i+1]),
224 self.SetStyling(len(segments[i+1]),
225 self.ANSI_STYLES[tag][0])
225 self.ANSI_STYLES[tag][0])
226
226
227 segments.pop(i)
227 segments.pop(i)
228
228
229 self.GotoPos(self.GetLength())
229 self.GotoPos(self.GetLength())
230
230
231
231
232 def new_prompt(self, prompt):
232 def new_prompt(self, prompt):
233 """ Prints a prompt at start of line, and move the start of the
233 """ Prints a prompt at start of line, and move the start of the
234 current block there.
234 current block there.
235
235
236 The prompt can be give with ascii escape sequences.
236 The prompt can be give with ascii escape sequences.
237 """
237 """
238 self.write(prompt)
238 self.write(prompt)
239 # now we update our cursor giving end of prompt
239 # now we update our cursor giving end of prompt
240 self.current_prompt_pos = self.GetLength()
240 self.current_prompt_pos = self.GetLength()
241 self.current_prompt_line = self.GetCurrentLine()
241 self.current_prompt_line = self.GetCurrentLine()
242 wx.Yield()
242 wx.Yield()
243 self.EnsureCaretVisible()
243 self.EnsureCaretVisible()
244
244
245
245
246 def replace_current_edit_buffer(self, text):
246 def replace_current_edit_buffer(self, text):
247 """ Replace currently entered command line with given text.
247 """ Replace currently entered command line with given text.
248 """
248 """
249 self.SetSelection(self.current_prompt_pos, self.GetLength())
249 self.SetSelection(self.current_prompt_pos, self.GetLength())
250 self.ReplaceSelection(text)
250 self.ReplaceSelection(text)
251 self.GotoPos(self.GetLength())
251 self.GotoPos(self.GetLength())
252
252
253
253
254 def get_current_edit_buffer(self):
254 def get_current_edit_buffer(self):
255 """ Returns the text in current edit buffer.
255 """ Returns the text in current edit buffer.
256 """
256 """
257 return self.GetTextRange(self.current_prompt_pos,
257 return self.GetTextRange(self.current_prompt_pos,
258 self.GetLength())
258 self.GetLength())
259
259
260
260
261 #--------------------------------------------------------------------------
261 #--------------------------------------------------------------------------
262 # Private API
262 # Private API
263 #--------------------------------------------------------------------------
263 #--------------------------------------------------------------------------
264
264
265 def _apply_style(self):
265 def _apply_style(self):
266 """ Applies the colors for the different text elements and the
266 """ Applies the colors for the different text elements and the
267 carret.
267 carret.
268 """
268 """
269 self.SetCaretForeground(self.carret_color)
269 self.SetCaretForeground(self.carret_color)
270
270
271 #self.StyleClearAll()
271 #self.StyleClearAll()
272 self.StyleSetSpec(stc.STC_STYLE_BRACELIGHT,
272 self.StyleSetSpec(stc.STC_STYLE_BRACELIGHT,
273 "fore:#FF0000,back:#0000FF,bold")
273 "fore:#FF0000,back:#0000FF,bold")
274 self.StyleSetSpec(stc.STC_STYLE_BRACEBAD,
274 self.StyleSetSpec(stc.STC_STYLE_BRACEBAD,
275 "fore:#000000,back:#FF0000,bold")
275 "fore:#000000,back:#FF0000,bold")
276
276
277 for style in self.ANSI_STYLES.values():
277 for style in self.ANSI_STYLES.values():
278 self.StyleSetSpec(style[0], "bold,fore:%s" % style[1])
278 self.StyleSetSpec(style[0], "bold,fore:%s" % style[1])
279
279
280
280
281 def write_completion(self, possibilities, mode=None):
281 def write_completion(self, possibilities, mode=None):
282 if mode=='text' or self.autocomplete_mode == 'text':
282 if mode=='text' or self.autocomplete_mode == 'text':
283 max_len = len(max(possibilities, key=len))
283 max_len = len(max(possibilities, key=len))
284 current_buffer = self.get_current_edit_buffer()
284 current_buffer = self.get_current_edit_buffer()
285
285
286 self.write('\n')
286 self.write('\n')
287 for symbol in possibilities:
287 for symbol in possibilities:
288 self.write(symbol.ljust(max_len))
288 self.write(symbol.ljust(max_len))
289 self.new_prompt(self.prompt % (self.last_result['number'] + 1))
289 self.new_prompt(self.prompt % (self.last_result['number'] + 1))
290 self.replace_current_edit_buffer(current_buffer)
290 self.replace_current_edit_buffer(current_buffer)
291 else:
291 else:
292 #possibilities.sort() # Python sorts are case sensitive
293 self.AutoCompSetIgnoreCase(False)
292 self.AutoCompSetIgnoreCase(False)
294 self.AutoCompSetAutoHide(False)
293 self.AutoCompSetAutoHide(False)
295 #let compute the length ot text)last word
294 # compute the length ot the last word
296 splitter = [' ', '(', '[', '{']
295 separators = [' ', '(', '[', '{', '\n', '\t', '.']
297 last_word = self.get_current_edit_buffer()
296 symbol = self.get_current_edit_buffer()
298 for breaker in splitter:
297 for separator in separators:
299 last_word = last_word.split(breaker)[-1]
298 symbol = symbol.split(separator)[-1]
300 self.AutoCompSetMaxHeight(len(possibilities))
299 self.AutoCompSetMaxHeight(len(possibilities))
301 self.AutoCompShow(len(last_word), " ".join(possibilities))
300 self.AutoCompShow(len(symbol), " ".join(possibilities))
302
301
303
302
304 def scroll_to_bottom(self):
303 def scroll_to_bottom(self):
305 maxrange = self.GetScrollRange(wx.VERTICAL)
304 maxrange = self.GetScrollRange(wx.VERTICAL)
306 self.ScrollLines(maxrange)
305 self.ScrollLines(maxrange)
307
306
308
307
309 def _on_enter(self):
308 def _on_enter(self):
310 """ Called when the return key is hit.
309 """ Called when the return key is hit.
311 """
310 """
312 pass
311 pass
313
312
314
313
315 def _on_key_down(self, event, skip=True):
314 def _on_key_down(self, event, skip=True):
316 """ Key press callback used for correcting behavior for
315 """ Key press callback used for correcting behavior for
317 console-like interfaces: the cursor is constraint to be after
316 console-like interfaces: the cursor is constraint to be after
318 the last prompt.
317 the last prompt.
319
318
320 Return True if event as been catched.
319 Return True if event as been catched.
321 """
320 """
322 catched = True
321 catched = True
323 # Intercept some specific keys.
322 # Intercept some specific keys.
324 if event.KeyCode == ord('L') and event.ControlDown() :
323 if event.KeyCode == ord('L') and event.ControlDown() :
325 self.scroll_to_bottom()
324 self.scroll_to_bottom()
326 elif event.KeyCode == ord('K') and event.ControlDown() :
325 elif event.KeyCode == ord('K') and event.ControlDown() :
327 self.replace_current_edit_buffer('')
326 self.replace_current_edit_buffer('')
328 elif event.KeyCode == wx.WXK_PAGEUP and event.ShiftDown():
327 elif event.KeyCode == wx.WXK_PAGEUP and event.ShiftDown():
329 self.ScrollPages(-1)
328 self.ScrollPages(-1)
330 elif event.KeyCode == wx.WXK_PAGEDOWN and event.ShiftDown():
329 elif event.KeyCode == wx.WXK_PAGEDOWN and event.ShiftDown():
331 self.ScrollPages(1)
330 self.ScrollPages(1)
332 else:
331 else:
333 catched = False
332 catched = False
334
333
335 if self.AutoCompActive():
334 if self.AutoCompActive():
336 event.Skip()
335 event.Skip()
337 else:
336 else:
338 if event.KeyCode in (13, wx.WXK_NUMPAD_ENTER) and \
337 if event.KeyCode in (13, wx.WXK_NUMPAD_ENTER) and \
339 event.Modifiers in (wx.MOD_NONE, wx.MOD_WIN):
338 event.Modifiers in (wx.MOD_NONE, wx.MOD_WIN):
340 catched = True
339 catched = True
341 self.write('\n')
340 self.write('\n')
342 self._on_enter()
341 self._on_enter()
343
342
344 elif event.KeyCode == wx.WXK_HOME:
343 elif event.KeyCode == wx.WXK_HOME:
345 if event.Modifiers in (wx.MOD_NONE, wx.MOD_WIN):
344 if event.Modifiers in (wx.MOD_NONE, wx.MOD_WIN):
346 self.GotoPos(self.current_prompt_pos)
345 self.GotoPos(self.current_prompt_pos)
347 catched = True
346 catched = True
348
347
349 elif event.Modifiers in (wx.MOD_SHIFT, wx.MOD_WIN) :
348 elif event.Modifiers in (wx.MOD_SHIFT, wx.MOD_WIN) :
350 # FIXME: This behavior is not ideal: if the selection
349 # FIXME: This behavior is not ideal: if the selection
351 # is already started, it will jump.
350 # is already started, it will jump.
352 self.SetSelectionStart(self.current_prompt_pos)
351 self.SetSelectionStart(self.current_prompt_pos)
353 self.SetSelectionEnd(self.GetCurrentPos())
352 self.SetSelectionEnd(self.GetCurrentPos())
354 catched = True
353 catched = True
355
354
356 elif event.KeyCode == wx.WXK_UP:
355 elif event.KeyCode == wx.WXK_UP:
357 if self.GetCurrentLine() > self.current_prompt_line:
356 if self.GetCurrentLine() > self.current_prompt_line:
358 if self.GetCurrentLine() == self.current_prompt_line + 1 \
357 if self.GetCurrentLine() == self.current_prompt_line + 1 \
359 and self.GetColumn(self.GetCurrentPos()) < \
358 and self.GetColumn(self.GetCurrentPos()) < \
360 self.GetColumn(self.current_prompt_pos):
359 self.GetColumn(self.current_prompt_pos):
361 self.GotoPos(self.current_prompt_pos)
360 self.GotoPos(self.current_prompt_pos)
362 else:
361 else:
363 event.Skip()
362 event.Skip()
364 catched = True
363 catched = True
365
364
366 elif event.KeyCode in (wx.WXK_LEFT, wx.WXK_BACK):
365 elif event.KeyCode in (wx.WXK_LEFT, wx.WXK_BACK):
367 if self.GetCurrentPos() > self.current_prompt_pos:
366 if self.GetCurrentPos() > self.current_prompt_pos:
368 event.Skip()
367 event.Skip()
369 catched = True
368 catched = True
370
369
371 if skip and not catched:
370 if skip and not catched:
372 event.Skip()
371 event.Skip()
373
372
374 return catched
373 return catched
375
374
376
375
377 def _on_key_up(self, event, skip=True):
376 def _on_key_up(self, event, skip=True):
378 """ If cursor is outside the editing region, put it back.
377 """ If cursor is outside the editing region, put it back.
379 """
378 """
380 event.Skip()
379 event.Skip()
381 if self.GetCurrentPos() < self.current_prompt_pos:
380 if self.GetCurrentPos() < self.current_prompt_pos:
382 self.GotoPos(self.current_prompt_pos)
381 self.GotoPos(self.current_prompt_pos)
383
382
384
383
385
384
386
385
387 if __name__ == '__main__':
386 if __name__ == '__main__':
388 # Some simple code to test the console widget.
387 # Some simple code to test the console widget.
389 class MainWindow(wx.Frame):
388 class MainWindow(wx.Frame):
390 def __init__(self, parent, id, title):
389 def __init__(self, parent, id, title):
391 wx.Frame.__init__(self, parent, id, title, size=(300,250))
390 wx.Frame.__init__(self, parent, id, title, size=(300,250))
392 self._sizer = wx.BoxSizer(wx.VERTICAL)
391 self._sizer = wx.BoxSizer(wx.VERTICAL)
393 self.console_widget = ConsoleWidget(self)
392 self.console_widget = ConsoleWidget(self)
394 self._sizer.Add(self.console_widget, 1, wx.EXPAND)
393 self._sizer.Add(self.console_widget, 1, wx.EXPAND)
395 self.SetSizer(self._sizer)
394 self.SetSizer(self._sizer)
396 self.SetAutoLayout(1)
395 self.SetAutoLayout(1)
397 self.Show(True)
396 self.Show(True)
398
397
399 app = wx.PySimpleApp()
398 app = wx.PySimpleApp()
400 w = MainWindow(None, wx.ID_ANY, 'ConsoleWidget')
399 w = MainWindow(None, wx.ID_ANY, 'ConsoleWidget')
401 w.SetSize((780, 460))
400 w.SetSize((780, 460))
402 w.Show()
401 w.Show()
403
402
404 app.MainLoop()
403 app.MainLoop()
405
404
406
405
@@ -1,178 +1,201 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.cocoa.tests.test_cocoa_frontend -*-
3 # ipython1.frontend.cocoa.tests.test_cocoa_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 """
8 """
9
9
10 __docformat__ = "restructuredtext en"
10 __docformat__ = "restructuredtext en"
11
11
12 #-------------------------------------------------------------------------------
12 #-------------------------------------------------------------------------------
13 # Copyright (C) 2008 The IPython Development Team
13 # Copyright (C) 2008 The IPython Development Team
14 #
14 #
15 # Distributed under the terms of the BSD License. The full license is in
15 # Distributed under the terms of the BSD License. The full license is in
16 # the file COPYING, distributed as part of this software.
16 # the file COPYING, distributed as part of this software.
17 #-------------------------------------------------------------------------------
17 #-------------------------------------------------------------------------------
18
18
19 #-------------------------------------------------------------------------------
19 #-------------------------------------------------------------------------------
20 # Imports
20 # Imports
21 #-------------------------------------------------------------------------------
21 #-------------------------------------------------------------------------------
22
22
23
23
24 import wx
24 import wx
25 import re
25 import re
26 from wx import stc
26 from wx import stc
27 from console_widget import ConsoleWidget
27 from console_widget import ConsoleWidget
28 import __builtin__
28
29
29 from IPython.frontend.prefilterfrontend import PrefilterFrontEnd
30 from IPython.frontend.prefilterfrontend import PrefilterFrontEnd
30
31
31 #_COMMAND_BG = '#FAFAF1' # Nice green
32 #_COMMAND_BG = '#FAFAF1' # Nice green
32 _RUNNING_BUFFER_BG = '#FDFFBE' # Nice yellow
33 _RUNNING_BUFFER_BG = '#FDFFD3' # Nice yellow
33
34
34 _RUNNING_BUFFER_MARKER = 31
35 _RUNNING_BUFFER_MARKER = 31
35
36
36
37
37 #-------------------------------------------------------------------------------
38 #-------------------------------------------------------------------------------
38 # Classes to implement the Wx frontend
39 # Classes to implement the Wx frontend
39 #-------------------------------------------------------------------------------
40 #-------------------------------------------------------------------------------
40 class IPythonWxController(PrefilterFrontEnd, ConsoleWidget):
41 class IPythonWxController(PrefilterFrontEnd, ConsoleWidget):
41
42
42 output_prompt = \
43 output_prompt = \
43 '\x01\x1b[0;31m\x02Out[\x01\x1b[1;31m\x02%i\x01\x1b[0;31m\x02]: \x01\x1b[0m\x02'
44 '\x01\x1b[0;31m\x02Out[\x01\x1b[1;31m\x02%i\x01\x1b[0;31m\x02]: \x01\x1b[0m\x02'
44
45
45 #--------------------------------------------------------------------------
46 #--------------------------------------------------------------------------
46 # Public API
47 # Public API
47 #--------------------------------------------------------------------------
48 #--------------------------------------------------------------------------
48
49
49 def __init__(self, parent, id=wx.ID_ANY, pos=wx.DefaultPosition,
50 def __init__(self, parent, id=wx.ID_ANY, pos=wx.DefaultPosition,
50 size=wx.DefaultSize, style=wx.CLIP_CHILDREN,
51 size=wx.DefaultSize, style=wx.CLIP_CHILDREN,
51 *args, **kwds):
52 *args, **kwds):
52 """ Create Shell instance.
53 """ Create Shell instance.
53 """
54 """
54 ConsoleWidget.__init__(self, parent, id, pos, size, style)
55 ConsoleWidget.__init__(self, parent, id, pos, size, style)
55 PrefilterFrontEnd.__init__(self)
56 PrefilterFrontEnd.__init__(self)
56
57
57 # Capture Character keys
58 # Capture Character keys
58 self.Bind(wx.EVT_KEY_DOWN, self._on_key_down)
59 self.Bind(wx.EVT_KEY_DOWN, self._on_key_down)
59
60
60 # Marker for running buffer.
61 # Marker for running buffer.
61 self.MarkerDefine(_RUNNING_BUFFER_MARKER, stc.STC_MARK_BACKGROUND,
62 self.MarkerDefine(_RUNNING_BUFFER_MARKER, stc.STC_MARK_BACKGROUND,
62 background=_RUNNING_BUFFER_BG)
63 background=_RUNNING_BUFFER_BG)
63
64
64
65
65
66
66 def do_completion(self, mode=None):
67 def do_completion(self, mode=None):
67 """ Do code completion.
68 """ Do code completion.
68 mode can be 'text', 'popup' or 'none' to use default.
69 mode can be 'text', 'popup' or 'none' to use default.
69 """
70 """
70 line = self.get_current_edit_buffer()
71 line = self.get_current_edit_buffer()
71 completions = self.complete(line)
72 completions = self.complete(line)
72 if len(completions)>0:
73 if len(completions)>0:
73 self.write_completion(completions, mode=mode)
74 self.write_completion(completions, mode=mode)
74
75
75
76
77 def do_calltip(self):
78 # compute the length ot the last word
79 separators = [' ', '(', '[', '{', '\n', '\t']
80 symbol = self.get_current_edit_buffer()
81 for separator in separators:
82 symbol_string = symbol.split(separator)[-1]
83 base_symbol_string = symbol_string.split('.')[0]
84 if base_symbol_string in self.shell.user_ns:
85 symbol = self.shell.user_ns[base_symbol_string]
86 elif base_symbol_string in self.shell.user_global_ns:
87 symbol = self.shell.user_global_ns[base_symbol_string]
88 elif base_symbol_string in __builtin__.__dict__:
89 symbol = __builtin__.__dict__[base_symbol_string]
90 else:
91 return False
92 for name in base_symbol_string.split('.')[1:] + ['__doc__']:
93 symbol = getattr(symbol, name)
94 self.CallTipShow(self.GetCurrentPos(), symbol)
95
76 def update_completion(self):
96 def update_completion(self):
77 line = self.get_current_edit_buffer()
97 line = self.get_current_edit_buffer()
78 if self.AutoCompActive() and not line[-1] == '.':
98 if self.AutoCompActive() and not line[-1] == '.':
79 line = line[:-1]
99 line = line[:-1]
80 completions = self.complete(line)
100 completions = self.complete(line)
81 choose_single = self.AutoCompGetChooseSingle()
101 choose_single = self.AutoCompGetChooseSingle()
82 self.AutoCompSetChooseSingle(False)
102 self.AutoCompSetChooseSingle(False)
83 self.write_completion(completions, mode='popup')
103 self.write_completion(completions, mode='popup')
84 self.AutoCompSetChooseSingle(choose_single)
104 self.AutoCompSetChooseSingle(choose_single)
85
105
86
106
87 def execute(self, python_string, raw_string=None):
107 def execute(self, python_string, raw_string=None):
88 self._cursor = wx.BusyCursor()
108 self._cursor = wx.BusyCursor()
89 if raw_string is None:
109 if raw_string is None:
90 raw_string = python_string
110 raw_string = python_string
91 end_line = self.current_prompt_line \
111 end_line = self.current_prompt_line \
92 + max(1, len(raw_string.split('\n'))-1)
112 + max(1, len(raw_string.split('\n'))-1)
93 for i in range(self.current_prompt_line, end_line):
113 for i in range(self.current_prompt_line, end_line):
94 self.MarkerAdd(i, 31)
114 self.MarkerAdd(i, 31)
95 PrefilterFrontEnd.execute(self, python_string, raw_string=raw_string)
115 PrefilterFrontEnd.execute(self, python_string, raw_string=raw_string)
96
116
97
117
98 def after_execute(self):
118 def after_execute(self):
99 PrefilterFrontEnd.after_execute(self)
119 PrefilterFrontEnd.after_execute(self)
100 if hasattr(self, '_cursor'):
120 if hasattr(self, '_cursor'):
101 del self._cursor
121 del self._cursor
102
122
103 #--------------------------------------------------------------------------
123 #--------------------------------------------------------------------------
104 # Private API
124 # Private API
105 #--------------------------------------------------------------------------
125 #--------------------------------------------------------------------------
106
126
107
127
108 def _on_key_down(self, event, skip=True):
128 def _on_key_down(self, event, skip=True):
109 """ Capture the character events, let the parent
129 """ Capture the character events, let the parent
110 widget handle them, and put our logic afterward.
130 widget handle them, and put our logic afterward.
111 """
131 """
112 current_line_number = self.GetCurrentLine()
132 current_line_number = self.GetCurrentLine()
113 if self.AutoCompActive():
133 if self.AutoCompActive():
114 event.Skip()
134 event.Skip()
115 if event.KeyCode in (wx.WXK_BACK, wx.WXK_DELETE):
135 if event.KeyCode in (wx.WXK_BACK, wx.WXK_DELETE):
116 wx.CallAfter(self.do_completion)
136 wx.CallAfter(self.do_completion)
117 elif not event.KeyCode in (wx.WXK_UP, wx.WXK_DOWN, wx.WXK_LEFT,
137 elif not event.KeyCode in (wx.WXK_UP, wx.WXK_DOWN, wx.WXK_LEFT,
118 wx.WXK_RIGHT):
138 wx.WXK_RIGHT):
119 wx.CallAfter(self.update_completion)
139 wx.CallAfter(self.update_completion)
120 else:
140 else:
121 # Up history
141 # Up history
122 if event.KeyCode == wx.WXK_UP and (
142 if event.KeyCode == wx.WXK_UP and (
123 ( current_line_number == self.current_prompt_line and
143 ( current_line_number == self.current_prompt_line and
124 event.Modifiers in (wx.MOD_NONE, wx.MOD_WIN) )
144 event.Modifiers in (wx.MOD_NONE, wx.MOD_WIN) )
125 or event.ControlDown() ):
145 or event.ControlDown() ):
126 new_buffer = self.get_history_previous(
146 new_buffer = self.get_history_previous(
127 self.get_current_edit_buffer())
147 self.get_current_edit_buffer())
128 if new_buffer is not None:
148 if new_buffer is not None:
129 self.replace_current_edit_buffer(new_buffer)
149 self.replace_current_edit_buffer(new_buffer)
130 if self.GetCurrentLine() > self.current_prompt_line:
150 if self.GetCurrentLine() > self.current_prompt_line:
131 # Go to first line, for seemless history up.
151 # Go to first line, for seemless history up.
132 self.GotoPos(self.current_prompt_pos)
152 self.GotoPos(self.current_prompt_pos)
133 # Down history
153 # Down history
134 elif event.KeyCode == wx.WXK_DOWN and (
154 elif event.KeyCode == wx.WXK_DOWN and (
135 ( current_line_number == self.LineCount -1 and
155 ( current_line_number == self.LineCount -1 and
136 event.Modifiers in (wx.MOD_NONE, wx.MOD_WIN) )
156 event.Modifiers in (wx.MOD_NONE, wx.MOD_WIN) )
137 or event.ControlDown() ):
157 or event.ControlDown() ):
138 new_buffer = self.get_history_next()
158 new_buffer = self.get_history_next()
139 if new_buffer is not None:
159 if new_buffer is not None:
140 self.replace_current_edit_buffer(new_buffer)
160 self.replace_current_edit_buffer(new_buffer)
141 elif event.KeyCode == ord('\t'):
161 elif event.KeyCode == ord('\t'):
142 last_line = self.get_current_edit_buffer().split('\n')[-1]
162 last_line = self.get_current_edit_buffer().split('\n')[-1]
143 if not re.match(r'^\s*$', last_line):
163 if not re.match(r'^\s*$', last_line):
144 self.do_completion(mode='text')
164 self.do_completion(mode='text')
145 else:
165 else:
146 event.Skip()
166 event.Skip()
167 elif event.KeyCode == ord('('):
168 event.Skip()
169 self.do_calltip()
147 else:
170 else:
148 ConsoleWidget._on_key_down(self, event, skip=skip)
171 ConsoleWidget._on_key_down(self, event, skip=skip)
149
172
150
173
151 def _on_key_up(self, event, skip=True):
174 def _on_key_up(self, event, skip=True):
152 if event.KeyCode == 59:
175 if event.KeyCode == 59:
153 # Intercepting '.'
176 # Intercepting '.'
154 event.Skip()
177 event.Skip()
155 #self.do_completion(mode='popup')
178 #self.do_completion(mode='popup')
156 else:
179 else:
157 ConsoleWidget._on_key_up(self, event, skip=skip)
180 ConsoleWidget._on_key_up(self, event, skip=skip)
158
181
159
182
160 if __name__ == '__main__':
183 if __name__ == '__main__':
161 class MainWindow(wx.Frame):
184 class MainWindow(wx.Frame):
162 def __init__(self, parent, id, title):
185 def __init__(self, parent, id, title):
163 wx.Frame.__init__(self, parent, id, title, size=(300,250))
186 wx.Frame.__init__(self, parent, id, title, size=(300,250))
164 self._sizer = wx.BoxSizer(wx.VERTICAL)
187 self._sizer = wx.BoxSizer(wx.VERTICAL)
165 self.shell = IPythonWxController(self)
188 self.shell = IPythonWxController(self)
166 self._sizer.Add(self.shell, 1, wx.EXPAND)
189 self._sizer.Add(self.shell, 1, wx.EXPAND)
167 self.SetSizer(self._sizer)
190 self.SetSizer(self._sizer)
168 self.SetAutoLayout(1)
191 self.SetAutoLayout(1)
169 self.Show(True)
192 self.Show(True)
170
193
171 app = wx.PySimpleApp()
194 app = wx.PySimpleApp()
172 frame = MainWindow(None, wx.ID_ANY, 'Ipython')
195 frame = MainWindow(None, wx.ID_ANY, 'Ipython')
173 frame.shell.SetFocus()
196 frame.shell.SetFocus()
174 frame.SetSize((660, 460))
197 frame.SetSize((660, 460))
175 self = frame.shell
198 self = frame.shell
176
199
177 app.MainLoop()
200 app.MainLoop()
178
201
General Comments 0
You need to be logged in to leave comments. Login now