##// END OF EJS Templates
Make the wx frontend well-behaved under windows.
gvaroquaux -
Show More
@@ -1,177 +1,176 b''
1 1 """
2 2 Base front end class for all line-oriented frontends.
3 3
4 4 Currently this focuses on synchronous frontends.
5 5 """
6 6 __docformat__ = "restructuredtext en"
7 7
8 8 #-------------------------------------------------------------------------------
9 9 # Copyright (C) 2008 The IPython Development Team
10 10 #
11 11 # Distributed under the terms of the BSD License. The full license is in
12 12 # the file COPYING, distributed as part of this software.
13 13 #-------------------------------------------------------------------------------
14 14
15 15 #-------------------------------------------------------------------------------
16 16 # Imports
17 17 #-------------------------------------------------------------------------------
18 18 import re
19 19
20 20 import IPython
21 21
22
23 22 from frontendbase import FrontEndBase
24 23 from IPython.kernel.core.interpreter import Interpreter
25 24
26
27 25 def common_prefix(strings):
28 26 ref = strings[0]
29 27 prefix = ''
30 28 for size in range(len(ref)):
31 29 test_prefix = ref[:size+1]
32 30 for string in strings[1:]:
33 31 if not string.startswith(test_prefix):
34 32 return prefix
35 33 prefix = test_prefix
36 34
37 35 return prefix
38 36
39 37 #-------------------------------------------------------------------------------
40 38 # Base class for the line-oriented front ends
41 39 #-------------------------------------------------------------------------------
42 40 class LineFrontEndBase(FrontEndBase):
43 41
44 42 # We need to keep the prompt number, to be able to increment
45 43 # it when there is an exception.
46 44 prompt_number = 1
47 45
48 46 # To bootstrap
49 47 last_result = dict(number=0)
50 48
51 49 #--------------------------------------------------------------------------
52 50 # Public API
53 51 #--------------------------------------------------------------------------
54 52
55 53 def __init__(self, shell=None, history=None):
56 54 if shell is None:
57 55 shell = Interpreter()
58 56 FrontEndBase.__init__(self, shell=shell, history=history)
59 57
60 58 #FIXME: print banner.
61 59 banner = """IPython1 %s -- An enhanced Interactive Python.""" \
62 60 % IPython.__version__
63 61
64 62
65 63 def complete(self, line):
66 64 """Complete line in engine's user_ns
67 65
68 66 Parameters
69 67 ----------
70 68 line : string
71 69
72 70 Result
73 71 ------
74 72 The replacement for the line and the list of possible completions.
75 73 """
76 74 completions = self.shell.complete(line)
77 75 complete_sep = re.compile('[\s\{\}\[\]\(\)\=]')
78 76 if completions:
79 77 prefix = common_prefix(completions)
80 78 residual = complete_sep.split(line)[:-1]
81 79 line = line[:-len(residual)] + prefix
82 80 return line, completions
83 81
84 82
85 83 def render_result(self, result):
86 84 if 'stdout' in result and result['stdout']:
87 85 self.write('\n' + result['stdout'])
88 86 if 'display' in result and result['display']:
89 87 self.write("%s%s\n" % (
90 88 self.output_prompt % result['number'],
91 89 result['display']['pprint']
92 90 ) )
93 91
94 92
95 93 def render_error(self, failure):
96 94 self.insert_text('\n\n'+str(failure)+'\n\n')
97 95 return failure
98 96
99 97
100 98 def prefilter_input(self, string):
101 99 string = string.replace('\r\n', '\n')
102 100 string = string.replace('\t', 4*' ')
103 101 # Clean the trailing whitespace
104 102 string = '\n'.join(l.rstrip() for l in string.split('\n'))
105 103 return string
106 104
107 105
108 106 def is_complete(self, string):
109 107 if string in ('', '\n'):
110 108 return True
111 109 elif ( len(self.get_current_edit_buffer().split('\n'))>2
112 110 and not re.findall(r"\n[\t ]*\n[\t ]*$", string)):
113 111 return False
114 112 else:
115 113 # Add line returns here, to make sure that the statement is
116 114 # complete.
117 115 return FrontEndBase.is_complete(self, string.rstrip() + '\n\n')
118 116
119 117
120 118 def execute(self, python_string, raw_string=None):
121 119 """ Send the python_string to the interpreter, stores the
122 120 raw_string in the history and starts a new prompt.
123 121 """
124 122 if raw_string is None:
125 123 raw_string = python_string
126 124 # Create a false result, in case there is an exception
127 125 self.last_result = dict(number=self.prompt_number)
128 126 try:
129 127 self.history.input_cache[-1] = raw_string.rstrip()
130 128 result = self.shell.execute(python_string)
131 129 self.last_result = result
132 130 self.render_result(result)
133 131 except:
134 132 self.show_traceback()
135 133 finally:
136 134 self.after_execute()
137 135
138 136
139 137 def after_execute(self):
140 138 """ All the operations required after an execution to put the
141 139 terminal back in a shape where it is usable.
142 140 """
143 141 self.prompt_number += 1
144 142 self.new_prompt(self.prompt % (self.last_result['number'] + 1))
145 143 # Start a new empty history entry
146 144 self._add_history(None, '')
147 145 self.history_cursor = len(self.history.input_cache) - 1
148 146
149 147
150 148 def _on_enter(self):
151 149 """ Called when the return key is pressed in a line editing
152 150 buffer.
153 151 """
154 152 current_buffer = self.get_current_edit_buffer()
155 153 cleaned_buffer = self.prefilter_input(current_buffer)
156 154 if self.is_complete(cleaned_buffer):
157 155 self.execute(cleaned_buffer, raw_string=current_buffer)
158 156 else:
159 self.write(self._get_indent_string(current_buffer[:-1]))
160 if current_buffer.rstrip().endswith(':'):
157 self.write(self._get_indent_string(
158 current_buffer[:-1]))
159 if current_buffer[:-1].split('\n')[-1].rstrip().endswith(':'):
161 160 self.write('\t')
162 161
163 162
164 163 #--------------------------------------------------------------------------
165 164 # Private API
166 165 #--------------------------------------------------------------------------
167 166
168 167 def _get_indent_string(self, string):
169 168 string = string.replace('\t', ' '*4)
170 169 string = string.split('\n')[-1]
171 170 indent_chars = len(string) - len(string.lstrip())
172 171 indent_string = '\t'*(indent_chars // 4) + \
173 172 ' '*(indent_chars % 4)
174 173
175 174 return indent_string
176 175
177 176
@@ -1,421 +1,430 b''
1 1 # encoding: utf-8
2 2 """
3 3 A Wx widget to act as a console and input commands.
4 4
5 5 This widget deals with prompts and provides an edit buffer
6 6 restricted to after the last prompt.
7 7 """
8 8
9 9 __docformat__ = "restructuredtext en"
10 10
11 11 #-------------------------------------------------------------------------------
12 12 # Copyright (C) 2008 The IPython Development Team
13 13 #
14 14 # Distributed under the terms of the BSD License. The full license is
15 15 # in the file COPYING, distributed as part of this software.
16 16 #-------------------------------------------------------------------------------
17 17
18 18 #-------------------------------------------------------------------------------
19 19 # Imports
20 20 #-------------------------------------------------------------------------------
21 21
22 22 import wx
23 23 import wx.stc as stc
24 24
25 25 from wx.py import editwindow
26 import sys
27 LINESEP = '\n'
28 if sys.platform == 'win32':
29 LINESEP = '\n\r'
26 30
27 31 import re
28 32
29 33 # FIXME: Need to provide an API for non user-generated display on the
30 34 # screen: this should not be editable by the user.
31 35
32 if wx.Platform == '__WXMSW__':
33 _DEFAULT_SIZE = 80
34 else:
35 36 _DEFAULT_SIZE = 10
36 37
37 38 _DEFAULT_STYLE = {
38 39 'stdout' : 'fore:#0000FF',
39 40 'stderr' : 'fore:#007f00',
40 41 'trace' : 'fore:#FF0000',
41 42
42 43 'default' : 'size:%d' % _DEFAULT_SIZE,
43 44 'bracegood' : 'fore:#FFFFFF,back:#0000FF,bold',
44 45 'bracebad' : 'fore:#000000,back:#FF0000,bold',
45 46
46 47 # properties for the various Python lexer styles
47 48 'comment' : 'fore:#007F00',
48 49 'number' : 'fore:#007F7F',
49 50 'string' : 'fore:#7F007F,italic',
50 51 'char' : 'fore:#7F007F,italic',
51 52 'keyword' : 'fore:#00007F,bold',
52 53 'triple' : 'fore:#7F0000',
53 54 'tripledouble' : 'fore:#7F0000',
54 55 'class' : 'fore:#0000FF,bold,underline',
55 56 'def' : 'fore:#007F7F,bold',
56 57 'operator' : 'bold'
57 58 }
58 59
59 60 # new style numbers
60 61 _STDOUT_STYLE = 15
61 62 _STDERR_STYLE = 16
62 63 _TRACE_STYLE = 17
63 64
64 65
65 66 # system colors
66 67 #SYS_COLOUR_BACKGROUND = wx.SystemSettings.GetColour(wx.SYS_COLOUR_BACKGROUND)
67 68
68 69 #-------------------------------------------------------------------------------
69 70 # The console widget class
70 71 #-------------------------------------------------------------------------------
71 72 class ConsoleWidget(editwindow.EditWindow):
72 73 """ Specialized styled text control view for console-like workflow.
73 74
74 75 This widget is mainly interested in dealing with the prompt and
75 76 keeping the cursor inside the editing line.
76 77 """
77 78
78 79 title = 'Console'
79 80
80 81 style = _DEFAULT_STYLE.copy()
81 82
82 83 # Translation table from ANSI escape sequences to color. Override
83 84 # this to specify your colors.
84 85 ANSI_STYLES = {'0;30': [0, 'BLACK'], '0;31': [1, 'RED'],
85 86 '0;32': [2, 'GREEN'], '0;33': [3, 'BROWN'],
86 87 '0;34': [4, 'BLUE'], '0;35': [5, 'PURPLE'],
87 88 '0;36': [6, 'CYAN'], '0;37': [7, 'LIGHT GREY'],
88 89 '1;30': [8, 'DARK GREY'], '1;31': [9, 'RED'],
89 90 '1;32': [10, 'SEA GREEN'], '1;33': [11, 'YELLOW'],
90 91 '1;34': [12, 'LIGHT BLUE'], '1;35':
91 92 [13, 'MEDIUM VIOLET RED'],
92 93 '1;36': [14, 'LIGHT STEEL BLUE'], '1;37': [15, 'YELLOW']}
93 94
94 95 # The color of the carret (call _apply_style() after setting)
95 96 carret_color = 'BLACK'
96 97
97
98 98 #--------------------------------------------------------------------------
99 99 # Public API
100 100 #--------------------------------------------------------------------------
101 101
102 102 def __init__(self, parent, id=wx.ID_ANY, pos=wx.DefaultPosition,
103 103 size=wx.DefaultSize, style=0, ):
104 104 editwindow.EditWindow.__init__(self, parent, id, pos, size, style)
105 105 self.configure_scintilla()
106 106
107 107 # FIXME: we need to retrieve this from the interpreter.
108 108 self.prompt = \
109 109 '\n\x01\x1b[0;34m\x02In [\x01\x1b[1;34m\x02%i\x01\x1b[0;34m\x02]: \x01\x1b[0m\x02'
110 110 self.new_prompt(self.prompt % 1)
111 111
112 112 self.Bind(wx.EVT_KEY_DOWN, self._on_key_down)
113 113 self.Bind(wx.EVT_KEY_UP, self._on_key_up)
114 114
115 115
116 116 def configure_scintilla(self):
117 self.SetEOLMode(stc.STC_EOL_LF)
118
117 119 # Ctrl"+" or Ctrl "-" can be used to zoomin/zoomout the text inside
118 120 # the widget
119 121 self.CmdKeyAssign(ord('+'), stc.STC_SCMOD_CTRL, stc.STC_CMD_ZOOMIN)
120 122 self.CmdKeyAssign(ord('-'), stc.STC_SCMOD_CTRL, stc.STC_CMD_ZOOMOUT)
121 123 # Also allow Ctrl Shift "=" for poor non US keyboard users.
122 124 self.CmdKeyAssign(ord('='), stc.STC_SCMOD_CTRL|stc.STC_SCMOD_SHIFT,
123 125 stc.STC_CMD_ZOOMIN)
124 126
125 127 #self.CmdKeyAssign(stc.STC_KEY_PRIOR, stc.STC_SCMOD_SHIFT,
126 128 # stc.STC_CMD_PAGEUP)
127 129
128 130 #self.CmdKeyAssign(stc.STC_KEY_NEXT, stc.STC_SCMOD_SHIFT,
129 131 # stc.STC_CMD_PAGEDOWN)
130 132
131 133 # Keys: we need to clear some of the keys the that don't play
132 134 # well with a console.
133 135 self.CmdKeyClear(ord('D'), stc.STC_SCMOD_CTRL)
134 136 self.CmdKeyClear(ord('L'), stc.STC_SCMOD_CTRL)
135 137 self.CmdKeyClear(ord('T'), stc.STC_SCMOD_CTRL)
136 138
137 139
138 140 self.SetEOLMode(stc.STC_EOL_CRLF)
139 141 self.SetWrapMode(stc.STC_WRAP_CHAR)
140 142 self.SetWrapMode(stc.STC_WRAP_WORD)
141 143 self.SetBufferedDraw(True)
142 144 self.SetUseAntiAliasing(True)
143 145 self.SetLayoutCache(stc.STC_CACHE_PAGE)
144 146 self.SetUndoCollection(False)
145 147 self.SetUseTabs(True)
146 148 self.SetIndent(4)
147 149 self.SetTabWidth(4)
148 150
149 151 self.EnsureCaretVisible()
150 152 # we don't want scintilla's autocompletion to choose
151 153 # automaticaly out of a single choice list, as we pop it up
152 154 # automaticaly
153 155 self.AutoCompSetChooseSingle(False)
154 156 self.AutoCompSetMaxHeight(10)
155 157
156 158 self.SetMargins(3, 3) #text is moved away from border with 3px
157 159 # Suppressing Scintilla margins
158 160 self.SetMarginWidth(0, 0)
159 161 self.SetMarginWidth(1, 0)
160 162 self.SetMarginWidth(2, 0)
161 163
162 164 self._apply_style()
163 165
164 166 # Xterm escape sequences
165 167 self.color_pat = re.compile('\x01?\x1b\[(.*?)m\x02?')
166 168 self.title_pat = re.compile('\x1b]0;(.*?)\x07')
167 169
168 170 #self.SetEdgeMode(stc.STC_EDGE_LINE)
169 171 #self.SetEdgeColumn(80)
170 172
171 173 # styles
172 174 p = self.style
173 175 self.StyleSetSpec(stc.STC_STYLE_DEFAULT, p['default'])
174 176 self.StyleClearAll()
175 177 self.StyleSetSpec(_STDOUT_STYLE, p['stdout'])
176 178 self.StyleSetSpec(_STDERR_STYLE, p['stderr'])
177 179 self.StyleSetSpec(_TRACE_STYLE, p['trace'])
178 180
179 181 self.StyleSetSpec(stc.STC_STYLE_BRACELIGHT, p['bracegood'])
180 182 self.StyleSetSpec(stc.STC_STYLE_BRACEBAD, p['bracebad'])
181 183 self.StyleSetSpec(stc.STC_P_COMMENTLINE, p['comment'])
182 184 self.StyleSetSpec(stc.STC_P_NUMBER, p['number'])
183 185 self.StyleSetSpec(stc.STC_P_STRING, p['string'])
184 186 self.StyleSetSpec(stc.STC_P_CHARACTER, p['char'])
185 187 self.StyleSetSpec(stc.STC_P_WORD, p['keyword'])
186 188 self.StyleSetSpec(stc.STC_P_WORD2, p['keyword'])
187 189 self.StyleSetSpec(stc.STC_P_TRIPLE, p['triple'])
188 190 self.StyleSetSpec(stc.STC_P_TRIPLEDOUBLE, p['tripledouble'])
189 191 self.StyleSetSpec(stc.STC_P_CLASSNAME, p['class'])
190 192 self.StyleSetSpec(stc.STC_P_DEFNAME, p['def'])
191 193 self.StyleSetSpec(stc.STC_P_OPERATOR, p['operator'])
192 194 self.StyleSetSpec(stc.STC_P_COMMENTBLOCK, p['comment'])
193 195
194 196
195 197 def write(self, text):
196 198 """ Write given text to buffer, while translating the ansi escape
197 199 sequences.
198 200 """
199 201 # XXX: do not put print statements in this method, the print
200 202 # statements will call this method, and you will end up with
201 203 # an infinit loop
202 204 title = self.title_pat.split(text)
203 205 if len(title)>1:
204 206 self.title = title[-2]
205 207
206 208 text = self.title_pat.sub('', text)
207 209 segments = self.color_pat.split(text)
208 210 segment = segments.pop(0)
211 self.GotoPos(self.GetLength())
209 212 self.StartStyling(self.GetLength(), 0xFF)
210 213 self.AppendText(segment)
211 214
212 215 if segments:
213 216 for ansi_tag, text in zip(segments[::2], segments[1::2]):
214 217 self.StartStyling(self.GetLength(), 0xFF)
215 218 self.AppendText(text)
216 219
217 220 if ansi_tag not in self.ANSI_STYLES:
218 221 style = 0
219 222 else:
220 223 style = self.ANSI_STYLES[ansi_tag][0]
221 224
222 225 self.SetStyling(len(text), style)
223 226
224 227 self.GotoPos(self.GetLength())
225 228 wx.Yield()
226 229
227 230
228 231 def new_prompt(self, prompt):
229 232 """ Prints a prompt at start of line, and move the start of the
230 233 current block there.
231 234
232 235 The prompt can be give with ascii escape sequences.
233 236 """
234 237 self.write(prompt)
235 238 # now we update our cursor giving end of prompt
236 239 self.current_prompt_pos = self.GetLength()
237 240 self.current_prompt_line = self.GetCurrentLine()
238 241 wx.Yield()
239 242 self.EnsureCaretVisible()
240 243
241 244
242 245 def replace_current_edit_buffer(self, text):
243 246 """ Replace currently entered command line with given text.
244 247 """
245 248 self.SetSelection(self.current_prompt_pos, self.GetLength())
246 249 self.ReplaceSelection(text)
247 250 self.GotoPos(self.GetLength())
248 251
249 252
250 253 def get_current_edit_buffer(self):
251 254 """ Returns the text in current edit buffer.
252 255 """
253 return self.GetTextRange(self.current_prompt_pos,
256 current_edit_buffer = self.GetTextRange(self.current_prompt_pos,
254 257 self.GetLength())
258 current_edit_buffer = current_edit_buffer.replace(LINESEP, '\n')
259 return current_edit_buffer
255 260
256 261
257 262 #--------------------------------------------------------------------------
258 263 # Private API
259 264 #--------------------------------------------------------------------------
260 265
261 266 def _apply_style(self):
262 267 """ Applies the colors for the different text elements and the
263 268 carret.
264 269 """
265 270 self.SetCaretForeground(self.carret_color)
266 271
267 272 #self.StyleClearAll()
268 273 self.StyleSetSpec(stc.STC_STYLE_BRACELIGHT,
269 274 "fore:#FF0000,back:#0000FF,bold")
270 275 self.StyleSetSpec(stc.STC_STYLE_BRACEBAD,
271 276 "fore:#000000,back:#FF0000,bold")
272 277
273 278 for style in self.ANSI_STYLES.values():
274 279 self.StyleSetSpec(style[0], "bold,fore:%s" % style[1])
275 280
276 281
277 282 def write_completion(self, possibilities):
278 283 # FIXME: This is non Wx specific and needs to be moved into
279 284 # the base class.
280 285 current_buffer = self.get_current_edit_buffer()
281 286
282 287 self.write('\n')
283 288 max_len = len(max(possibilities, key=len)) + 1
284 289
285 290 #now we check how much symbol we can put on a line...
286 291 chars_per_line = self.GetSize()[0]/self.GetCharWidth()
287 292 symbols_per_line = max(1, chars_per_line/max_len)
288 293
289 294 pos = 1
290 295 buf = []
291 296 for symbol in possibilities:
292 297 if pos < symbols_per_line:
293 298 buf.append(symbol.ljust(max_len))
294 299 pos += 1
295 300 else:
296 301 buf.append(symbol.rstrip() +'\n')
297 302 pos = 1
298 303 self.write(''.join(buf))
299 304 self.new_prompt(self.prompt % (self.last_result['number'] + 1))
300 305 self.replace_current_edit_buffer(current_buffer)
301 306
302 307
303 308 def pop_completion(self, possibilities, offset=0):
304 309 """ Pops up an autocompletion menu. Offset is the offset
305 310 in characters of the position at which the menu should
306 311 appear, relativ to the cursor.
307 312 """
308 313 self.AutoCompSetIgnoreCase(False)
309 314 self.AutoCompSetAutoHide(False)
310 315 self.AutoCompSetMaxHeight(len(possibilities))
311 316 self.AutoCompShow(offset, " ".join(possibilities))
312 317
313 318
314 319 def scroll_to_bottom(self):
315 320 maxrange = self.GetScrollRange(wx.VERTICAL)
316 321 self.ScrollLines(maxrange)
317 322
318 323
319 def _on_enter(self):
320 """ Called when the return key is hit.
321 """
322 pass
323
324
325 324 def _on_key_down(self, event, skip=True):
326 325 """ Key press callback used for correcting behavior for
327 326 console-like interfaces: the cursor is constraint to be after
328 327 the last prompt.
329 328
330 329 Return True if event as been catched.
331 330 """
332 331 catched = True
333 332 # Intercept some specific keys.
334 333 if event.KeyCode == ord('L') and event.ControlDown() :
335 334 self.scroll_to_bottom()
336 335 elif event.KeyCode == ord('K') and event.ControlDown() :
337 336 self.replace_current_edit_buffer('')
338 337 elif event.KeyCode == wx.WXK_PAGEUP and event.ShiftDown():
339 338 self.ScrollPages(-1)
340 339 elif event.KeyCode == wx.WXK_PAGEDOWN and event.ShiftDown():
341 340 self.ScrollPages(1)
342 341 elif event.KeyCode == wx.WXK_UP and event.ShiftDown():
343 342 self.ScrollLines(-1)
344 343 elif event.KeyCode == wx.WXK_DOWN and event.ShiftDown():
345 344 self.ScrollLines(1)
346 345 else:
347 346 catched = False
348 347
349 348 if self.AutoCompActive():
350 349 event.Skip()
351 350 else:
352 351 if event.KeyCode in (13, wx.WXK_NUMPAD_ENTER) and \
353 352 event.Modifiers in (wx.MOD_NONE, wx.MOD_WIN):
354 353 catched = True
355 354 self.CallTipCancel()
356 355 self.write('\n')
356 # Under windows scintilla seems to be doing funny stuff to the
357 # line returns here, but get_current_edit_buffer filters this
358 # out.
359 if sys.platform == 'win32':
360 self.replace_current_edit_buffer(
361 self.get_current_edit_buffer())
357 362 self._on_enter()
358 363
359 364 elif event.KeyCode == wx.WXK_HOME:
360 365 if event.Modifiers in (wx.MOD_NONE, wx.MOD_WIN):
361 366 self.GotoPos(self.current_prompt_pos)
362 367 catched = True
363 368
364 369 elif event.Modifiers in (wx.MOD_SHIFT, wx.MOD_WIN) :
365 370 # FIXME: This behavior is not ideal: if the selection
366 371 # is already started, it will jump.
367 372 self.SetSelectionStart(self.current_prompt_pos)
368 373 self.SetSelectionEnd(self.GetCurrentPos())
369 374 catched = True
370 375
371 376 elif event.KeyCode == wx.WXK_UP:
372 377 if self.GetCurrentLine() > self.current_prompt_line:
373 378 if self.GetCurrentLine() == self.current_prompt_line + 1 \
374 379 and self.GetColumn(self.GetCurrentPos()) < \
375 380 self.GetColumn(self.current_prompt_pos):
376 381 self.GotoPos(self.current_prompt_pos)
377 382 else:
378 383 event.Skip()
379 384 catched = True
380 385
381 386 elif event.KeyCode in (wx.WXK_LEFT, wx.WXK_BACK):
382 387 if self.GetCurrentPos() > self.current_prompt_pos:
383 388 event.Skip()
384 389 catched = True
385 390
386 391 if skip and not catched:
392 # Put the cursor back in the edit region
393 if self.GetCurrentPos() < self.current_prompt_pos:
394 self.GotoPos(self.current_prompt_pos)
395 else:
387 396 event.Skip()
388 397
389 398 return catched
390 399
391 400
392 401 def _on_key_up(self, event, skip=True):
393 402 """ If cursor is outside the editing region, put it back.
394 403 """
395 404 event.Skip()
396 405 if self.GetCurrentPos() < self.current_prompt_pos:
397 406 self.GotoPos(self.current_prompt_pos)
398 407
399 408
400 409
401 410
402 411 if __name__ == '__main__':
403 412 # Some simple code to test the console widget.
404 413 class MainWindow(wx.Frame):
405 414 def __init__(self, parent, id, title):
406 415 wx.Frame.__init__(self, parent, id, title, size=(300,250))
407 416 self._sizer = wx.BoxSizer(wx.VERTICAL)
408 417 self.console_widget = ConsoleWidget(self)
409 418 self._sizer.Add(self.console_widget, 1, wx.EXPAND)
410 419 self.SetSizer(self._sizer)
411 420 self.SetAutoLayout(1)
412 421 self.Show(True)
413 422
414 423 app = wx.PySimpleApp()
415 424 w = MainWindow(None, wx.ID_ANY, 'ConsoleWidget')
416 425 w.SetSize((780, 460))
417 426 w.Show()
418 427
419 428 app.MainLoop()
420 429
421 430
@@ -1,271 +1,277 b''
1 1 # encoding: utf-8 -*- test-case-name:
2 2 # FIXME: Need to add tests.
3 3 # ipython1.frontend.cocoa.tests.test_cocoa_frontend -*-
4 4
5 5 """Classes to provide a Wx frontend to the
6 6 IPython.kernel.core.interpreter.
7 7
8 8 """
9 9
10 10 __docformat__ = "restructuredtext en"
11 11
12 12 #-------------------------------------------------------------------------------
13 13 # Copyright (C) 2008 The IPython Development Team
14 14 #
15 15 # Distributed under the terms of the BSD License. The full license is in
16 16 # the file COPYING, distributed as part of this software.
17 17 #-------------------------------------------------------------------------------
18 18
19 19 #-------------------------------------------------------------------------------
20 20 # Imports
21 21 #-------------------------------------------------------------------------------
22 22
23 23
24 24 import wx
25 25 import re
26 26 from wx import stc
27 27 from console_widget import ConsoleWidget
28 28 import __builtin__
29 29 from time import sleep
30 30
31 31 from IPython.frontend.prefilterfrontend import PrefilterFrontEnd
32 32
33 33 #_COMMAND_BG = '#FAFAF1' # Nice green
34 34 _RUNNING_BUFFER_BG = '#FDFFD3' # Nice yellow
35 35 _ERROR_BG = '#FFF1F1' # Nice red
36 36
37 37 _RUNNING_BUFFER_MARKER = 31
38 38 _ERROR_MARKER = 30
39 39
40 40 #-------------------------------------------------------------------------------
41 41 # Classes to implement the Wx frontend
42 42 #-------------------------------------------------------------------------------
43 43 class WxController(PrefilterFrontEnd, ConsoleWidget):
44 44
45 45 output_prompt = \
46 46 '\x01\x1b[0;31m\x02Out[\x01\x1b[1;31m\x02%i\x01\x1b[0;31m\x02]: \x01\x1b[0m\x02'
47 47
48 debug = True
49
48 50 #--------------------------------------------------------------------------
49 51 # Public API
50 52 #--------------------------------------------------------------------------
51 53
52 54 def __init__(self, parent, id=wx.ID_ANY, pos=wx.DefaultPosition,
53 55 size=wx.DefaultSize, style=wx.CLIP_CHILDREN,
54 56 *args, **kwds):
55 57 """ Create Shell instance.
56 58 """
57 59 ConsoleWidget.__init__(self, parent, id, pos, size, style)
58 60 PrefilterFrontEnd.__init__(self)
59 61
60 62 # Capture Character keys
61 63 self.Bind(wx.EVT_KEY_DOWN, self._on_key_down)
62 64
63 65 # Marker for running buffer.
64 66 self.MarkerDefine(_RUNNING_BUFFER_MARKER, stc.STC_MARK_BACKGROUND,
65 67 background=_RUNNING_BUFFER_BG)
66 68 # Marker for tracebacks.
67 69 self.MarkerDefine(_ERROR_MARKER, stc.STC_MARK_BACKGROUND,
68 70 background=_ERROR_BG)
69 71
70 72
71
72 73 def do_completion(self):
73 74 """ Do code completion.
74 75 """
75 76 line = self.get_current_edit_buffer()
76 77 new_line, completions = self.complete(line)
77 78 if len(completions)>1:
78 79 self.write_completion(completions)
79 80 self.replace_current_edit_buffer(new_line)
80 81
81 82
82 83 def do_calltip(self):
83 84 separators = re.compile('[\s\{\}\[\]\(\)\= ,:]')
84 85 symbol = self.get_current_edit_buffer()
85 86 symbol_string = separators.split(symbol)[-1]
86 87 base_symbol_string = symbol_string.split('.')[0]
87 88 if base_symbol_string in self.shell.user_ns:
88 89 symbol = self.shell.user_ns[base_symbol_string]
89 90 elif base_symbol_string in self.shell.user_global_ns:
90 91 symbol = self.shell.user_global_ns[base_symbol_string]
91 92 elif base_symbol_string in __builtin__.__dict__:
92 93 symbol = __builtin__.__dict__[base_symbol_string]
93 94 else:
94 95 return False
95 96 for name in symbol_string.split('.')[1:] + ['__doc__']:
96 97 symbol = getattr(symbol, name)
97 98 try:
98 99 self.AutoCompCancel()
99 100 wx.Yield()
100 101 self.CallTipShow(self.GetCurrentPos(), symbol)
101 102 except:
102 103 # The retrieve symbol couldn't be converted to a string
103 104 pass
104 105
105 106
106 107 def popup_completion(self, create=False):
107 108 """ Updates the popup completion menu if it exists. If create is
108 109 true, open the menu.
109 110 """
110 111 line = self.get_current_edit_buffer()
111 112 if (self.AutoCompActive() and not line[-1] == '.') \
112 113 or create==True:
113 114 suggestion, completions = self.complete(line)
114 115 offset=0
115 116 if completions:
116 117 complete_sep = re.compile('[\s\{\}\[\]\(\)\= ,:]')
117 118 residual = complete_sep.split(line)[-1]
118 119 offset = len(residual)
119 120 self.pop_completion(completions, offset=offset)
120 121
121 122
122 123 def raw_input(self, prompt):
123 124 """ A replacement from python's raw_input.
124 125 """
125 126 self.new_prompt(prompt)
126 127 self.waiting = True
127 128 self.__old_on_enter = self._on_enter
128 129 def my_on_enter():
129 130 self.waiting = False
130 131 self._on_enter = my_on_enter
131 132 # XXX: Busy waiting, ugly.
132 133 while self.waiting:
133 134 wx.Yield()
134 135 sleep(0.1)
135 136 self._on_enter = self.__old_on_enter
136 137 return self.get_current_edit_buffer().rstrip('\n')
137 138
138 139
139 140 def execute(self, python_string, raw_string=None):
140 141 self.CallTipCancel()
141 142 self._cursor = wx.BusyCursor()
142 143 if raw_string is None:
143 144 raw_string = python_string
144 145 end_line = self.current_prompt_line \
145 146 + max(1, len(raw_string.split('\n'))-1)
146 147 for i in range(self.current_prompt_line, end_line):
147 148 self.MarkerAdd(i, _RUNNING_BUFFER_MARKER)
148 149 # Update the display:
149 150 wx.Yield()
150 151 ## Remove the trailing "\n" for cleaner display
151 152 #self.SetSelection(self.GetLength()-1, self.GetLength())
152 153 #self.ReplaceSelection('')
153 154 self.GotoPos(self.GetLength())
154 155 PrefilterFrontEnd.execute(self, python_string, raw_string=raw_string)
155 156
156 157
157 158 def capture_output(self):
158 159 self.__old_raw_input = __builtin__.raw_input
159 160 __builtin__.raw_input = self.raw_input
160 161 PrefilterFrontEnd.capture_output(self)
161 162
162 163
163 164 def release_output(self):
164 165 __builtin__.raw_input = self.__old_raw_input
165 166 PrefilterFrontEnd.capture_output(self)
166 167
167 168
168 169 def after_execute(self):
169 170 PrefilterFrontEnd.after_execute(self)
170 171 if hasattr(self, '_cursor'):
171 172 del self._cursor
172 173
173 174
174 175 def show_traceback(self):
175 176 start_line = self.GetCurrentLine()
176 177 PrefilterFrontEnd.show_traceback(self)
177 178 wx.Yield()
178 179 for i in range(start_line, self.GetCurrentLine()):
179 180 self.MarkerAdd(i, _ERROR_MARKER)
180 181
181 182
182 183 #--------------------------------------------------------------------------
183 184 # Private API
184 185 #--------------------------------------------------------------------------
185 186
186 187
187 188 def _on_key_down(self, event, skip=True):
188 189 """ Capture the character events, let the parent
189 190 widget handle them, and put our logic afterward.
190 191 """
191 192 current_line_number = self.GetCurrentLine()
192 193 # Calltips
193 194 if event.KeyCode == ord('('):
194 195 event.Skip()
195 196 self.do_calltip()
196 197 elif self.AutoCompActive():
197 198 event.Skip()
198 199 if event.KeyCode in (wx.WXK_BACK, wx.WXK_DELETE):
199 200 wx.CallAfter(self.popup_completion, create=True)
200 201 elif not event.KeyCode in (wx.WXK_UP, wx.WXK_DOWN, wx.WXK_LEFT,
201 202 wx.WXK_RIGHT):
202 203 wx.CallAfter(self.popup_completion)
203 204 else:
204 205 # Up history
205 206 if event.KeyCode == wx.WXK_UP and (
206 207 ( current_line_number == self.current_prompt_line and
207 208 event.Modifiers in (wx.MOD_NONE, wx.MOD_WIN) )
208 209 or event.ControlDown() ):
209 210 new_buffer = self.get_history_previous(
210 211 self.get_current_edit_buffer())
211 212 if new_buffer is not None:
212 213 self.replace_current_edit_buffer(new_buffer)
213 214 if self.GetCurrentLine() > self.current_prompt_line:
214 215 # Go to first line, for seemless history up.
215 216 self.GotoPos(self.current_prompt_pos)
216 217 # Down history
217 218 elif event.KeyCode == wx.WXK_DOWN and (
218 219 ( current_line_number == self.LineCount -1 and
219 220 event.Modifiers in (wx.MOD_NONE, wx.MOD_WIN) )
220 221 or event.ControlDown() ):
221 222 new_buffer = self.get_history_next()
222 223 if new_buffer is not None:
223 224 self.replace_current_edit_buffer(new_buffer)
224 225 # Tab-completion
225 226 elif event.KeyCode == ord('\t'):
226 227 last_line = self.get_current_edit_buffer().split('\n')[-1]
227 228 if not re.match(r'^\s*$', last_line):
228 229 self.do_completion()
229 230 else:
230 231 event.Skip()
231 232 else:
232 233 ConsoleWidget._on_key_down(self, event, skip=skip)
233 234
234 235
235 236 def _on_key_up(self, event, skip=True):
236 237 if event.KeyCode == 59:
237 238 # Intercepting '.'
238 239 event.Skip()
239 240 self.popup_completion(create=True)
240 241 else:
241 242 ConsoleWidget._on_key_up(self, event, skip=skip)
242 243
244 def _on_enter(self):
245 if self.debug:
246 import sys
247 print >>sys.__stdout__, repr(self.get_current_edit_buffer())
248 PrefilterFrontEnd._on_enter(self)
243 249
244 250 def _set_title(self, title):
245 251 return self.Parent.SetTitle(title)
246 252
247 253 def _get_title(self):
248 254 return self.Parent.GetTitle()
249 255
250 256 title = property(_get_title, _set_title)
251 257
252 258
253 259 if __name__ == '__main__':
254 260 class MainWindow(wx.Frame):
255 261 def __init__(self, parent, id, title):
256 262 wx.Frame.__init__(self, parent, id, title, size=(300,250))
257 263 self._sizer = wx.BoxSizer(wx.VERTICAL)
258 264 self.shell = WxController(self)
259 265 self._sizer.Add(self.shell, 1, wx.EXPAND)
260 266 self.SetSizer(self._sizer)
261 267 self.SetAutoLayout(1)
262 268 self.Show(True)
263 269
264 270 app = wx.PySimpleApp()
265 271 frame = MainWindow(None, wx.ID_ANY, 'Ipython')
266 272 frame.shell.SetFocus()
267 273 frame.SetSize((680, 460))
268 274 self = frame.shell
269 275
270 276 app.MainLoop()
271 277
General Comments 0
You need to be logged in to leave comments. Login now