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