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