##// END OF EJS Templates
Clean up history. Tweak multiline input. Improve key handling.
Gael Varoquaux -
Show More
@@ -1,438 +1,370 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
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 #-------------------------------------------------------------------------------
66 #-------------------------------------------------------------------------------
67 # The console widget class
67 # The console widget class
68 #-------------------------------------------------------------------------------
68 #-------------------------------------------------------------------------------
69 class ConsoleWidget(editwindow.EditWindow):
69 class ConsoleWidget(editwindow.EditWindow):
70 """ Specialized styled text control view for console-like workflow.
70 """ Specialized styled text control view for console-like workflow.
71
71
72 This widget is mainly interested in dealing with the prompt and
72 This widget is mainly interested in dealing with the prompt and
73 keeping the cursor inside the editing line.
73 keeping the cursor inside the editing line.
74 """
74 """
75
75
76 style = _DEFAULT_STYLE.copy()
76 style = _DEFAULT_STYLE.copy()
77
77
78 # Translation table from ANSI escape sequences to color. Override
78 # Translation table from ANSI escape sequences to color. Override
79 # this to specify your colors.
79 # this to specify your colors.
80 ANSI_STYLES = {'0;30': [0, 'BLACK'], '0;31': [1, 'RED'],
80 ANSI_STYLES = {'0;30': [0, 'BLACK'], '0;31': [1, 'RED'],
81 '0;32': [2, 'GREEN'], '0;33': [3, 'BROWN'],
81 '0;32': [2, 'GREEN'], '0;33': [3, 'BROWN'],
82 '0;34': [4, 'BLUE'], '0;35': [5, 'PURPLE'],
82 '0;34': [4, 'BLUE'], '0;35': [5, 'PURPLE'],
83 '0;36': [6, 'CYAN'], '0;37': [7, 'LIGHT GREY'],
83 '0;36': [6, 'CYAN'], '0;37': [7, 'LIGHT GREY'],
84 '1;30': [8, 'DARK GREY'], '1;31': [9, 'RED'],
84 '1;30': [8, 'DARK GREY'], '1;31': [9, 'RED'],
85 '1;32': [10, 'SEA GREEN'], '1;33': [11, 'YELLOW'],
85 '1;32': [10, 'SEA GREEN'], '1;33': [11, 'YELLOW'],
86 '1;34': [12, 'LIGHT BLUE'], '1;35':
86 '1;34': [12, 'LIGHT BLUE'], '1;35':
87 [13, 'MEDIUM VIOLET RED'],
87 [13, 'MEDIUM VIOLET RED'],
88 '1;36': [14, 'LIGHT STEEL BLUE'], '1;37': [15, 'YELLOW']}
88 '1;36': [14, 'LIGHT STEEL BLUE'], '1;37': [15, 'YELLOW']}
89
89
90 # The color of the carret (call _apply_style() after setting)
90 # The color of the carret (call _apply_style() after setting)
91 carret_color = 'BLACK'
91 carret_color = 'BLACK'
92
92
93
93
94 #--------------------------------------------------------------------------
94 #--------------------------------------------------------------------------
95 # Public API
95 # Public API
96 #--------------------------------------------------------------------------
96 #--------------------------------------------------------------------------
97
97
98 def __init__(self, parent, id=wx.ID_ANY, pos=wx.DefaultPosition,
98 def __init__(self, parent, id=wx.ID_ANY, pos=wx.DefaultPosition,
99 size=wx.DefaultSize, style=0,
99 size=wx.DefaultSize, style=0,
100 autocomplete_mode='IPYTHON'):
100 autocomplete_mode='IPYTHON'):
101 """ Autocomplete_mode: Can be 'IPYTHON' or 'STC'
101 """ Autocomplete_mode: Can be 'IPYTHON' or 'STC'
102 'IPYTHON' show autocompletion the ipython way
102 'IPYTHON' show autocompletion the ipython way
103 'STC" show it scintilla text control way
103 'STC" show it scintilla text control way
104 """
104 """
105 #stc.StyledTextCtrl.__init__(self, parent, id, pos, size, style)
106 editwindow.EditWindow.__init__(self, parent, id, pos, size, style)
105 editwindow.EditWindow.__init__(self, parent, id, pos, size, style)
107 self.configure_scintilla()
106 self.configure_scintilla()
108
107
109 # FIXME: we need to retrieve this from the interpreter.
108 # FIXME: we need to retrieve this from the interpreter.
110 self.prompt = \
109 self.prompt = \
111 '\n\x01\x1b[0;34m\x02In [\x01\x1b[1;34m\x02%i\x01\x1b[0;34m\x02]: \x01\x1b[0m\x02'
110 '\n\x01\x1b[0;34m\x02In [\x01\x1b[1;34m\x02%i\x01\x1b[0;34m\x02]: \x01\x1b[0m\x02'
112 self.new_prompt(self.prompt % 1)
111 self.new_prompt(self.prompt % 1)
113
112
114 self.autocomplete_mode = autocomplete_mode
113 self.autocomplete_mode = autocomplete_mode
115
114
116 self.Bind(wx.EVT_KEY_DOWN, self._onKeypress)
115 self.Bind(wx.EVT_KEY_DOWN, self._on_key_down)
117
116
118
117
119 def configure_scintilla(self):
118 def configure_scintilla(self):
120 # Ctrl"+" or Ctrl "-" can be used to zoomin/zoomout the text inside
119 # Ctrl"+" or Ctrl "-" can be used to zoomin/zoomout the text inside
121 # the widget
120 # the widget
122 self.CmdKeyAssign(ord('+'), stc.STC_SCMOD_CTRL, stc.STC_CMD_ZOOMIN)
121 self.CmdKeyAssign(ord('+'), stc.STC_SCMOD_CTRL, stc.STC_CMD_ZOOMIN)
123 self.CmdKeyAssign(ord('-'), stc.STC_SCMOD_CTRL, stc.STC_CMD_ZOOMOUT)
122 self.CmdKeyAssign(ord('-'), stc.STC_SCMOD_CTRL, stc.STC_CMD_ZOOMOUT)
124 # Also allow Ctrl Shift "=" for poor non US keyboard users.
123 # Also allow Ctrl Shift "=" for poor non US keyboard users.
125 self.CmdKeyAssign(ord('='), stc.STC_SCMOD_CTRL|stc.STC_SCMOD_SHIFT,
124 self.CmdKeyAssign(ord('='), stc.STC_SCMOD_CTRL|stc.STC_SCMOD_SHIFT,
126 stc.STC_CMD_ZOOMIN)
125 stc.STC_CMD_ZOOMIN)
127
126
128 self.SetEOLMode(stc.STC_EOL_CRLF)
127 self.SetEOLMode(stc.STC_EOL_CRLF)
129 self.SetWrapMode(stc.STC_WRAP_CHAR)
128 self.SetWrapMode(stc.STC_WRAP_CHAR)
130 self.SetWrapMode(stc.STC_WRAP_WORD)
129 self.SetWrapMode(stc.STC_WRAP_WORD)
131 self.SetBufferedDraw(True)
130 self.SetBufferedDraw(True)
132 self.SetUseAntiAliasing(True)
131 self.SetUseAntiAliasing(True)
133 self.SetLayoutCache(stc.STC_CACHE_PAGE)
132 self.SetLayoutCache(stc.STC_CACHE_PAGE)
134 self.SetUndoCollection(False)
133 self.SetUndoCollection(False)
135 self.SetUseTabs(True)
134 self.SetUseTabs(True)
136 self.SetIndent(4)
135 self.SetIndent(4)
137 self.SetTabWidth(4)
136 self.SetTabWidth(4)
138
137
139 self.EnsureCaretVisible()
138 self.EnsureCaretVisible()
140
139
141 self.SetMargins(3, 3) #text is moved away from border with 3px
140 self.SetMargins(3, 3) #text is moved away from border with 3px
142 # Suppressing Scintilla margins
141 # Suppressing Scintilla margins
143 self.SetMarginWidth(0, 0)
142 self.SetMarginWidth(0, 0)
144 self.SetMarginWidth(1, 0)
143 self.SetMarginWidth(1, 0)
145 self.SetMarginWidth(2, 0)
144 self.SetMarginWidth(2, 0)
146
145
147 self._apply_style()
146 self._apply_style()
148
147
149 self.indent = 0
150 self.color_pat = re.compile('\x01?\x1b\[(.*?)m\x02?')
148 self.color_pat = re.compile('\x01?\x1b\[(.*?)m\x02?')
151
149
152 #self.SetEdgeMode(stc.STC_EDGE_LINE)
150 #self.SetEdgeMode(stc.STC_EDGE_LINE)
153 #self.SetEdgeColumn(80)
151 #self.SetEdgeColumn(80)
154
152
155 # styles
153 # styles
156 p = self.style
154 p = self.style
157 self.StyleSetSpec(stc.STC_STYLE_DEFAULT, p['default'])
155 self.StyleSetSpec(stc.STC_STYLE_DEFAULT, p['default'])
158 self.StyleClearAll()
156 self.StyleClearAll()
159 self.StyleSetSpec(_STDOUT_STYLE, p['stdout'])
157 self.StyleSetSpec(_STDOUT_STYLE, p['stdout'])
160 self.StyleSetSpec(_STDERR_STYLE, p['stderr'])
158 self.StyleSetSpec(_STDERR_STYLE, p['stderr'])
161 self.StyleSetSpec(_TRACE_STYLE, p['trace'])
159 self.StyleSetSpec(_TRACE_STYLE, p['trace'])
162
160
163 self.StyleSetSpec(stc.STC_STYLE_BRACELIGHT, p['bracegood'])
161 self.StyleSetSpec(stc.STC_STYLE_BRACELIGHT, p['bracegood'])
164 self.StyleSetSpec(stc.STC_STYLE_BRACEBAD, p['bracebad'])
162 self.StyleSetSpec(stc.STC_STYLE_BRACEBAD, p['bracebad'])
165 self.StyleSetSpec(stc.STC_P_COMMENTLINE, p['comment'])
163 self.StyleSetSpec(stc.STC_P_COMMENTLINE, p['comment'])
166 self.StyleSetSpec(stc.STC_P_NUMBER, p['number'])
164 self.StyleSetSpec(stc.STC_P_NUMBER, p['number'])
167 self.StyleSetSpec(stc.STC_P_STRING, p['string'])
165 self.StyleSetSpec(stc.STC_P_STRING, p['string'])
168 self.StyleSetSpec(stc.STC_P_CHARACTER, p['char'])
166 self.StyleSetSpec(stc.STC_P_CHARACTER, p['char'])
169 self.StyleSetSpec(stc.STC_P_WORD, p['keyword'])
167 self.StyleSetSpec(stc.STC_P_WORD, p['keyword'])
170 self.StyleSetSpec(stc.STC_P_TRIPLE, p['triple'])
168 self.StyleSetSpec(stc.STC_P_TRIPLE, p['triple'])
171 self.StyleSetSpec(stc.STC_P_TRIPLEDOUBLE, p['tripledouble'])
169 self.StyleSetSpec(stc.STC_P_TRIPLEDOUBLE, p['tripledouble'])
172 self.StyleSetSpec(stc.STC_P_CLASSNAME, p['class'])
170 self.StyleSetSpec(stc.STC_P_CLASSNAME, p['class'])
173 self.StyleSetSpec(stc.STC_P_DEFNAME, p['def'])
171 self.StyleSetSpec(stc.STC_P_DEFNAME, p['def'])
174 self.StyleSetSpec(stc.STC_P_OPERATOR, p['operator'])
172 self.StyleSetSpec(stc.STC_P_OPERATOR, p['operator'])
175 self.StyleSetSpec(stc.STC_P_COMMENTBLOCK, p['comment'])
173 self.StyleSetSpec(stc.STC_P_COMMENTBLOCK, p['comment'])
176
174
177
175
178
179
180
181 def write(self, text):
176 def write(self, text):
182 """ Write given text to buffer, while translating the ansi escape
177 """ Write given text to buffer, while translating the ansi escape
183 sequences.
178 sequences.
184 """
179 """
185 segments = self.color_pat.split(text)
180 segments = self.color_pat.split(text)
186 segment = segments.pop(0)
181 segment = segments.pop(0)
187 self.StartStyling(self.GetLength(), 0xFF)
182 self.StartStyling(self.GetLength(), 0xFF)
188 self.AppendText(segment)
183 self.AppendText(segment)
189
184
190 if segments:
185 if segments:
191 ansi_tags = self.color_pat.findall(text)
186 ansi_tags = self.color_pat.findall(text)
192
187
193 for tag in ansi_tags:
188 for tag in ansi_tags:
194 i = segments.index(tag)
189 i = segments.index(tag)
195 self.StartStyling(self.GetLength(), 0xFF)
190 self.StartStyling(self.GetLength(), 0xFF)
196 self.AppendText(segments[i+1])
191 self.AppendText(segments[i+1])
197
192
198 if tag != '0':
193 if tag != '0':
199 self.SetStyling(len(segments[i+1]),
194 self.SetStyling(len(segments[i+1]),
200 self.ANSI_STYLES[tag][0])
195 self.ANSI_STYLES[tag][0])
201
196
202 segments.pop(i)
197 segments.pop(i)
203
198
204 self.GotoPos(self.GetLength())
199 self.GotoPos(self.GetLength())
205
200
206
201
207 def new_prompt(self, prompt):
202 def new_prompt(self, prompt):
208 """ Prints a prompt at start of line, and move the start of the
203 """ Prints a prompt at start of line, and move the start of the
209 current block there.
204 current block there.
210
205
211 The prompt can be give with ascii escape sequences.
206 The prompt can be give with ascii escape sequences.
212 """
207 """
213 self.write(prompt)
208 self.write(prompt)
214 # now we update our cursor giving end of prompt
209 # now we update our cursor giving end of prompt
215 self.current_prompt_pos = self.GetLength()
210 self.current_prompt_pos = self.GetLength()
216 self.current_prompt_line = self.GetCurrentLine()
211 self.current_prompt_line = self.GetCurrentLine()
217
212
218 autoindent = self.indent * ' '
219 autoindent = autoindent.replace(' ','\t')
220 self.write(autoindent)
221
222
213
223 def replace_current_edit_buffer(self, text):
214 def replace_current_edit_buffer(self, text):
224 """ Replace currently entered command line with given text.
215 """ Replace currently entered command line with given text.
225 """
216 """
226 self.SetSelection(self.current_prompt_pos, self.GetLength())
217 self.SetSelection(self.current_prompt_pos, self.GetLength())
227 self.ReplaceSelection(text)
218 self.ReplaceSelection(text)
228 self.GotoPos(self.GetLength())
219 self.GotoPos(self.GetLength())
229
220
230
221
231 def get_current_edit_buffer(self):
222 def get_current_edit_buffer(self):
232 """ Returns the text in current edit buffer.
223 """ Returns the text in current edit buffer.
233 """
224 """
234 return self.GetTextRange(self.current_prompt_pos,
225 return self.GetTextRange(self.current_prompt_pos,
235 self.GetLength())
226 self.GetLength())
236
227
237
228
238 #--------------------------------------------------------------------------
229 #--------------------------------------------------------------------------
239 # Private API
230 # Private API
240 #--------------------------------------------------------------------------
231 #--------------------------------------------------------------------------
241
232
242 def _apply_style(self):
233 def _apply_style(self):
243 """ Applies the colors for the different text elements and the
234 """ Applies the colors for the different text elements and the
244 carret.
235 carret.
245 """
236 """
246 # FIXME: We need to do something for the fonts, but this is
247 # clearly not the right option.
248 #we define platform specific fonts
249 # if wx.Platform == '__WXMSW__':
250 # faces = { 'times': 'Times New Roman',
251 # 'mono' : 'Courier New',
252 # 'helv' : 'Arial',
253 # 'other': 'Comic Sans MS',
254 # 'size' : 10,
255 # 'size2': 8,
256 # }
257 # elif wx.Platform == '__WXMAC__':
258 # faces = { 'times': 'Times New Roman',
259 # 'mono' : 'Monaco',
260 # 'helv' : 'Arial',
261 # 'other': 'Comic Sans MS',
262 # 'size' : 10,
263 # 'size2': 8,
264 # }
265 # else:
266 # faces = { 'times': 'Times',
267 # 'mono' : 'Courier',
268 # 'helv' : 'Helvetica',
269 # 'other': 'new century schoolbook',
270 # 'size' : 10,
271 # 'size2': 8,
272 # }
273 # self.StyleSetSpec(stc.STC_STYLE_DEFAULT,
274 # "fore:%s,back:%s,size:%d,face:%s"
275 # % (self.ANSI_STYLES['0;30'][1],
276 # self.background_color,
277 # faces['size'], faces['mono']))
278
279 self.SetCaretForeground(self.carret_color)
237 self.SetCaretForeground(self.carret_color)
280
238
281 self.StyleClearAll()
239 self.StyleClearAll()
282 self.StyleSetSpec(stc.STC_STYLE_BRACELIGHT,
240 self.StyleSetSpec(stc.STC_STYLE_BRACELIGHT,
283 "fore:#FF0000,back:#0000FF,bold")
241 "fore:#FF0000,back:#0000FF,bold")
284 self.StyleSetSpec(stc.STC_STYLE_BRACEBAD,
242 self.StyleSetSpec(stc.STC_STYLE_BRACEBAD,
285 "fore:#000000,back:#FF0000,bold")
243 "fore:#000000,back:#FF0000,bold")
286
244
287 for style in self.ANSI_STYLES.values():
245 for style in self.ANSI_STYLES.values():
288 self.StyleSetSpec(style[0], "bold,fore:%s" % style[1])
246 self.StyleSetSpec(style[0], "bold,fore:%s" % style[1])
289
247
290
248
291 def removeFromTo(self, from_pos, to_pos):
249 def removeFromTo(self, from_pos, to_pos):
292 if from_pos < to_pos:
250 if from_pos < to_pos:
293 self.SetSelection(from_pos, to_pos)
251 self.SetSelection(from_pos, to_pos)
294 self.DeleteBack()
252 self.DeleteBack()
295
253
296
254
297 def selectFromTo(self, from_pos, to_pos):
255 def selectFromTo(self, from_pos, to_pos):
298 self.SetSelectionStart(from_pos)
256 self.SetSelectionStart(from_pos)
299 self.SetSelectionEnd(to_pos)
257 self.SetSelectionEnd(to_pos)
300
258
301
259
302 def writeCompletion(self, possibilities):
260 def writeCompletion(self, possibilities):
303 if self.autocomplete_mode == 'IPYTHON':
261 if self.autocomplete_mode == 'IPYTHON':
304 max_len = len(max(possibilities, key=len))
262 max_len = len(max(possibilities, key=len))
305 max_symbol = ' '*max_len
263 max_symbol = ' '*max_len
306
264
307 #now we check how much symbol we can put on a line...
265 #now we check how much symbol we can put on a line...
308 test_buffer = max_symbol + ' '*4
266 test_buffer = max_symbol + ' '*4
309
267
310 allowed_symbols = 80/len(test_buffer)
268 allowed_symbols = 80/len(test_buffer)
311 if allowed_symbols == 0:
269 if allowed_symbols == 0:
312 allowed_symbols = 1
270 allowed_symbols = 1
313
271
314 pos = 1
272 pos = 1
315 buf = ''
273 buf = ''
316 for symbol in possibilities:
274 for symbol in possibilities:
317 #buf += symbol+'\n'#*spaces)
275 #buf += symbol+'\n'#*spaces)
318 if pos < allowed_symbols:
276 if pos < allowed_symbols:
319 spaces = max_len - len(symbol) + 4
277 spaces = max_len - len(symbol) + 4
320 buf += symbol+' '*spaces
278 buf += symbol+' '*spaces
321 pos += 1
279 pos += 1
322 else:
280 else:
323 buf += symbol+'\n'
281 buf += symbol+'\n'
324 pos = 1
282 pos = 1
325 self.write(buf)
283 self.write(buf)
326 else:
284 else:
327 possibilities.sort() # Python sorts are case sensitive
285 possibilities.sort() # Python sorts are case sensitive
328 self.AutoCompSetIgnoreCase(False)
286 self.AutoCompSetIgnoreCase(False)
329 self.AutoCompSetAutoHide(False)
287 self.AutoCompSetAutoHide(False)
330 #let compute the length ot last word
288 #let compute the length ot last word
331 splitter = [' ', '(', '[', '{']
289 splitter = [' ', '(', '[', '{']
332 last_word = self.get_current_edit_buffer()
290 last_word = self.get_current_edit_buffer()
333 for breaker in splitter:
291 for breaker in splitter:
334 last_word = last_word.split(breaker)[-1]
292 last_word = last_word.split(breaker)[-1]
335 self.AutoCompShow(len(last_word), " ".join(possibilities))
293 self.AutoCompShow(len(last_word), " ".join(possibilities))
336
294
337
295
338 def _onKeypress(self, event, skip=True):
296 def _on_key_down(self, event, skip=True):
339 """ Key press callback used for correcting behavior for
297 """ Key press callback used for correcting behavior for
340 console-like interfaces: the cursor is constraint to be after
298 console-like interfaces: the cursor is constraint to be after
341 the last prompt.
299 the last prompt.
342
300
343 Return True if event as been catched.
301 Return True if event as been catched.
344 """
302 """
345 catched = False
303 catched = False
304 # Intercept annoying entries (eg: ctrl-D, ctrl-L)
305 if event.KeyCode in (68, 76) and event.ControlDown() :
306 skip = False
307 catched = True
308
346 if self.AutoCompActive():
309 if self.AutoCompActive():
347 event.Skip()
310 event.Skip()
348 else:
311 else:
349 if event.KeyCode == wx.WXK_HOME:
312 if event.KeyCode == wx.WXK_HOME:
350 if event.Modifiers in (wx.MOD_NONE, wx.MOD_WIN):
313 if event.Modifiers in (wx.MOD_NONE, wx.MOD_WIN):
351 self.GotoPos(self.current_prompt_pos)
314 self.GotoPos(self.current_prompt_pos)
352 catched = True
315 catched = True
353
316
354 elif event.Modifiers == wx.MOD_SHIFT:
317 elif event.Modifiers in (wx.MOD_SHIFT, wx.MOD_WIN) :
355 self.selectFromTo(self.current_prompt_pos,
318 self.selectFromTo(self.current_prompt_pos,
356 self.GetCurrentPos())
319 self.GetCurrentPos())
357 catched = True
320 catched = True
358
321
359 elif event.KeyCode == wx.WXK_UP:
322 elif event.KeyCode == wx.WXK_UP:
360 if self.GetCurrentLine() > self.current_prompt_line:
323 if self.GetCurrentLine() > self.current_prompt_line:
361 if self.GetCurrentLine() == self.current_prompt_line + 1 \
324 if self.GetCurrentLine() == self.current_prompt_line + 1 \
362 and self.GetColumn(self.GetCurrentPos()) < \
325 and self.GetColumn(self.GetCurrentPos()) < \
363 self.GetColumn(self.current_prompt_pos):
326 self.GetColumn(self.current_prompt_pos):
364 self.GotoPos(self.current_prompt_pos)
327 self.GotoPos(self.current_prompt_pos)
365 else:
328 else:
366 event.Skip()
329 event.Skip()
367 catched = True
330 catched = True
368
331
369 elif event.KeyCode in (wx.WXK_LEFT, wx.WXK_BACK):
332 elif event.KeyCode in (wx.WXK_LEFT, wx.WXK_BACK):
370 if self.GetCurrentPos() > self.current_prompt_pos:
333 if self.GetCurrentPos() > self.current_prompt_pos:
371 event.Skip()
334 event.Skip()
372 catched = True
335 catched = True
373
336
374 if skip and not catched:
337 if skip and not catched:
375 event.Skip()
338 event.Skip()
376
339
377 if event.KeyCode not in (wx.WXK_PAGEUP, wx.WXK_PAGEDOWN)\
340 if event.KeyCode not in (wx.WXK_PAGEUP, wx.WXK_PAGEDOWN)\
378 and event.Modifiers in (wx.MOD_NONE, wx.MOD_WIN,
341 and event.Modifiers in (wx.MOD_NONE, wx.MOD_WIN,
379 wx.MOD_SHIFT):
342 wx.MOD_SHIFT):
380 # If cursor is outside the editing region, put it back.
343 # If cursor is outside the editing region, put it back.
381 if self.GetCurrentPos() < self.current_prompt_pos:
344 if self.GetCurrentPos() < self.current_prompt_pos:
382 self.GotoPos(self.current_prompt_pos)
345 self.GotoPos(self.current_prompt_pos)
383
346
384 return catched
347 return catched
385
348
386
349
387 def OnUpdateUI(self, evt):
388 # check for matching braces
389 braceAtCaret = -1
390 braceOpposite = -1
391 charBefore = None
392 caretPos = self.GetCurrentPos()
393
394 if caretPos > 0:
395 charBefore = self.GetCharAt(caretPos - 1)
396 styleBefore = self.GetStyleAt(caretPos - 1)
397
398 # check before
399 if charBefore and chr(charBefore) in "[]{}()" and styleBefore == stc.STC_P_OPERATOR:
400 braceAtCaret = caretPos - 1
401
402 # check after
403 if braceAtCaret < 0:
404 charAfter = self.GetCharAt(caretPos)
405 styleAfter = self.GetStyleAt(caretPos)
406
407 if charAfter and chr(charAfter) in "[]{}()" and styleAfter == stc.STC_P_OPERATOR:
408 braceAtCaret = caretPos
409
410 if braceAtCaret >= 0:
411 braceOpposite = self.BraceMatch(braceAtCaret)
412
413 if braceAtCaret != -1 and braceOpposite == -1:
414 self.BraceBadLight(braceAtCaret)
415 else:
416 self.BraceHighlight(braceAtCaret, braceOpposite)
417
418
350
419 if __name__ == '__main__':
351 if __name__ == '__main__':
420 # Some simple code to test the console widget.
352 # Some simple code to test the console widget.
421 class MainWindow(wx.Frame):
353 class MainWindow(wx.Frame):
422 def __init__(self, parent, id, title):
354 def __init__(self, parent, id, title):
423 wx.Frame.__init__(self, parent, id, title, size=(300,250))
355 wx.Frame.__init__(self, parent, id, title, size=(300,250))
424 self._sizer = wx.BoxSizer(wx.VERTICAL)
356 self._sizer = wx.BoxSizer(wx.VERTICAL)
425 self.console_widget = ConsoleWidget(self)
357 self.console_widget = ConsoleWidget(self)
426 self._sizer.Add(self.console_widget, 1, wx.EXPAND)
358 self._sizer.Add(self.console_widget, 1, wx.EXPAND)
427 self.SetSizer(self._sizer)
359 self.SetSizer(self._sizer)
428 self.SetAutoLayout(1)
360 self.SetAutoLayout(1)
429 self.Show(True)
361 self.Show(True)
430
362
431 app = wx.PySimpleApp()
363 app = wx.PySimpleApp()
432 w = MainWindow(None, wx.ID_ANY, 'ConsoleWidget')
364 w = MainWindow(None, wx.ID_ANY, 'ConsoleWidget')
433 w.SetSize((780, 460))
365 w.SetSize((780, 460))
434 w.Show()
366 w.Show()
435
367
436 app.MainLoop()
368 app.MainLoop()
437
369
438
370
@@ -1,180 +1,207 b''
1 # encoding: utf-8
1 # encoding: utf-8 -*- test-case-name:
2 # -*- test-case-name: ipython1.frontend.cocoa.tests.test_cocoa_frontend -*-
2 # ipython1.frontend.cocoa.tests.test_cocoa_frontend -*-
3
3
4 """Classes to provide a Wx frontend to the
4 """Classes to provide a Wx frontend to the
5 ipython1.kernel.engineservice.EngineService.
5 ipython1.kernel.engineservice.EngineService.
6
6
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 in
14 # Distributed under the terms of the BSD License. The full license is in
15 # the file COPYING, distributed as part of this software.
15 # the file COPYING, distributed as part of this software.
16 #-------------------------------------------------------------------------------
16 #-------------------------------------------------------------------------------
17
17
18 #-------------------------------------------------------------------------------
18 #-------------------------------------------------------------------------------
19 # Imports
19 # Imports
20 #-------------------------------------------------------------------------------
20 #-------------------------------------------------------------------------------
21
21
22
22
23 import wx
23 import wx
24 from console_widget import ConsoleWidget
24 from console_widget import ConsoleWidget
25 import re
25 import re
26
26
27 import IPython
27 import IPython
28 from IPython.kernel.engineservice import EngineService
28 from IPython.kernel.engineservice import EngineService
29 from IPython.kernel.core.history import FrontEndHistory
29 from IPython.frontend.frontendbase import FrontEndBase
30 from IPython.frontend.frontendbase import FrontEndBase
30
31
31
32 #-------------------------------------------------------------------------------
32 #-------------------------------------------------------------------------------
33 # Classes to implement the Wx frontend
33 # Classes to implement the Wx frontend
34 #-------------------------------------------------------------------------------
34 #-------------------------------------------------------------------------------
35
35
36
36
37
37
38
38
39 class IPythonWxController(FrontEndBase, ConsoleWidget):
39 class IPythonWxController(FrontEndBase, ConsoleWidget):
40
40
41 output_prompt = \
41 output_prompt = \
42 '\n\x01\x1b[0;31m\x02Out[\x01\x1b[1;31m\x02%i\x01\x1b[0;31m\x02]: \x01\x1b[0m\x02'
42 '\n\x01\x1b[0;31m\x02Out[\x01\x1b[1;31m\x02%i\x01\x1b[0;31m\x02]: \x01\x1b[0m\x02'
43
43
44 # Are we entering multi line input?
44 # Are we entering multi line input?
45 multi_line_input = False
45 multi_line_input = False
46
46
47 # The added tab stop to the string. It may, for instance, come from
47 # The added tab stop to the string. It may, for instance, come from
48 # copy and pasting something with tabs.
48 # copy and pasting something with tabs.
49 tab_stop = 0
49 tab_stop = 0
50 # FIXME: We still have to deal with this.
50 # FIXME: We still have to deal with this.
51
51
52 #--------------------------------------------------------------------------
52 #--------------------------------------------------------------------------
53 # Public API
53 # Public API
54 #--------------------------------------------------------------------------
54 #--------------------------------------------------------------------------
55
55
56 def __init__(self, parent, id=wx.ID_ANY, pos=wx.DefaultPosition,
56 def __init__(self, parent, id=wx.ID_ANY, pos=wx.DefaultPosition,
57 size=wx.DefaultSize, style=wx.CLIP_CHILDREN,
57 size=wx.DefaultSize, style=wx.CLIP_CHILDREN,
58 *args, **kwds):
58 *args, **kwds):
59 """ Create Shell instance.
59 """ Create Shell instance.
60 """
60 """
61 ConsoleWidget.__init__(self, parent, id, pos, size, style)
61 ConsoleWidget.__init__(self, parent, id, pos, size, style)
62 FrontEndBase.__init__(self, engine=EngineService())
62 FrontEndBase.__init__(self, engine=EngineService(),
63 )
63
64
65 # FIXME: Something is wrong with the history, I instanciate it
66 # with an empty cache, but this is not the way to do.
64 self.lines = {}
67 self.lines = {}
65
68
66 # Start the IPython engine
69 # Start the IPython engine
67 self.engine.startService()
70 self.engine.startService()
68
71
69 # Capture Character keys
72 # Capture Character keys
70 self.Bind(wx.EVT_KEY_UP, self._on_key_up)
73 self.Bind(wx.EVT_KEY_DOWN, self._on_key_down)
71
74
72 #FIXME: print banner.
75 #FIXME: print banner.
73 banner = """IPython1 %s -- An enhanced Interactive Python.""" \
76 banner = """IPython1 %s -- An enhanced Interactive Python.""" \
74 % IPython.__version__
77 % IPython.__version__
75
78
76
79
77 def appWillTerminate_(self, notification):
80 def appWillTerminate_(self, notification):
78 """appWillTerminate"""
81 """appWillTerminate"""
79
82
80 self.engine.stopService()
83 self.engine.stopService()
81
84
82
85
83 def complete(self, token):
86 def complete(self, token):
84 """Complete token in engine's user_ns
87 """Complete token in engine's user_ns
85
88
86 Parameters
89 Parameters
87 ----------
90 ----------
88 token : string
91 token : string
89
92
90 Result
93 Result
91 ------
94 ------
92 Deferred result of
95 Deferred result of
93 IPython.kernel.engineservice.IEngineBase.complete
96 IPython.kernel.engineservice.IEngineBase.complete
94 """
97 """
95
98
96 return self.engine.complete(token)
99 return self.engine.complete(token)
97
100
98
101
99 def render_result(self, result):
102 def render_result(self, result):
100 if 'stdout' in result:
103 if 'stdout' in result and result['stdout']:
101 self.write(result['stdout'])
104 self.write('\n' + result['stdout'])
102 if 'display' in result:
105 if 'display' in result and result['display']:
103 self.write(self.output_prompt % result['number']
106 self.write("%s%s\n" % (
104 + result['display']['pprint'])
107 self.output_prompt % result['number'],
108 result['display']['pprint']
109 ) )
105
110
106
111
107 def render_error(self, failure):
112 def render_error(self, failure):
108 self.insert_text('\n\n'+str(failure)+'\n\n')
113 self.insert_text('\n\n'+str(failure)+'\n\n')
109 return failure
114 return failure
110
115
111
116
112 #--------------------------------------------------------------------------
117 #--------------------------------------------------------------------------
113 # Private API
118 # Private API
114 #--------------------------------------------------------------------------
119 #--------------------------------------------------------------------------
115
120
116
121
117 def _on_key_up(self, event, skip=True):
122 def _on_key_down(self, event, skip=True):
118 """ Capture the character events, let the parent
123 """ Capture the character events, let the parent
119 widget handle them, and put our logic afterward.
124 widget handle them, and put our logic afterward.
120 """
125 """
121 event.Skip()
126 current_line_number = self.GetCurrentLine()
122 # Capture enter
127 # Capture enter
123 if event.KeyCode == 13 and \
128 if event.KeyCode in (13, wx.WXK_NUMPAD_ENTER) and \
124 event.Modifiers in (wx.MOD_NONE, wx.MOD_WIN):
129 event.Modifiers in (wx.MOD_NONE, wx.MOD_WIN):
125 self._on_enter()
130 self._on_enter()
131 # Up history
132 elif event.KeyCode == wx.WXK_UP and (
133 ( current_line_number == self.current_prompt_line and
134 event.Modifiers in (wx.MOD_NONE, wx.MOD_WIN) )
135 or event.ControlDown() ):
136 new_buffer = self.get_history_previous(
137 self.get_current_edit_buffer())
138 if new_buffer is not None:
139 self.replace_current_edit_buffer(new_buffer)
140 # Down history
141 elif event.KeyCode == wx.WXK_DOWN and (
142 ( current_line_number == self.LineCount -1 and
143 event.Modifiers in (wx.MOD_NONE, wx.MOD_WIN) )
144 or event.ControlDown() ):
145 new_buffer = self.get_history_next()
146 if new_buffer is not None:
147 self.replace_current_edit_buffer(new_buffer)
148 else:
149 ConsoleWidget._on_key_down(self, event, skip=True)
150
126
151
127
128 def _on_enter(self):
152 def _on_enter(self):
129 """ Called when the return key is pressed in a line editing
153 """ Called when the return key is pressed in a line editing
130 buffer.
154 buffer.
131 """
155 """
132 current_buffer = self.get_current_edit_buffer()
156 current_buffer = self.get_current_edit_buffer()
133 current_buffer = current_buffer.replace('\r\n', '\n')
157 current_buffer = current_buffer.replace('\r\n', '\n')
134 current_buffer = current_buffer.replace('\t', 4*' ')
158 current_buffer = current_buffer.replace('\t', 4*' ')
159 cleaned_buffer = '\n'.join(l.rstrip()
160 for l in current_buffer.split('\n'))
135 if ( not self.multi_line_input
161 if ( not self.multi_line_input
136 or re.findall(r"\n[\t ]*\n[\t ]*$", current_buffer)):
162 or re.findall(r"\n[\t ]*$", cleaned_buffer)):
137 if self.is_complete(current_buffer):
163 if self.is_complete(cleaned_buffer):
138 result = self.engine.shell.execute(current_buffer)
164 self._add_history(None, cleaned_buffer.rstrip())
165 result = self.engine.shell.execute(cleaned_buffer)
139 self.render_result(result)
166 self.render_result(result)
140 self.new_prompt(self.prompt % result['number'])
167 self.new_prompt(self.prompt % (result['number'] + 1))
141 self.multi_line_input = False
168 self.multi_line_input = False
142 else:
169 else:
143 if self.multi_line_input:
170 if self.multi_line_input:
144 self.write(self._get_indent_string(current_buffer[:-1]))
171 self.write('\n' + self._get_indent_string(current_buffer))
145 else:
172 else:
146 self.multi_line_input = True
173 self.multi_line_input = True
147 self.write('\t')
174 self.write('\n\t')
148 else:
175 else:
149 self.write(self._get_indent_string(current_buffer[:-1]))
176 self.write('\n'+self._get_indent_string(current_buffer))
150
177
151
178
152 def _get_indent_string(self, string):
179 def _get_indent_string(self, string):
153 string = string.split('\n')[-1]
180 string = string.split('\n')[-1]
154 indent_chars = len(string) - len(string.lstrip())
181 indent_chars = len(string) - len(string.lstrip())
155 indent_string = '\t'*(indent_chars // 4) + \
182 indent_string = '\t'*(indent_chars // 4) + \
156 ' '*(indent_chars % 4)
183 ' '*(indent_chars % 4)
157
184
158 return indent_string
185 return indent_string
159
186
160
187
161
188
162 if __name__ == '__main__':
189 if __name__ == '__main__':
163 class MainWindow(wx.Frame):
190 class MainWindow(wx.Frame):
164 def __init__(self, parent, id, title):
191 def __init__(self, parent, id, title):
165 wx.Frame.__init__(self, parent, id, title, size=(300,250))
192 wx.Frame.__init__(self, parent, id, title, size=(300,250))
166 self._sizer = wx.BoxSizer(wx.VERTICAL)
193 self._sizer = wx.BoxSizer(wx.VERTICAL)
167 self.shell = IPythonWxController(self)
194 self.shell = IPythonWxController(self)
168 self._sizer.Add(self.shell, 1, wx.EXPAND)
195 self._sizer.Add(self.shell, 1, wx.EXPAND)
169 self.SetSizer(self._sizer)
196 self.SetSizer(self._sizer)
170 self.SetAutoLayout(1)
197 self.SetAutoLayout(1)
171 self.Show(True)
198 self.Show(True)
172
199
173 app = wx.PySimpleApp()
200 app = wx.PySimpleApp()
174 frame = MainWindow(None, wx.ID_ANY, 'Ipython')
201 frame = MainWindow(None, wx.ID_ANY, 'Ipython')
175 frame.shell.SetFocus()
202 frame.shell.SetFocus()
176 frame.SetSize((780, 460))
203 frame.SetSize((660, 460))
177 shell = frame.shell
204 self = frame.shell
178
205
179 # app.MainLoop()
206 # app.MainLoop()
180
207
General Comments 0
You need to be logged in to leave comments. Login now