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