##// END OF EJS Templates
Get synchronous writes working under windows.
gvaroquaux -
Show More
@@ -1,436 +1,433
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 # Maybe this is faster than wx.Yield(), this is certainly
178 # more robust under windows, as it avoids recursive
179 # Yields.
180 self.ProcessEvent(wx.PaintEvent())
177 wx.Yield()
181 178 self._last_refresh_time = current_time
182 179
183 180
184 181 def new_prompt(self, prompt):
185 182 """ Prints a prompt at start of line, and move the start of the
186 183 current block there.
187 184
188 185 The prompt can be given with ascii escape sequences.
189 186 """
190 187 self.write(prompt, refresh=False)
191 188 # now we update our cursor giving end of prompt
192 189 self.current_prompt_pos = self.GetLength()
193 190 self.current_prompt_line = self.GetCurrentLine()
194 191 wx.Yield()
195 192 self.EnsureCaretVisible()
196 193
197 194
198 195 def scroll_to_bottom(self):
199 196 maxrange = self.GetScrollRange(wx.VERTICAL)
200 197 self.ScrollLines(maxrange)
201 198
202 199
203 200 def pop_completion(self, possibilities, offset=0):
204 201 """ Pops up an autocompletion menu. Offset is the offset
205 202 in characters of the position at which the menu should
206 203 appear, relativ to the cursor.
207 204 """
208 205 self.AutoCompSetIgnoreCase(False)
209 206 self.AutoCompSetAutoHide(False)
210 207 self.AutoCompSetMaxHeight(len(possibilities))
211 208 self.AutoCompShow(offset, " ".join(possibilities))
212 209
213 210
214 211 def get_line_width(self):
215 212 """ Return the width of the line in characters.
216 213 """
217 214 return self.GetSize()[0]/self.GetCharWidth()
218 215
219 216 #--------------------------------------------------------------------------
220 217 # EditWindow API
221 218 #--------------------------------------------------------------------------
222 219
223 220 def OnUpdateUI(self, event):
224 221 """ Override the OnUpdateUI of the EditWindow class, to prevent
225 222 syntax highlighting both for faster redraw, and for more
226 223 consistent look and feel.
227 224 """
228 225
229 226 #--------------------------------------------------------------------------
230 227 # Private API
231 228 #--------------------------------------------------------------------------
232 229
233 230 def _apply_style(self):
234 231 """ Applies the colors for the different text elements and the
235 232 carret.
236 233 """
237 234 self.SetCaretForeground(self.carret_color)
238 235
239 236 #self.StyleClearAll()
240 237 self.StyleSetSpec(stc.STC_STYLE_BRACELIGHT,
241 238 "fore:#FF0000,back:#0000FF,bold")
242 239 self.StyleSetSpec(stc.STC_STYLE_BRACEBAD,
243 240 "fore:#000000,back:#FF0000,bold")
244 241
245 242 for style in self.ANSI_STYLES.values():
246 243 self.StyleSetSpec(style[0], "bold,fore:%s" % style[1])
247 244
248 245
249 246 def _configure_scintilla(self):
250 247 self.SetEOLMode(stc.STC_EOL_LF)
251 248
252 249 # Ctrl"+" or Ctrl "-" can be used to zoomin/zoomout the text inside
253 250 # the widget
254 251 self.CmdKeyAssign(ord('+'), stc.STC_SCMOD_CTRL, stc.STC_CMD_ZOOMIN)
255 252 self.CmdKeyAssign(ord('-'), stc.STC_SCMOD_CTRL, stc.STC_CMD_ZOOMOUT)
256 253 # Also allow Ctrl Shift "=" for poor non US keyboard users.
257 254 self.CmdKeyAssign(ord('='), stc.STC_SCMOD_CTRL|stc.STC_SCMOD_SHIFT,
258 255 stc.STC_CMD_ZOOMIN)
259 256
260 257 # Keys: we need to clear some of the keys the that don't play
261 258 # well with a console.
262 259 self.CmdKeyClear(ord('D'), stc.STC_SCMOD_CTRL)
263 260 self.CmdKeyClear(ord('L'), stc.STC_SCMOD_CTRL)
264 261 self.CmdKeyClear(ord('T'), stc.STC_SCMOD_CTRL)
265 262 self.CmdKeyClear(ord('A'), stc.STC_SCMOD_CTRL)
266 263
267 264 self.SetEOLMode(stc.STC_EOL_CRLF)
268 265 self.SetWrapMode(stc.STC_WRAP_CHAR)
269 266 self.SetWrapMode(stc.STC_WRAP_WORD)
270 267 self.SetBufferedDraw(True)
271 268 self.SetUseAntiAliasing(True)
272 269 self.SetLayoutCache(stc.STC_CACHE_PAGE)
273 270 self.SetUndoCollection(False)
274 271 self.SetUseTabs(True)
275 272 self.SetIndent(4)
276 273 self.SetTabWidth(4)
277 274
278 275 # we don't want scintilla's autocompletion to choose
279 276 # automaticaly out of a single choice list, as we pop it up
280 277 # automaticaly
281 278 self.AutoCompSetChooseSingle(False)
282 279 self.AutoCompSetMaxHeight(10)
283 280 # XXX: this doesn't seem to have an effect.
284 281 self.AutoCompSetFillUps('\n')
285 282
286 283 self.SetMargins(3, 3) #text is moved away from border with 3px
287 284 # Suppressing Scintilla margins
288 285 self.SetMarginWidth(0, 0)
289 286 self.SetMarginWidth(1, 0)
290 287 self.SetMarginWidth(2, 0)
291 288
292 289 self._apply_style()
293 290
294 291 # Xterm escape sequences
295 292 self.color_pat = re.compile('\x01?\x1b\[(.*?)m\x02?')
296 293 self.title_pat = re.compile('\x1b]0;(.*?)\x07')
297 294
298 295 #self.SetEdgeMode(stc.STC_EDGE_LINE)
299 296 #self.SetEdgeColumn(80)
300 297
301 298 # styles
302 299 p = self.style
303 300 self.StyleSetSpec(stc.STC_STYLE_DEFAULT, p['default'])
304 301 self.StyleClearAll()
305 302 self.StyleSetSpec(_STDOUT_STYLE, p['stdout'])
306 303 self.StyleSetSpec(_STDERR_STYLE, p['stderr'])
307 304 self.StyleSetSpec(_TRACE_STYLE, p['trace'])
308 305
309 306 self.StyleSetSpec(stc.STC_STYLE_BRACELIGHT, p['bracegood'])
310 307 self.StyleSetSpec(stc.STC_STYLE_BRACEBAD, p['bracebad'])
311 308 self.StyleSetSpec(stc.STC_P_COMMENTLINE, p['comment'])
312 309 self.StyleSetSpec(stc.STC_P_NUMBER, p['number'])
313 310 self.StyleSetSpec(stc.STC_P_STRING, p['string'])
314 311 self.StyleSetSpec(stc.STC_P_CHARACTER, p['char'])
315 312 self.StyleSetSpec(stc.STC_P_WORD, p['keyword'])
316 313 self.StyleSetSpec(stc.STC_P_WORD2, p['keyword'])
317 314 self.StyleSetSpec(stc.STC_P_TRIPLE, p['triple'])
318 315 self.StyleSetSpec(stc.STC_P_TRIPLEDOUBLE, p['tripledouble'])
319 316 self.StyleSetSpec(stc.STC_P_CLASSNAME, p['class'])
320 317 self.StyleSetSpec(stc.STC_P_DEFNAME, p['def'])
321 318 self.StyleSetSpec(stc.STC_P_OPERATOR, p['operator'])
322 319 self.StyleSetSpec(stc.STC_P_COMMENTBLOCK, p['comment'])
323 320
324 321 def _on_key_down(self, event, skip=True):
325 322 """ Key press callback used for correcting behavior for
326 323 console-like interfaces: the cursor is constraint to be after
327 324 the last prompt.
328 325
329 326 Return True if event as been catched.
330 327 """
331 328 catched = True
332 329 # Intercept some specific keys.
333 330 if event.KeyCode == ord('L') and event.ControlDown() :
334 331 self.scroll_to_bottom()
335 332 elif event.KeyCode == ord('K') and event.ControlDown() :
336 333 self.input_buffer = ''
337 334 elif event.KeyCode == ord('A') and event.ControlDown() :
338 335 self.GotoPos(self.GetLength())
339 336 self.SetSelectionStart(self.current_prompt_pos)
340 337 self.SetSelectionEnd(self.GetCurrentPos())
341 338 catched = True
342 339 elif event.KeyCode == ord('E') and event.ControlDown() :
343 340 self.GotoPos(self.GetLength())
344 341 catched = True
345 342 elif event.KeyCode == wx.WXK_PAGEUP:
346 343 self.ScrollPages(-1)
347 344 elif event.KeyCode == wx.WXK_PAGEDOWN:
348 345 self.ScrollPages(1)
349 346 elif event.KeyCode == wx.WXK_UP and event.ShiftDown():
350 347 self.ScrollLines(-1)
351 348 elif event.KeyCode == wx.WXK_DOWN and event.ShiftDown():
352 349 self.ScrollLines(1)
353 350 else:
354 351 catched = False
355 352
356 353 if self.AutoCompActive():
357 354 event.Skip()
358 355 else:
359 356 if event.KeyCode in (13, wx.WXK_NUMPAD_ENTER) and \
360 357 event.Modifiers in (wx.MOD_NONE, wx.MOD_WIN):
361 358 catched = True
362 359 self.CallTipCancel()
363 360 self.write('\n', refresh=False)
364 361 # Under windows scintilla seems to be doing funny stuff to the
365 362 # line returns here, but the getter for input_buffer filters
366 363 # this out.
367 364 if sys.platform == 'win32':
368 365 self.input_buffer = self.input_buffer
369 366 self._on_enter()
370 367
371 368 elif event.KeyCode == wx.WXK_HOME:
372 369 if event.Modifiers in (wx.MOD_NONE, wx.MOD_WIN):
373 370 self.GotoPos(self.current_prompt_pos)
374 371 catched = True
375 372
376 373 elif event.Modifiers == wx.MOD_SHIFT:
377 374 # FIXME: This behavior is not ideal: if the selection
378 375 # is already started, it will jump.
379 376 self.SetSelectionStart(self.current_prompt_pos)
380 377 self.SetSelectionEnd(self.GetCurrentPos())
381 378 catched = True
382 379
383 380 elif event.KeyCode == wx.WXK_UP:
384 381 if self.GetCurrentLine() > self.current_prompt_line:
385 382 if self.GetCurrentLine() == self.current_prompt_line + 1 \
386 383 and self.GetColumn(self.GetCurrentPos()) < \
387 384 self.GetColumn(self.current_prompt_pos):
388 385 self.GotoPos(self.current_prompt_pos)
389 386 else:
390 387 event.Skip()
391 388 catched = True
392 389
393 390 elif event.KeyCode in (wx.WXK_LEFT, wx.WXK_BACK):
394 391 if self.GetCurrentPos() > self.current_prompt_pos:
395 392 event.Skip()
396 393 catched = True
397 394
398 395 if skip and not catched:
399 396 # Put the cursor back in the edit region
400 397 if self.GetCurrentPos() < self.current_prompt_pos:
401 398 self.GotoPos(self.current_prompt_pos)
402 399 else:
403 400 event.Skip()
404 401
405 402 return catched
406 403
407 404
408 405 def _on_key_up(self, event, skip=True):
409 406 """ If cursor is outside the editing region, put it back.
410 407 """
411 408 event.Skip()
412 409 if self.GetCurrentPos() < self.current_prompt_pos:
413 410 self.GotoPos(self.current_prompt_pos)
414 411
415 412
416 413
417 414 if __name__ == '__main__':
418 415 # Some simple code to test the console widget.
419 416 class MainWindow(wx.Frame):
420 417 def __init__(self, parent, id, title):
421 418 wx.Frame.__init__(self, parent, id, title, size=(300,250))
422 419 self._sizer = wx.BoxSizer(wx.VERTICAL)
423 420 self.console_widget = ConsoleWidget(self)
424 421 self._sizer.Add(self.console_widget, 1, wx.EXPAND)
425 422 self.SetSizer(self._sizer)
426 423 self.SetAutoLayout(1)
427 424 self.Show(True)
428 425
429 426 app = wx.PySimpleApp()
430 427 w = MainWindow(None, wx.ID_ANY, 'ConsoleWidget')
431 428 w.SetSize((780, 460))
432 429 w.Show()
433 430
434 431 app.MainLoop()
435 432
436 433
@@ -1,524 +1,526
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 # Update the display:
290 wx.Yield()
291 self.GotoPos(self.GetLength())
292 PrefilterFrontEnd.execute(self, python_string, raw_string=raw_string)
289 # Use a callafter to update the display robustly under windows
290 def callback():
291 self.GotoPos(self.GetLength())
292 PrefilterFrontEnd.execute(self, python_string,
293 raw_string=raw_string)
294 wx.CallAfter(callback)
293 295
294 296 def save_output_hooks(self):
295 297 self.__old_raw_input = __builtin__.raw_input
296 298 PrefilterFrontEnd.save_output_hooks(self)
297 299
298 300 def capture_output(self):
299 301 __builtin__.raw_input = self.raw_input
300 302 self.SetLexer(stc.STC_LEX_NULL)
301 303 PrefilterFrontEnd.capture_output(self)
302 304
303 305
304 306 def release_output(self):
305 307 __builtin__.raw_input = self.__old_raw_input
306 308 PrefilterFrontEnd.release_output(self)
307 309 self.SetLexer(stc.STC_LEX_PYTHON)
308 310
309 311
310 312 def after_execute(self):
311 313 PrefilterFrontEnd.after_execute(self)
312 314 # Clear the wait cursor
313 315 if hasattr(self, '_cursor'):
314 316 del self._cursor
315 317 self.SetCursor(wx.StockCursor(wx.CURSOR_CHAR))
316 318
317 319
318 320 def show_traceback(self):
319 321 start_line = self.GetCurrentLine()
320 322 PrefilterFrontEnd.show_traceback(self)
321 323 wx.Yield()
322 324 for i in range(start_line, self.GetCurrentLine()):
323 325 self._markers[i] = self.MarkerAdd(i, _ERROR_MARKER)
324 326
325 327
326 328 #--------------------------------------------------------------------------
327 329 # FrontEndBase interface
328 330 #--------------------------------------------------------------------------
329 331
330 332 def render_error(self, e):
331 333 start_line = self.GetCurrentLine()
332 334 self.write('\n' + e + '\n')
333 335 for i in range(start_line, self.GetCurrentLine()):
334 336 self._markers[i] = self.MarkerAdd(i, _ERROR_MARKER)
335 337
336 338
337 339 #--------------------------------------------------------------------------
338 340 # ConsoleWidget interface
339 341 #--------------------------------------------------------------------------
340 342
341 343 def new_prompt(self, prompt):
342 344 """ Display a new prompt, and start a new input buffer.
343 345 """
344 346 self._input_state = 'readline'
345 347 ConsoleWidget.new_prompt(self, prompt)
346 348 i = self.current_prompt_line
347 349 self._markers[i] = self.MarkerAdd(i, _INPUT_MARKER)
348 350
349 351
350 352 def write(self, *args, **kwargs):
351 353 # Avoid multiple inheritence, be explicit about which
352 354 # parent method class gets called
353 355 ConsoleWidget.write(self, *args, **kwargs)
354 356
355 357
356 358 def _on_key_down(self, event, skip=True):
357 359 """ Capture the character events, let the parent
358 360 widget handle them, and put our logic afterward.
359 361 """
360 362 # FIXME: This method needs to be broken down in smaller ones.
361 363 current_line_number = self.GetCurrentLine()
362 364 if event.KeyCode in (ord('c'), ord('C')) and event.ControlDown():
363 365 # Capture Control-C
364 366 if self._input_state == 'subprocess':
365 367 if self.debug:
366 368 print >>sys.__stderr__, 'Killing running process'
367 369 if hasattr(self._running_process, 'process'):
368 370 self._running_process.process.kill()
369 371 elif self._input_state == 'buffering':
370 372 if self.debug:
371 373 print >>sys.__stderr__, 'Raising KeyboardInterrupt'
372 374 raise KeyboardInterrupt
373 375 # XXX: We need to make really sure we
374 376 # get back to a prompt.
375 377 elif self._input_state == 'subprocess' and (
376 378 ( event.KeyCode<256 and
377 379 not event.ControlDown() )
378 380 or
379 381 ( event.KeyCode in (ord('d'), ord('D')) and
380 382 event.ControlDown())):
381 383 # We are running a process, we redirect keys.
382 384 ConsoleWidget._on_key_down(self, event, skip=skip)
383 385 char = chr(event.KeyCode)
384 386 # Deal with some inconsistency in wx keycodes:
385 387 if char == '\r':
386 388 char = '\n'
387 389 elif not event.ShiftDown():
388 390 char = char.lower()
389 391 if event.ControlDown() and event.KeyCode in (ord('d'), ord('D')):
390 392 char = '\04'
391 393 self._running_process.process.stdin.write(char)
392 394 self._running_process.process.stdin.flush()
393 395 elif event.KeyCode in (ord('('), 57):
394 396 # Calltips
395 397 event.Skip()
396 398 self.do_calltip()
397 399 elif self.AutoCompActive() and not event.KeyCode == ord('\t'):
398 400 event.Skip()
399 401 if event.KeyCode in (wx.WXK_BACK, wx.WXK_DELETE):
400 402 wx.CallAfter(self._popup_completion, create=True)
401 403 elif not event.KeyCode in (wx.WXK_UP, wx.WXK_DOWN, wx.WXK_LEFT,
402 404 wx.WXK_RIGHT, wx.WXK_ESCAPE):
403 405 wx.CallAfter(self._popup_completion)
404 406 else:
405 407 # Up history
406 408 if event.KeyCode == wx.WXK_UP and (
407 409 ( current_line_number == self.current_prompt_line and
408 410 event.Modifiers in (wx.MOD_NONE, wx.MOD_WIN) )
409 411 or event.ControlDown() ):
410 412 new_buffer = self.get_history_previous(
411 413 self.input_buffer)
412 414 if new_buffer is not None:
413 415 self.input_buffer = new_buffer
414 416 if self.GetCurrentLine() > self.current_prompt_line:
415 417 # Go to first line, for seemless history up.
416 418 self.GotoPos(self.current_prompt_pos)
417 419 # Down history
418 420 elif event.KeyCode == wx.WXK_DOWN and (
419 421 ( current_line_number == self.LineCount -1 and
420 422 event.Modifiers in (wx.MOD_NONE, wx.MOD_WIN) )
421 423 or event.ControlDown() ):
422 424 new_buffer = self.get_history_next()
423 425 if new_buffer is not None:
424 426 self.input_buffer = new_buffer
425 427 # Tab-completion
426 428 elif event.KeyCode == ord('\t'):
427 429 last_line = self.input_buffer.split('\n')[-1]
428 430 if not re.match(r'^\s*$', last_line):
429 431 self.complete_current_input()
430 432 if self.AutoCompActive():
431 433 wx.CallAfter(self._popup_completion, create=True)
432 434 else:
433 435 event.Skip()
434 436 else:
435 437 ConsoleWidget._on_key_down(self, event, skip=skip)
436 438
437 439
438 440 def _on_key_up(self, event, skip=True):
439 441 """ Called when any key is released.
440 442 """
441 443 if event.KeyCode in (59, ord('.')):
442 444 # Intercepting '.'
443 445 event.Skip()
444 446 wx.CallAfter(self._popup_completion, create=True)
445 447 else:
446 448 ConsoleWidget._on_key_up(self, event, skip=skip)
447 449
448 450
449 451 def _on_enter(self):
450 452 """ Called on return key down, in readline input_state.
451 453 """
452 454 if self.debug:
453 455 print >>sys.__stdout__, repr(self.input_buffer)
454 456 PrefilterFrontEnd._on_enter(self)
455 457
456 458
457 459 #--------------------------------------------------------------------------
458 460 # EditWindow API
459 461 #--------------------------------------------------------------------------
460 462
461 463 def OnUpdateUI(self, event):
462 464 """ Override the OnUpdateUI of the EditWindow class, to prevent
463 465 syntax highlighting both for faster redraw, and for more
464 466 consistent look and feel.
465 467 """
466 468 if not self._input_state == 'readline':
467 469 ConsoleWidget.OnUpdateUI(self, event)
468 470
469 471 #--------------------------------------------------------------------------
470 472 # Private API
471 473 #--------------------------------------------------------------------------
472 474
473 475 def _end_system_call(self):
474 476 """ Called at the end of a system call.
475 477 """
476 478 self._input_state = 'buffering'
477 479 self._running_process = False
478 480
479 481
480 482 def _buffer_flush(self, event):
481 483 """ Called by the timer to flush the write buffer.
482 484
483 485 This is always called in the mainloop, by the wx timer.
484 486 """
485 487 self._out_buffer_lock.acquire()
486 488 _out_buffer = self._out_buffer
487 489 self._out_buffer = []
488 490 self._out_buffer_lock.release()
489 491 self.write(''.join(_out_buffer), refresh=False)
490 492
491 493
492 494 def _colorize_input_buffer(self):
493 495 """ Keep the input buffer lines at a bright color.
494 496 """
495 497 if not self._input_state in ('readline', 'raw_input'):
496 498 return
497 499 end_line = self.GetCurrentLine()
498 500 if not sys.platform == 'win32':
499 501 end_line += 1
500 502 for i in range(self.current_prompt_line, end_line):
501 503 if i in self._markers:
502 504 self.MarkerDeleteHandle(self._markers[i])
503 505 self._markers[i] = self.MarkerAdd(i, _INPUT_MARKER)
504 506
505 507
506 508 if __name__ == '__main__':
507 509 class MainWindow(wx.Frame):
508 510 def __init__(self, parent, id, title):
509 511 wx.Frame.__init__(self, parent, id, title, size=(300,250))
510 512 self._sizer = wx.BoxSizer(wx.VERTICAL)
511 513 self.shell = WxController(self)
512 514 self._sizer.Add(self.shell, 1, wx.EXPAND)
513 515 self.SetSizer(self._sizer)
514 516 self.SetAutoLayout(1)
515 517 self.Show(True)
516 518
517 519 app = wx.PySimpleApp()
518 520 frame = MainWindow(None, wx.ID_ANY, 'Ipython')
519 521 frame.shell.SetFocus()
520 522 frame.SetSize((680, 460))
521 523 self = frame.shell
522 524
523 525 app.MainLoop()
524 526
General Comments 0
You need to be logged in to leave comments. Login now