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