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