##// END OF EJS Templates
Tweak to have windows show tracebacks.
gvaroquaux -
Show More
@@ -1,433 +1,434 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 26 import time
27 27 import sys
28 28 LINESEP = '\n'
29 29 if sys.platform == 'win32':
30 30 LINESEP = '\n\r'
31 31
32 32 import re
33 33
34 34 # FIXME: Need to provide an API for non user-generated display on the
35 35 # screen: this should not be editable by the user.
36 36
37 37 _DEFAULT_SIZE = 10
38 38 if sys.platform == 'darwin':
39 39 _DEFAULT_STYLE = 12
40 40
41 41 _DEFAULT_STYLE = {
42 42 'stdout' : 'fore:#0000FF',
43 43 'stderr' : 'fore:#007f00',
44 44 'trace' : 'fore:#FF0000',
45 45
46 46 'default' : 'size:%d' % _DEFAULT_SIZE,
47 47 'bracegood' : 'fore:#00AA00,back:#000000,bold',
48 48 'bracebad' : 'fore:#FF0000,back:#000000,bold',
49 49
50 50 # properties for the various Python lexer styles
51 51 'comment' : 'fore:#007F00',
52 52 'number' : 'fore:#007F7F',
53 53 'string' : 'fore:#7F007F,italic',
54 54 'char' : 'fore:#7F007F,italic',
55 55 'keyword' : 'fore:#00007F,bold',
56 56 'triple' : 'fore:#7F0000',
57 57 'tripledouble' : 'fore:#7F0000',
58 58 'class' : 'fore:#0000FF,bold,underline',
59 59 'def' : 'fore:#007F7F,bold',
60 60 'operator' : 'bold'
61 61 }
62 62
63 63 # new style numbers
64 64 _STDOUT_STYLE = 15
65 65 _STDERR_STYLE = 16
66 66 _TRACE_STYLE = 17
67 67
68 68
69 69 # system colors
70 70 #SYS_COLOUR_BACKGROUND = wx.SystemSettings.GetColour(wx.SYS_COLOUR_BACKGROUND)
71 71
72 72 #-------------------------------------------------------------------------------
73 73 # The console widget class
74 74 #-------------------------------------------------------------------------------
75 75 class ConsoleWidget(editwindow.EditWindow):
76 76 """ Specialized styled text control view for console-like workflow.
77 77
78 78 This widget is mainly interested in dealing with the prompt and
79 79 keeping the cursor inside the editing line.
80 80 """
81 81
82 82 # This is where the title captured from the ANSI escape sequences are
83 83 # stored.
84 84 title = 'Console'
85 85
86 86 # The buffer being edited.
87 87 def _set_input_buffer(self, string):
88 88 self.SetSelection(self.current_prompt_pos, self.GetLength())
89 89 self.ReplaceSelection(string)
90 90 self.GotoPos(self.GetLength())
91 91
92 92 def _get_input_buffer(self):
93 93 """ Returns the text in current edit buffer.
94 94 """
95 95 input_buffer = self.GetTextRange(self.current_prompt_pos,
96 96 self.GetLength())
97 97 input_buffer = input_buffer.replace(LINESEP, '\n')
98 98 return input_buffer
99 99
100 100 input_buffer = property(_get_input_buffer, _set_input_buffer)
101 101
102 102 style = _DEFAULT_STYLE.copy()
103 103
104 104 # Translation table from ANSI escape sequences to color. Override
105 105 # this to specify your colors.
106 106 ANSI_STYLES = {'0;30': [0, 'BLACK'], '0;31': [1, 'RED'],
107 107 '0;32': [2, 'GREEN'], '0;33': [3, 'BROWN'],
108 108 '0;34': [4, 'BLUE'], '0;35': [5, 'PURPLE'],
109 109 '0;36': [6, 'CYAN'], '0;37': [7, 'LIGHT GREY'],
110 110 '1;30': [8, 'DARK GREY'], '1;31': [9, 'RED'],
111 111 '1;32': [10, 'SEA GREEN'], '1;33': [11, 'YELLOW'],
112 112 '1;34': [12, 'LIGHT BLUE'], '1;35':
113 113 [13, 'MEDIUM VIOLET RED'],
114 114 '1;36': [14, 'LIGHT STEEL BLUE'], '1;37': [15, 'YELLOW']}
115 115
116 116 # The color of the carret (call _apply_style() after setting)
117 117 carret_color = 'BLACK'
118 118
119 119 # Store the last time a refresh was done
120 120 _last_refresh_time = 0
121 121
122 122 #--------------------------------------------------------------------------
123 123 # Public API
124 124 #--------------------------------------------------------------------------
125 125
126 126 def __init__(self, parent, id=wx.ID_ANY, pos=wx.DefaultPosition,
127 127 size=wx.DefaultSize, style=wx.WANTS_CHARS, ):
128 128 editwindow.EditWindow.__init__(self, parent, id, pos, size, style)
129 129 self._configure_scintilla()
130 130
131 131 self.Bind(wx.EVT_KEY_DOWN, self._on_key_down)
132 132 self.Bind(wx.EVT_KEY_UP, self._on_key_up)
133 133
134 134
135 135 def write(self, text, refresh=True):
136 136 """ Write given text to buffer, while translating the ansi escape
137 137 sequences.
138 138 """
139 139 # XXX: do not put print statements to sys.stdout/sys.stderr in
140 140 # this method, the print statements will call this method, as
141 141 # you will end up with an infinit loop
142 142 title = self.title_pat.split(text)
143 143 if len(title)>1:
144 144 self.title = title[-2]
145 145
146 146 text = self.title_pat.sub('', text)
147 147 segments = self.color_pat.split(text)
148 148 segment = segments.pop(0)
149 149 self.GotoPos(self.GetLength())
150 150 self.StartStyling(self.GetLength(), 0xFF)
151 151 try:
152 152 self.AppendText(segment)
153 153 except UnicodeDecodeError:
154 154 # XXX: Do I really want to skip the exception?
155 155 pass
156 156
157 157 if segments:
158 158 for ansi_tag, text in zip(segments[::2], segments[1::2]):
159 159 self.StartStyling(self.GetLength(), 0xFF)
160 160 try:
161 161 self.AppendText(text)
162 162 except UnicodeDecodeError:
163 163 # XXX: Do I really want to skip the exception?
164 164 pass
165 165
166 166 if ansi_tag not in self.ANSI_STYLES:
167 167 style = 0
168 168 else:
169 169 style = self.ANSI_STYLES[ansi_tag][0]
170 170
171 171 self.SetStyling(len(text), style)
172 172
173 173 self.GotoPos(self.GetLength())
174 174 if refresh:
175 175 current_time = time.time()
176 176 if current_time - self._last_refresh_time > 0.03:
177 177 wx.Yield()
178 # self.ProcessEvent(wx.PaintEvent())
178 179 self._last_refresh_time = current_time
179 180
180 181
181 182 def new_prompt(self, prompt):
182 183 """ Prints a prompt at start of line, and move the start of the
183 184 current block there.
184 185
185 186 The prompt can be given with ascii escape sequences.
186 187 """
187 188 self.write(prompt, refresh=False)
188 189 # now we update our cursor giving end of prompt
189 190 self.current_prompt_pos = self.GetLength()
190 191 self.current_prompt_line = self.GetCurrentLine()
191 192 wx.Yield()
192 193 self.EnsureCaretVisible()
193 194
194 195
195 196 def scroll_to_bottom(self):
196 197 maxrange = self.GetScrollRange(wx.VERTICAL)
197 198 self.ScrollLines(maxrange)
198 199
199 200
200 201 def pop_completion(self, possibilities, offset=0):
201 202 """ Pops up an autocompletion menu. Offset is the offset
202 203 in characters of the position at which the menu should
203 204 appear, relativ to the cursor.
204 205 """
205 206 self.AutoCompSetIgnoreCase(False)
206 207 self.AutoCompSetAutoHide(False)
207 208 self.AutoCompSetMaxHeight(len(possibilities))
208 209 self.AutoCompShow(offset, " ".join(possibilities))
209 210
210 211
211 212 def get_line_width(self):
212 213 """ Return the width of the line in characters.
213 214 """
214 215 return self.GetSize()[0]/self.GetCharWidth()
215 216
216 217 #--------------------------------------------------------------------------
217 218 # EditWindow API
218 219 #--------------------------------------------------------------------------
219 220
220 221 def OnUpdateUI(self, event):
221 222 """ Override the OnUpdateUI of the EditWindow class, to prevent
222 223 syntax highlighting both for faster redraw, and for more
223 224 consistent look and feel.
224 225 """
225 226
226 227 #--------------------------------------------------------------------------
227 228 # Private API
228 229 #--------------------------------------------------------------------------
229 230
230 231 def _apply_style(self):
231 232 """ Applies the colors for the different text elements and the
232 233 carret.
233 234 """
234 235 self.SetCaretForeground(self.carret_color)
235 236
236 237 #self.StyleClearAll()
237 238 self.StyleSetSpec(stc.STC_STYLE_BRACELIGHT,
238 239 "fore:#FF0000,back:#0000FF,bold")
239 240 self.StyleSetSpec(stc.STC_STYLE_BRACEBAD,
240 241 "fore:#000000,back:#FF0000,bold")
241 242
242 243 for style in self.ANSI_STYLES.values():
243 244 self.StyleSetSpec(style[0], "bold,fore:%s" % style[1])
244 245
245 246
246 247 def _configure_scintilla(self):
247 248 self.SetEOLMode(stc.STC_EOL_LF)
248 249
249 250 # Ctrl"+" or Ctrl "-" can be used to zoomin/zoomout the text inside
250 251 # the widget
251 252 self.CmdKeyAssign(ord('+'), stc.STC_SCMOD_CTRL, stc.STC_CMD_ZOOMIN)
252 253 self.CmdKeyAssign(ord('-'), stc.STC_SCMOD_CTRL, stc.STC_CMD_ZOOMOUT)
253 254 # Also allow Ctrl Shift "=" for poor non US keyboard users.
254 255 self.CmdKeyAssign(ord('='), stc.STC_SCMOD_CTRL|stc.STC_SCMOD_SHIFT,
255 256 stc.STC_CMD_ZOOMIN)
256 257
257 258 # Keys: we need to clear some of the keys the that don't play
258 259 # well with a console.
259 260 self.CmdKeyClear(ord('D'), stc.STC_SCMOD_CTRL)
260 261 self.CmdKeyClear(ord('L'), stc.STC_SCMOD_CTRL)
261 262 self.CmdKeyClear(ord('T'), stc.STC_SCMOD_CTRL)
262 263 self.CmdKeyClear(ord('A'), stc.STC_SCMOD_CTRL)
263 264
264 265 self.SetEOLMode(stc.STC_EOL_CRLF)
265 266 self.SetWrapMode(stc.STC_WRAP_CHAR)
266 267 self.SetWrapMode(stc.STC_WRAP_WORD)
267 268 self.SetBufferedDraw(True)
268 269 self.SetUseAntiAliasing(True)
269 270 self.SetLayoutCache(stc.STC_CACHE_PAGE)
270 271 self.SetUndoCollection(False)
271 272 self.SetUseTabs(True)
272 273 self.SetIndent(4)
273 274 self.SetTabWidth(4)
274 275
275 276 # we don't want scintilla's autocompletion to choose
276 277 # automaticaly out of a single choice list, as we pop it up
277 278 # automaticaly
278 279 self.AutoCompSetChooseSingle(False)
279 280 self.AutoCompSetMaxHeight(10)
280 281 # XXX: this doesn't seem to have an effect.
281 282 self.AutoCompSetFillUps('\n')
282 283
283 284 self.SetMargins(3, 3) #text is moved away from border with 3px
284 285 # Suppressing Scintilla margins
285 286 self.SetMarginWidth(0, 0)
286 287 self.SetMarginWidth(1, 0)
287 288 self.SetMarginWidth(2, 0)
288 289
289 290 self._apply_style()
290 291
291 292 # Xterm escape sequences
292 293 self.color_pat = re.compile('\x01?\x1b\[(.*?)m\x02?')
293 294 self.title_pat = re.compile('\x1b]0;(.*?)\x07')
294 295
295 296 #self.SetEdgeMode(stc.STC_EDGE_LINE)
296 297 #self.SetEdgeColumn(80)
297 298
298 299 # styles
299 300 p = self.style
300 301 self.StyleSetSpec(stc.STC_STYLE_DEFAULT, p['default'])
301 302 self.StyleClearAll()
302 303 self.StyleSetSpec(_STDOUT_STYLE, p['stdout'])
303 304 self.StyleSetSpec(_STDERR_STYLE, p['stderr'])
304 305 self.StyleSetSpec(_TRACE_STYLE, p['trace'])
305 306
306 307 self.StyleSetSpec(stc.STC_STYLE_BRACELIGHT, p['bracegood'])
307 308 self.StyleSetSpec(stc.STC_STYLE_BRACEBAD, p['bracebad'])
308 309 self.StyleSetSpec(stc.STC_P_COMMENTLINE, p['comment'])
309 310 self.StyleSetSpec(stc.STC_P_NUMBER, p['number'])
310 311 self.StyleSetSpec(stc.STC_P_STRING, p['string'])
311 312 self.StyleSetSpec(stc.STC_P_CHARACTER, p['char'])
312 313 self.StyleSetSpec(stc.STC_P_WORD, p['keyword'])
313 314 self.StyleSetSpec(stc.STC_P_WORD2, p['keyword'])
314 315 self.StyleSetSpec(stc.STC_P_TRIPLE, p['triple'])
315 316 self.StyleSetSpec(stc.STC_P_TRIPLEDOUBLE, p['tripledouble'])
316 317 self.StyleSetSpec(stc.STC_P_CLASSNAME, p['class'])
317 318 self.StyleSetSpec(stc.STC_P_DEFNAME, p['def'])
318 319 self.StyleSetSpec(stc.STC_P_OPERATOR, p['operator'])
319 320 self.StyleSetSpec(stc.STC_P_COMMENTBLOCK, p['comment'])
320 321
321 322 def _on_key_down(self, event, skip=True):
322 323 """ Key press callback used for correcting behavior for
323 324 console-like interfaces: the cursor is constraint to be after
324 325 the last prompt.
325 326
326 327 Return True if event as been catched.
327 328 """
328 329 catched = True
329 330 # Intercept some specific keys.
330 331 if event.KeyCode == ord('L') and event.ControlDown() :
331 332 self.scroll_to_bottom()
332 333 elif event.KeyCode == ord('K') and event.ControlDown() :
333 334 self.input_buffer = ''
334 335 elif event.KeyCode == ord('A') and event.ControlDown() :
335 336 self.GotoPos(self.GetLength())
336 337 self.SetSelectionStart(self.current_prompt_pos)
337 338 self.SetSelectionEnd(self.GetCurrentPos())
338 339 catched = True
339 340 elif event.KeyCode == ord('E') and event.ControlDown() :
340 341 self.GotoPos(self.GetLength())
341 342 catched = True
342 343 elif event.KeyCode == wx.WXK_PAGEUP:
343 344 self.ScrollPages(-1)
344 345 elif event.KeyCode == wx.WXK_PAGEDOWN:
345 346 self.ScrollPages(1)
346 347 elif event.KeyCode == wx.WXK_UP and event.ShiftDown():
347 348 self.ScrollLines(-1)
348 349 elif event.KeyCode == wx.WXK_DOWN and event.ShiftDown():
349 350 self.ScrollLines(1)
350 351 else:
351 352 catched = False
352 353
353 354 if self.AutoCompActive():
354 355 event.Skip()
355 356 else:
356 357 if event.KeyCode in (13, wx.WXK_NUMPAD_ENTER) and \
357 358 event.Modifiers in (wx.MOD_NONE, wx.MOD_WIN):
358 359 catched = True
359 360 self.CallTipCancel()
360 361 self.write('\n', refresh=False)
361 362 # Under windows scintilla seems to be doing funny stuff to the
362 363 # line returns here, but the getter for input_buffer filters
363 364 # this out.
364 365 if sys.platform == 'win32':
365 366 self.input_buffer = self.input_buffer
366 367 self._on_enter()
367 368
368 369 elif event.KeyCode == wx.WXK_HOME:
369 370 if event.Modifiers in (wx.MOD_NONE, wx.MOD_WIN):
370 371 self.GotoPos(self.current_prompt_pos)
371 372 catched = True
372 373
373 374 elif event.Modifiers == wx.MOD_SHIFT:
374 375 # FIXME: This behavior is not ideal: if the selection
375 376 # is already started, it will jump.
376 377 self.SetSelectionStart(self.current_prompt_pos)
377 378 self.SetSelectionEnd(self.GetCurrentPos())
378 379 catched = True
379 380
380 381 elif event.KeyCode == wx.WXK_UP:
381 382 if self.GetCurrentLine() > self.current_prompt_line:
382 383 if self.GetCurrentLine() == self.current_prompt_line + 1 \
383 384 and self.GetColumn(self.GetCurrentPos()) < \
384 385 self.GetColumn(self.current_prompt_pos):
385 386 self.GotoPos(self.current_prompt_pos)
386 387 else:
387 388 event.Skip()
388 389 catched = True
389 390
390 391 elif event.KeyCode in (wx.WXK_LEFT, wx.WXK_BACK):
391 392 if self.GetCurrentPos() > self.current_prompt_pos:
392 393 event.Skip()
393 394 catched = True
394 395
395 396 if skip and not catched:
396 397 # Put the cursor back in the edit region
397 398 if self.GetCurrentPos() < self.current_prompt_pos:
398 399 self.GotoPos(self.current_prompt_pos)
399 400 else:
400 401 event.Skip()
401 402
402 403 return catched
403 404
404 405
405 406 def _on_key_up(self, event, skip=True):
406 407 """ If cursor is outside the editing region, put it back.
407 408 """
408 409 event.Skip()
409 410 if self.GetCurrentPos() < self.current_prompt_pos:
410 411 self.GotoPos(self.current_prompt_pos)
411 412
412 413
413 414
414 415 if __name__ == '__main__':
415 416 # Some simple code to test the console widget.
416 417 class MainWindow(wx.Frame):
417 418 def __init__(self, parent, id, title):
418 419 wx.Frame.__init__(self, parent, id, title, size=(300,250))
419 420 self._sizer = wx.BoxSizer(wx.VERTICAL)
420 421 self.console_widget = ConsoleWidget(self)
421 422 self._sizer.Add(self.console_widget, 1, wx.EXPAND)
422 423 self.SetSizer(self._sizer)
423 424 self.SetAutoLayout(1)
424 425 self.Show(True)
425 426
426 427 app = wx.PySimpleApp()
427 428 w = MainWindow(None, wx.ID_ANY, 'ConsoleWidget')
428 429 w.SetSize((780, 460))
429 430 w.Show()
430 431
431 432 app.MainLoop()
432 433
433 434
@@ -1,526 +1,527 b''
1 1 # encoding: utf-8 -*- test-case-name:
2 2 # FIXME: Need to add tests.
3 3 # ipython1.frontend.wx.tests.test_wx_frontend -*-
4 4
5 5 """Classes to provide a Wx frontend to the
6 6 IPython.kernel.core.interpreter.
7 7
8 8 This class inherits from ConsoleWidget, that provides a console-like
9 9 widget to provide a text-rendering widget suitable for a terminal.
10 10 """
11 11
12 12 __docformat__ = "restructuredtext en"
13 13
14 14 #-------------------------------------------------------------------------------
15 15 # Copyright (C) 2008 The IPython Development Team
16 16 #
17 17 # Distributed under the terms of the BSD License. The full license is in
18 18 # the file COPYING, distributed as part of this software.
19 19 #-------------------------------------------------------------------------------
20 20
21 21 #-------------------------------------------------------------------------------
22 22 # Imports
23 23 #-------------------------------------------------------------------------------
24 24
25 25 # Major library imports
26 26 import re
27 27 import __builtin__
28 28 from time import sleep
29 29 import sys
30 30 from threading import Lock
31 31 import string
32 32
33 33 import wx
34 34 from wx import stc
35 35
36 36 # Ipython-specific imports.
37 37 from IPython.frontend._process import PipedProcess
38 38 from console_widget import ConsoleWidget
39 39 from IPython.frontend.prefilterfrontend import PrefilterFrontEnd
40 40
41 41 #-------------------------------------------------------------------------------
42 42 # Constants
43 43 #-------------------------------------------------------------------------------
44 44
45 45 _COMPLETE_BUFFER_BG = '#FAFAF1' # Nice green
46 46 _INPUT_BUFFER_BG = '#FDFFD3' # Nice yellow
47 47 _ERROR_BG = '#FFF1F1' # Nice red
48 48
49 49 _COMPLETE_BUFFER_MARKER = 31
50 50 _ERROR_MARKER = 30
51 51 _INPUT_MARKER = 29
52 52
53 53 prompt_in1 = \
54 54 '\n\x01\x1b[0;34m\x02In [\x01\x1b[1;34m\x02$number\x01\x1b[0;34m\x02]: \x01\x1b[0m\x02'
55 55
56 56 prompt_out = \
57 57 '\x01\x1b[0;31m\x02Out[\x01\x1b[1;31m\x02$number\x01\x1b[0;31m\x02]: \x01\x1b[0m\x02'
58 58
59 59 #-------------------------------------------------------------------------------
60 60 # Classes to implement the Wx frontend
61 61 #-------------------------------------------------------------------------------
62 62 class WxController(ConsoleWidget, PrefilterFrontEnd):
63 63 """Classes to provide a Wx frontend to the
64 64 IPython.kernel.core.interpreter.
65 65
66 66 This class inherits from ConsoleWidget, that provides a console-like
67 67 widget to provide a text-rendering widget suitable for a terminal.
68 68 """
69 69
70 70 output_prompt_template = string.Template(prompt_out)
71 71
72 72 input_prompt_template = string.Template(prompt_in1)
73 73
74 74 # Print debug info on what is happening to the console.
75 75 debug = False
76 76
77 77 # The title of the terminal, as captured through the ANSI escape
78 78 # sequences.
79 79 def _set_title(self, title):
80 80 return self.Parent.SetTitle(title)
81 81
82 82 def _get_title(self):
83 83 return self.Parent.GetTitle()
84 84
85 85 title = property(_get_title, _set_title)
86 86
87 87
88 88 # The buffer being edited.
89 89 # We are duplicating the definition here because of multiple
90 90 # inheritence
91 91 def _set_input_buffer(self, string):
92 92 ConsoleWidget._set_input_buffer(self, string)
93 93 self._colorize_input_buffer()
94 94
95 95 def _get_input_buffer(self):
96 96 """ Returns the text in current edit buffer.
97 97 """
98 98 return ConsoleWidget._get_input_buffer(self)
99 99
100 100 input_buffer = property(_get_input_buffer, _set_input_buffer)
101 101
102 102
103 103 #--------------------------------------------------------------------------
104 104 # Private Attributes
105 105 #--------------------------------------------------------------------------
106 106
107 107 # A flag governing the behavior of the input. Can be:
108 108 #
109 109 # 'readline' for readline-like behavior with a prompt
110 110 # and an edit buffer.
111 111 # 'raw_input' similar to readline, but triggered by a raw-input
112 112 # call. Can be used by subclasses to act differently.
113 113 # 'subprocess' for sending the raw input directly to a
114 114 # subprocess.
115 115 # 'buffering' for buffering of the input, that will be used
116 116 # when the input state switches back to another state.
117 117 _input_state = 'readline'
118 118
119 119 # Attribute to store reference to the pipes of a subprocess, if we
120 120 # are running any.
121 121 _running_process = False
122 122
123 123 # A queue for writing fast streams to the screen without flooding the
124 124 # event loop
125 125 _out_buffer = []
126 126
127 127 # A lock to lock the _out_buffer to make sure we don't empty it
128 128 # while it is being swapped
129 129 _out_buffer_lock = Lock()
130 130
131 131 # The different line markers used to higlight the prompts.
132 132 _markers = dict()
133 133
134 134 #--------------------------------------------------------------------------
135 135 # Public API
136 136 #--------------------------------------------------------------------------
137 137
138 138 def __init__(self, parent, id=wx.ID_ANY, pos=wx.DefaultPosition,
139 139 size=wx.DefaultSize,
140 140 style=wx.CLIP_CHILDREN|wx.WANTS_CHARS,
141 141 *args, **kwds):
142 142 """ Create Shell instance.
143 143 """
144 144 ConsoleWidget.__init__(self, parent, id, pos, size, style)
145 145 PrefilterFrontEnd.__init__(self, **kwds)
146 146
147 147 # Marker for complete buffer.
148 148 self.MarkerDefine(_COMPLETE_BUFFER_MARKER, stc.STC_MARK_BACKGROUND,
149 149 background=_COMPLETE_BUFFER_BG)
150 150 # Marker for current input buffer.
151 151 self.MarkerDefine(_INPUT_MARKER, stc.STC_MARK_BACKGROUND,
152 152 background=_INPUT_BUFFER_BG)
153 153 # Marker for tracebacks.
154 154 self.MarkerDefine(_ERROR_MARKER, stc.STC_MARK_BACKGROUND,
155 155 background=_ERROR_BG)
156 156
157 157 # A time for flushing the write buffer
158 158 BUFFER_FLUSH_TIMER_ID = 100
159 159 self._buffer_flush_timer = wx.Timer(self, BUFFER_FLUSH_TIMER_ID)
160 160 wx.EVT_TIMER(self, BUFFER_FLUSH_TIMER_ID, self._buffer_flush)
161 161
162 162 if 'debug' in kwds:
163 163 self.debug = kwds['debug']
164 164 kwds.pop('debug')
165 165
166 166 # Inject self in namespace, for debug
167 167 if self.debug:
168 168 self.shell.user_ns['self'] = self
169 169
170 170
171 171 def raw_input(self, prompt):
172 172 """ A replacement from python's raw_input.
173 173 """
174 174 self.new_prompt(prompt)
175 175 self._input_state = 'raw_input'
176 176 if hasattr(self, '_cursor'):
177 177 del self._cursor
178 178 self.SetCursor(wx.StockCursor(wx.CURSOR_CROSS))
179 179 self.waiting = True
180 180 self.__old_on_enter = self._on_enter
181 181 def my_on_enter():
182 182 self.waiting = False
183 183 self._on_enter = my_on_enter
184 184 # XXX: Busy waiting, ugly.
185 185 while self.waiting:
186 186 wx.Yield()
187 187 sleep(0.1)
188 188 self._on_enter = self.__old_on_enter
189 189 self._input_state = 'buffering'
190 190 self._cursor = wx.BusyCursor()
191 191 return self.input_buffer.rstrip('\n')
192 192
193 193
194 194 def system_call(self, command_string):
195 195 self._input_state = 'subprocess'
196 196 self._running_process = PipedProcess(command_string,
197 197 out_callback=self.buffered_write,
198 198 end_callback = self._end_system_call)
199 199 self._running_process.start()
200 200 # XXX: another one of these polling loops to have a blocking
201 201 # call
202 202 wx.Yield()
203 203 while self._running_process:
204 204 wx.Yield()
205 205 sleep(0.1)
206 206 # Be sure to flush the buffer.
207 207 self._buffer_flush(event=None)
208 208
209 209
210 210 def do_calltip(self):
211 211 """ Analyse current and displays useful calltip for it.
212 212 """
213 213 if self.debug:
214 214 print >>sys.__stdout__, "do_calltip"
215 215 separators = re.compile('[\s\{\}\[\]\(\)\= ,:]')
216 216 symbol = self.input_buffer
217 217 symbol_string = separators.split(symbol)[-1]
218 218 base_symbol_string = symbol_string.split('.')[0]
219 219 if base_symbol_string in self.shell.user_ns:
220 220 symbol = self.shell.user_ns[base_symbol_string]
221 221 elif base_symbol_string in self.shell.user_global_ns:
222 222 symbol = self.shell.user_global_ns[base_symbol_string]
223 223 elif base_symbol_string in __builtin__.__dict__:
224 224 symbol = __builtin__.__dict__[base_symbol_string]
225 225 else:
226 226 return False
227 227 try:
228 228 for name in symbol_string.split('.')[1:] + ['__doc__']:
229 229 symbol = getattr(symbol, name)
230 230 self.AutoCompCancel()
231 231 wx.Yield()
232 232 self.CallTipShow(self.GetCurrentPos(), symbol)
233 233 except:
234 234 # The retrieve symbol couldn't be converted to a string
235 235 pass
236 236
237 237
238 238 def _popup_completion(self, create=False):
239 239 """ Updates the popup completion menu if it exists. If create is
240 240 true, open the menu.
241 241 """
242 242 if self.debug:
243 243 print >>sys.__stdout__, "_popup_completion"
244 244 line = self.input_buffer
245 245 if (self.AutoCompActive() and line and not line[-1] == '.') \
246 246 or create==True:
247 247 suggestion, completions = self.complete(line)
248 248 offset=0
249 249 if completions:
250 250 complete_sep = re.compile('[\s\{\}\[\]\(\)\= ,:]')
251 251 residual = complete_sep.split(line)[-1]
252 252 offset = len(residual)
253 253 self.pop_completion(completions, offset=offset)
254 254 if self.debug:
255 255 print >>sys.__stdout__, completions
256 256
257 257
258 258 def buffered_write(self, text):
259 259 """ A write method for streams, that caches the stream in order
260 260 to avoid flooding the event loop.
261 261
262 262 This can be called outside of the main loop, in separate
263 263 threads.
264 264 """
265 265 self._out_buffer_lock.acquire()
266 266 self._out_buffer.append(text)
267 267 self._out_buffer_lock.release()
268 268 if not self._buffer_flush_timer.IsRunning():
269 269 wx.CallAfter(self._buffer_flush_timer.Start,
270 270 milliseconds=100, oneShot=True)
271 271
272 272
273 273 #--------------------------------------------------------------------------
274 274 # LineFrontEnd interface
275 275 #--------------------------------------------------------------------------
276 276
277 277 def execute(self, python_string, raw_string=None):
278 278 self._input_state = 'buffering'
279 279 self.CallTipCancel()
280 280 self._cursor = wx.BusyCursor()
281 281 if raw_string is None:
282 282 raw_string = python_string
283 283 end_line = self.current_prompt_line \
284 284 + max(1, len(raw_string.split('\n'))-1)
285 285 for i in range(self.current_prompt_line, end_line):
286 286 if i in self._markers:
287 287 self.MarkerDeleteHandle(self._markers[i])
288 288 self._markers[i] = self.MarkerAdd(i, _COMPLETE_BUFFER_MARKER)
289 289 # Use a callafter to update the display robustly under windows
290 290 def callback():
291 291 self.GotoPos(self.GetLength())
292 292 PrefilterFrontEnd.execute(self, python_string,
293 293 raw_string=raw_string)
294 294 wx.CallAfter(callback)
295 295
296 296 def save_output_hooks(self):
297 297 self.__old_raw_input = __builtin__.raw_input
298 298 PrefilterFrontEnd.save_output_hooks(self)
299 299
300 300 def capture_output(self):
301 301 __builtin__.raw_input = self.raw_input
302 302 self.SetLexer(stc.STC_LEX_NULL)
303 303 PrefilterFrontEnd.capture_output(self)
304 304
305 305
306 306 def release_output(self):
307 307 __builtin__.raw_input = self.__old_raw_input
308 308 PrefilterFrontEnd.release_output(self)
309 309 self.SetLexer(stc.STC_LEX_PYTHON)
310 310
311 311
312 312 def after_execute(self):
313 313 PrefilterFrontEnd.after_execute(self)
314 314 # Clear the wait cursor
315 315 if hasattr(self, '_cursor'):
316 316 del self._cursor
317 317 self.SetCursor(wx.StockCursor(wx.CURSOR_CHAR))
318 318
319 319
320 320 def show_traceback(self):
321 321 start_line = self.GetCurrentLine()
322 322 PrefilterFrontEnd.show_traceback(self)
323 wx.Yield()
323 self.ProcessEvent(wx.PaintEvent())
324 #wx.Yield()
324 325 for i in range(start_line, self.GetCurrentLine()):
325 326 self._markers[i] = self.MarkerAdd(i, _ERROR_MARKER)
326 327
327 328
328 329 #--------------------------------------------------------------------------
329 330 # FrontEndBase interface
330 331 #--------------------------------------------------------------------------
331 332
332 333 def render_error(self, e):
333 334 start_line = self.GetCurrentLine()
334 335 self.write('\n' + e + '\n')
335 336 for i in range(start_line, self.GetCurrentLine()):
336 337 self._markers[i] = self.MarkerAdd(i, _ERROR_MARKER)
337 338
338 339
339 340 #--------------------------------------------------------------------------
340 341 # ConsoleWidget interface
341 342 #--------------------------------------------------------------------------
342 343
343 344 def new_prompt(self, prompt):
344 345 """ Display a new prompt, and start a new input buffer.
345 346 """
346 347 self._input_state = 'readline'
347 348 ConsoleWidget.new_prompt(self, prompt)
348 349 i = self.current_prompt_line
349 350 self._markers[i] = self.MarkerAdd(i, _INPUT_MARKER)
350 351
351 352
352 353 def write(self, *args, **kwargs):
353 354 # Avoid multiple inheritence, be explicit about which
354 355 # parent method class gets called
355 356 ConsoleWidget.write(self, *args, **kwargs)
356 357
357 358
358 359 def _on_key_down(self, event, skip=True):
359 360 """ Capture the character events, let the parent
360 361 widget handle them, and put our logic afterward.
361 362 """
362 363 # FIXME: This method needs to be broken down in smaller ones.
363 364 current_line_number = self.GetCurrentLine()
364 365 if event.KeyCode in (ord('c'), ord('C')) and event.ControlDown():
365 366 # Capture Control-C
366 367 if self._input_state == 'subprocess':
367 368 if self.debug:
368 369 print >>sys.__stderr__, 'Killing running process'
369 370 if hasattr(self._running_process, 'process'):
370 371 self._running_process.process.kill()
371 372 elif self._input_state == 'buffering':
372 373 if self.debug:
373 374 print >>sys.__stderr__, 'Raising KeyboardInterrupt'
374 375 raise KeyboardInterrupt
375 376 # XXX: We need to make really sure we
376 377 # get back to a prompt.
377 378 elif self._input_state == 'subprocess' and (
378 379 ( event.KeyCode<256 and
379 380 not event.ControlDown() )
380 381 or
381 382 ( event.KeyCode in (ord('d'), ord('D')) and
382 383 event.ControlDown())):
383 384 # We are running a process, we redirect keys.
384 385 ConsoleWidget._on_key_down(self, event, skip=skip)
385 386 char = chr(event.KeyCode)
386 387 # Deal with some inconsistency in wx keycodes:
387 388 if char == '\r':
388 389 char = '\n'
389 390 elif not event.ShiftDown():
390 391 char = char.lower()
391 392 if event.ControlDown() and event.KeyCode in (ord('d'), ord('D')):
392 393 char = '\04'
393 394 self._running_process.process.stdin.write(char)
394 395 self._running_process.process.stdin.flush()
395 396 elif event.KeyCode in (ord('('), 57):
396 397 # Calltips
397 398 event.Skip()
398 399 self.do_calltip()
399 400 elif self.AutoCompActive() and not event.KeyCode == ord('\t'):
400 401 event.Skip()
401 402 if event.KeyCode in (wx.WXK_BACK, wx.WXK_DELETE):
402 403 wx.CallAfter(self._popup_completion, create=True)
403 404 elif not event.KeyCode in (wx.WXK_UP, wx.WXK_DOWN, wx.WXK_LEFT,
404 405 wx.WXK_RIGHT, wx.WXK_ESCAPE):
405 406 wx.CallAfter(self._popup_completion)
406 407 else:
407 408 # Up history
408 409 if event.KeyCode == wx.WXK_UP and (
409 410 ( current_line_number == self.current_prompt_line and
410 411 event.Modifiers in (wx.MOD_NONE, wx.MOD_WIN) )
411 412 or event.ControlDown() ):
412 413 new_buffer = self.get_history_previous(
413 414 self.input_buffer)
414 415 if new_buffer is not None:
415 416 self.input_buffer = new_buffer
416 417 if self.GetCurrentLine() > self.current_prompt_line:
417 418 # Go to first line, for seemless history up.
418 419 self.GotoPos(self.current_prompt_pos)
419 420 # Down history
420 421 elif event.KeyCode == wx.WXK_DOWN and (
421 422 ( current_line_number == self.LineCount -1 and
422 423 event.Modifiers in (wx.MOD_NONE, wx.MOD_WIN) )
423 424 or event.ControlDown() ):
424 425 new_buffer = self.get_history_next()
425 426 if new_buffer is not None:
426 427 self.input_buffer = new_buffer
427 428 # Tab-completion
428 429 elif event.KeyCode == ord('\t'):
429 430 last_line = self.input_buffer.split('\n')[-1]
430 431 if not re.match(r'^\s*$', last_line):
431 432 self.complete_current_input()
432 433 if self.AutoCompActive():
433 434 wx.CallAfter(self._popup_completion, create=True)
434 435 else:
435 436 event.Skip()
436 437 else:
437 438 ConsoleWidget._on_key_down(self, event, skip=skip)
438 439
439 440
440 441 def _on_key_up(self, event, skip=True):
441 442 """ Called when any key is released.
442 443 """
443 444 if event.KeyCode in (59, ord('.')):
444 445 # Intercepting '.'
445 446 event.Skip()
446 447 wx.CallAfter(self._popup_completion, create=True)
447 448 else:
448 449 ConsoleWidget._on_key_up(self, event, skip=skip)
449 450
450 451
451 452 def _on_enter(self):
452 453 """ Called on return key down, in readline input_state.
453 454 """
454 455 if self.debug:
455 456 print >>sys.__stdout__, repr(self.input_buffer)
456 457 PrefilterFrontEnd._on_enter(self)
457 458
458 459
459 460 #--------------------------------------------------------------------------
460 461 # EditWindow API
461 462 #--------------------------------------------------------------------------
462 463
463 464 def OnUpdateUI(self, event):
464 465 """ Override the OnUpdateUI of the EditWindow class, to prevent
465 466 syntax highlighting both for faster redraw, and for more
466 467 consistent look and feel.
467 468 """
468 469 if not self._input_state == 'readline':
469 470 ConsoleWidget.OnUpdateUI(self, event)
470 471
471 472 #--------------------------------------------------------------------------
472 473 # Private API
473 474 #--------------------------------------------------------------------------
474 475
475 476 def _end_system_call(self):
476 477 """ Called at the end of a system call.
477 478 """
478 479 self._input_state = 'buffering'
479 480 self._running_process = False
480 481
481 482
482 483 def _buffer_flush(self, event):
483 484 """ Called by the timer to flush the write buffer.
484 485
485 486 This is always called in the mainloop, by the wx timer.
486 487 """
487 488 self._out_buffer_lock.acquire()
488 489 _out_buffer = self._out_buffer
489 490 self._out_buffer = []
490 491 self._out_buffer_lock.release()
491 492 self.write(''.join(_out_buffer), refresh=False)
492 493
493 494
494 495 def _colorize_input_buffer(self):
495 496 """ Keep the input buffer lines at a bright color.
496 497 """
497 498 if not self._input_state in ('readline', 'raw_input'):
498 499 return
499 500 end_line = self.GetCurrentLine()
500 501 if not sys.platform == 'win32':
501 502 end_line += 1
502 503 for i in range(self.current_prompt_line, end_line):
503 504 if i in self._markers:
504 505 self.MarkerDeleteHandle(self._markers[i])
505 506 self._markers[i] = self.MarkerAdd(i, _INPUT_MARKER)
506 507
507 508
508 509 if __name__ == '__main__':
509 510 class MainWindow(wx.Frame):
510 511 def __init__(self, parent, id, title):
511 512 wx.Frame.__init__(self, parent, id, title, size=(300,250))
512 513 self._sizer = wx.BoxSizer(wx.VERTICAL)
513 514 self.shell = WxController(self)
514 515 self._sizer.Add(self.shell, 1, wx.EXPAND)
515 516 self.SetSizer(self._sizer)
516 517 self.SetAutoLayout(1)
517 518 self.Show(True)
518 519
519 520 app = wx.PySimpleApp()
520 521 frame = MainWindow(None, wx.ID_ANY, 'Ipython')
521 522 frame.shell.SetFocus()
522 523 frame.SetSize((680, 460))
523 524 self = frame.shell
524 525
525 526 app.MainLoop()
526 527
General Comments 0
You need to be logged in to leave comments. Login now