##// END OF EJS Templates
Fix a race in killing subprocesses.
gvaroquaux -
Show More
@@ -1,523 +1,524 b''
1 # encoding: utf-8 -*- test-case-name:
1 # encoding: utf-8 -*- test-case-name:
2 # FIXME: Need to add tests.
2 # FIXME: Need to add tests.
3 # ipython1.frontend.wx.tests.test_wx_frontend -*-
3 # ipython1.frontend.wx.tests.test_wx_frontend -*-
4
4
5 """Classes to provide a Wx frontend to the
5 """Classes to provide a Wx frontend to the
6 IPython.kernel.core.interpreter.
6 IPython.kernel.core.interpreter.
7
7
8 This class inherits from ConsoleWidget, that provides a console-like
8 This class inherits from ConsoleWidget, that provides a console-like
9 widget to provide a text-rendering widget suitable for a terminal.
9 widget to provide a text-rendering widget suitable for a terminal.
10 """
10 """
11
11
12 __docformat__ = "restructuredtext en"
12 __docformat__ = "restructuredtext en"
13
13
14 #-------------------------------------------------------------------------------
14 #-------------------------------------------------------------------------------
15 # Copyright (C) 2008 The IPython Development Team
15 # Copyright (C) 2008 The IPython Development Team
16 #
16 #
17 # Distributed under the terms of the BSD License. The full license is in
17 # Distributed under the terms of the BSD License. The full license is in
18 # the file COPYING, distributed as part of this software.
18 # the file COPYING, distributed as part of this software.
19 #-------------------------------------------------------------------------------
19 #-------------------------------------------------------------------------------
20
20
21 #-------------------------------------------------------------------------------
21 #-------------------------------------------------------------------------------
22 # Imports
22 # Imports
23 #-------------------------------------------------------------------------------
23 #-------------------------------------------------------------------------------
24
24
25 # Major library imports
25 # Major library imports
26 import re
26 import re
27 import __builtin__
27 import __builtin__
28 from time import sleep
28 from time import sleep
29 import sys
29 import sys
30 from threading import Lock
30 from threading import Lock
31 import string
31 import string
32
32
33 import wx
33 import wx
34 from wx import stc
34 from wx import stc
35
35
36 # Ipython-specific imports.
36 # Ipython-specific imports.
37 from IPython.frontend._process import PipedProcess
37 from IPython.frontend._process import PipedProcess
38 from console_widget import ConsoleWidget
38 from console_widget import ConsoleWidget
39 from IPython.frontend.prefilterfrontend import PrefilterFrontEnd
39 from IPython.frontend.prefilterfrontend import PrefilterFrontEnd
40
40
41 #-------------------------------------------------------------------------------
41 #-------------------------------------------------------------------------------
42 # Constants
42 # Constants
43 #-------------------------------------------------------------------------------
43 #-------------------------------------------------------------------------------
44
44
45 _COMPLETE_BUFFER_BG = '#FAFAF1' # Nice green
45 _COMPLETE_BUFFER_BG = '#FAFAF1' # Nice green
46 _INPUT_BUFFER_BG = '#FDFFD3' # Nice yellow
46 _INPUT_BUFFER_BG = '#FDFFD3' # Nice yellow
47 _ERROR_BG = '#FFF1F1' # Nice red
47 _ERROR_BG = '#FFF1F1' # Nice red
48
48
49 _COMPLETE_BUFFER_MARKER = 31
49 _COMPLETE_BUFFER_MARKER = 31
50 _ERROR_MARKER = 30
50 _ERROR_MARKER = 30
51 _INPUT_MARKER = 29
51 _INPUT_MARKER = 29
52
52
53 prompt_in1 = \
53 prompt_in1 = \
54 '\n\x01\x1b[0;34m\x02In [\x01\x1b[1;34m\x02$number\x01\x1b[0;34m\x02]: \x01\x1b[0m\x02'
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 prompt_out = \
56 prompt_out = \
57 '\x01\x1b[0;31m\x02Out[\x01\x1b[1;31m\x02$number\x01\x1b[0;31m\x02]: \x01\x1b[0m\x02'
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 # Classes to implement the Wx frontend
60 # Classes to implement the Wx frontend
61 #-------------------------------------------------------------------------------
61 #-------------------------------------------------------------------------------
62 class WxController(ConsoleWidget, PrefilterFrontEnd):
62 class WxController(ConsoleWidget, PrefilterFrontEnd):
63 """Classes to provide a Wx frontend to the
63 """Classes to provide a Wx frontend to the
64 IPython.kernel.core.interpreter.
64 IPython.kernel.core.interpreter.
65
65
66 This class inherits from ConsoleWidget, that provides a console-like
66 This class inherits from ConsoleWidget, that provides a console-like
67 widget to provide a text-rendering widget suitable for a terminal.
67 widget to provide a text-rendering widget suitable for a terminal.
68 """
68 """
69
69
70 output_prompt_template = string.Template(prompt_out)
70 output_prompt_template = string.Template(prompt_out)
71
71
72 input_prompt_template = string.Template(prompt_in1)
72 input_prompt_template = string.Template(prompt_in1)
73
73
74 # Print debug info on what is happening to the console.
74 # Print debug info on what is happening to the console.
75 debug = False
75 debug = False
76
76
77 # The title of the terminal, as captured through the ANSI escape
77 # The title of the terminal, as captured through the ANSI escape
78 # sequences.
78 # sequences.
79 def _set_title(self, title):
79 def _set_title(self, title):
80 return self.Parent.SetTitle(title)
80 return self.Parent.SetTitle(title)
81
81
82 def _get_title(self):
82 def _get_title(self):
83 return self.Parent.GetTitle()
83 return self.Parent.GetTitle()
84
84
85 title = property(_get_title, _set_title)
85 title = property(_get_title, _set_title)
86
86
87
87
88 # The buffer being edited.
88 # The buffer being edited.
89 # We are duplicating the definition here because of multiple
89 # We are duplicating the definition here because of multiple
90 # inheritence
90 # inheritence
91 def _set_input_buffer(self, string):
91 def _set_input_buffer(self, string):
92 ConsoleWidget._set_input_buffer(self, string)
92 ConsoleWidget._set_input_buffer(self, string)
93 self._colorize_input_buffer()
93 self._colorize_input_buffer()
94
94
95 def _get_input_buffer(self):
95 def _get_input_buffer(self):
96 """ Returns the text in current edit buffer.
96 """ Returns the text in current edit buffer.
97 """
97 """
98 return ConsoleWidget._get_input_buffer(self)
98 return ConsoleWidget._get_input_buffer(self)
99
99
100 input_buffer = property(_get_input_buffer, _set_input_buffer)
100 input_buffer = property(_get_input_buffer, _set_input_buffer)
101
101
102
102
103 #--------------------------------------------------------------------------
103 #--------------------------------------------------------------------------
104 # Private Attributes
104 # Private Attributes
105 #--------------------------------------------------------------------------
105 #--------------------------------------------------------------------------
106
106
107 # A flag governing the behavior of the input. Can be:
107 # A flag governing the behavior of the input. Can be:
108 #
108 #
109 # 'readline' for readline-like behavior with a prompt
109 # 'readline' for readline-like behavior with a prompt
110 # and an edit buffer.
110 # and an edit buffer.
111 # 'raw_input' similar to readline, but triggered by a raw-input
111 # 'raw_input' similar to readline, but triggered by a raw-input
112 # call. Can be used by subclasses to act differently.
112 # call. Can be used by subclasses to act differently.
113 # 'subprocess' for sending the raw input directly to a
113 # 'subprocess' for sending the raw input directly to a
114 # subprocess.
114 # subprocess.
115 # 'buffering' for buffering of the input, that will be used
115 # 'buffering' for buffering of the input, that will be used
116 # when the input state switches back to another state.
116 # when the input state switches back to another state.
117 _input_state = 'readline'
117 _input_state = 'readline'
118
118
119 # Attribute to store reference to the pipes of a subprocess, if we
119 # Attribute to store reference to the pipes of a subprocess, if we
120 # are running any.
120 # are running any.
121 _running_process = False
121 _running_process = False
122
122
123 # A queue for writing fast streams to the screen without flooding the
123 # A queue for writing fast streams to the screen without flooding the
124 # event loop
124 # event loop
125 _out_buffer = []
125 _out_buffer = []
126
126
127 # A lock to lock the _out_buffer to make sure we don't empty it
127 # A lock to lock the _out_buffer to make sure we don't empty it
128 # while it is being swapped
128 # while it is being swapped
129 _out_buffer_lock = Lock()
129 _out_buffer_lock = Lock()
130
130
131 # The different line markers used to higlight the prompts.
131 # The different line markers used to higlight the prompts.
132 _markers = dict()
132 _markers = dict()
133
133
134 #--------------------------------------------------------------------------
134 #--------------------------------------------------------------------------
135 # Public API
135 # Public API
136 #--------------------------------------------------------------------------
136 #--------------------------------------------------------------------------
137
137
138 def __init__(self, parent, id=wx.ID_ANY, pos=wx.DefaultPosition,
138 def __init__(self, parent, id=wx.ID_ANY, pos=wx.DefaultPosition,
139 size=wx.DefaultSize,
139 size=wx.DefaultSize,
140 style=wx.CLIP_CHILDREN|wx.WANTS_CHARS,
140 style=wx.CLIP_CHILDREN|wx.WANTS_CHARS,
141 *args, **kwds):
141 *args, **kwds):
142 """ Create Shell instance.
142 """ Create Shell instance.
143 """
143 """
144 ConsoleWidget.__init__(self, parent, id, pos, size, style)
144 ConsoleWidget.__init__(self, parent, id, pos, size, style)
145 PrefilterFrontEnd.__init__(self, **kwds)
145 PrefilterFrontEnd.__init__(self, **kwds)
146
146
147 # Marker for complete buffer.
147 # Marker for complete buffer.
148 self.MarkerDefine(_COMPLETE_BUFFER_MARKER, stc.STC_MARK_BACKGROUND,
148 self.MarkerDefine(_COMPLETE_BUFFER_MARKER, stc.STC_MARK_BACKGROUND,
149 background=_COMPLETE_BUFFER_BG)
149 background=_COMPLETE_BUFFER_BG)
150 # Marker for current input buffer.
150 # Marker for current input buffer.
151 self.MarkerDefine(_INPUT_MARKER, stc.STC_MARK_BACKGROUND,
151 self.MarkerDefine(_INPUT_MARKER, stc.STC_MARK_BACKGROUND,
152 background=_INPUT_BUFFER_BG)
152 background=_INPUT_BUFFER_BG)
153 # Marker for tracebacks.
153 # Marker for tracebacks.
154 self.MarkerDefine(_ERROR_MARKER, stc.STC_MARK_BACKGROUND,
154 self.MarkerDefine(_ERROR_MARKER, stc.STC_MARK_BACKGROUND,
155 background=_ERROR_BG)
155 background=_ERROR_BG)
156
156
157 # A time for flushing the write buffer
157 # A time for flushing the write buffer
158 BUFFER_FLUSH_TIMER_ID = 100
158 BUFFER_FLUSH_TIMER_ID = 100
159 self._buffer_flush_timer = wx.Timer(self, BUFFER_FLUSH_TIMER_ID)
159 self._buffer_flush_timer = wx.Timer(self, BUFFER_FLUSH_TIMER_ID)
160 wx.EVT_TIMER(self, BUFFER_FLUSH_TIMER_ID, self._buffer_flush)
160 wx.EVT_TIMER(self, BUFFER_FLUSH_TIMER_ID, self._buffer_flush)
161
161
162 if 'debug' in kwds:
162 if 'debug' in kwds:
163 self.debug = kwds['debug']
163 self.debug = kwds['debug']
164 kwds.pop('debug')
164 kwds.pop('debug')
165
165
166 # Inject self in namespace, for debug
166 # Inject self in namespace, for debug
167 if self.debug:
167 if self.debug:
168 self.shell.user_ns['self'] = self
168 self.shell.user_ns['self'] = self
169
169
170
170
171 def raw_input(self, prompt):
171 def raw_input(self, prompt):
172 """ A replacement from python's raw_input.
172 """ A replacement from python's raw_input.
173 """
173 """
174 self.new_prompt(prompt)
174 self.new_prompt(prompt)
175 self._input_state = 'raw_input'
175 self._input_state = 'raw_input'
176 if hasattr(self, '_cursor'):
176 if hasattr(self, '_cursor'):
177 del self._cursor
177 del self._cursor
178 self.SetCursor(wx.StockCursor(wx.CURSOR_CROSS))
178 self.SetCursor(wx.StockCursor(wx.CURSOR_CROSS))
179 self.waiting = True
179 self.waiting = True
180 self.__old_on_enter = self._on_enter
180 self.__old_on_enter = self._on_enter
181 def my_on_enter():
181 def my_on_enter():
182 self.waiting = False
182 self.waiting = False
183 self._on_enter = my_on_enter
183 self._on_enter = my_on_enter
184 # XXX: Busy waiting, ugly.
184 # XXX: Busy waiting, ugly.
185 while self.waiting:
185 while self.waiting:
186 wx.Yield()
186 wx.Yield()
187 sleep(0.1)
187 sleep(0.1)
188 self._on_enter = self.__old_on_enter
188 self._on_enter = self.__old_on_enter
189 self._input_state = 'buffering'
189 self._input_state = 'buffering'
190 self._cursor = wx.BusyCursor()
190 self._cursor = wx.BusyCursor()
191 return self.input_buffer.rstrip('\n')
191 return self.input_buffer.rstrip('\n')
192
192
193
193
194 def system_call(self, command_string):
194 def system_call(self, command_string):
195 self._input_state = 'subprocess'
195 self._input_state = 'subprocess'
196 self._running_process = PipedProcess(command_string,
196 self._running_process = PipedProcess(command_string,
197 out_callback=self.buffered_write,
197 out_callback=self.buffered_write,
198 end_callback = self._end_system_call)
198 end_callback = self._end_system_call)
199 self._running_process.start()
199 self._running_process.start()
200 # XXX: another one of these polling loops to have a blocking
200 # XXX: another one of these polling loops to have a blocking
201 # call
201 # call
202 wx.Yield()
202 wx.Yield()
203 while self._running_process:
203 while self._running_process:
204 wx.Yield()
204 wx.Yield()
205 sleep(0.1)
205 sleep(0.1)
206 # Be sure to flush the buffer.
206 # Be sure to flush the buffer.
207 self._buffer_flush(event=None)
207 self._buffer_flush(event=None)
208
208
209
209
210 def do_calltip(self):
210 def do_calltip(self):
211 """ Analyse current and displays useful calltip for it.
211 """ Analyse current and displays useful calltip for it.
212 """
212 """
213 if self.debug:
213 if self.debug:
214 print >>sys.__stdout__, "do_calltip"
214 print >>sys.__stdout__, "do_calltip"
215 separators = re.compile('[\s\{\}\[\]\(\)\= ,:]')
215 separators = re.compile('[\s\{\}\[\]\(\)\= ,:]')
216 symbol = self.input_buffer
216 symbol = self.input_buffer
217 symbol_string = separators.split(symbol)[-1]
217 symbol_string = separators.split(symbol)[-1]
218 base_symbol_string = symbol_string.split('.')[0]
218 base_symbol_string = symbol_string.split('.')[0]
219 if base_symbol_string in self.shell.user_ns:
219 if base_symbol_string in self.shell.user_ns:
220 symbol = self.shell.user_ns[base_symbol_string]
220 symbol = self.shell.user_ns[base_symbol_string]
221 elif base_symbol_string in self.shell.user_global_ns:
221 elif base_symbol_string in self.shell.user_global_ns:
222 symbol = self.shell.user_global_ns[base_symbol_string]
222 symbol = self.shell.user_global_ns[base_symbol_string]
223 elif base_symbol_string in __builtin__.__dict__:
223 elif base_symbol_string in __builtin__.__dict__:
224 symbol = __builtin__.__dict__[base_symbol_string]
224 symbol = __builtin__.__dict__[base_symbol_string]
225 else:
225 else:
226 return False
226 return False
227 try:
227 try:
228 for name in symbol_string.split('.')[1:] + ['__doc__']:
228 for name in symbol_string.split('.')[1:] + ['__doc__']:
229 symbol = getattr(symbol, name)
229 symbol = getattr(symbol, name)
230 self.AutoCompCancel()
230 self.AutoCompCancel()
231 wx.Yield()
231 wx.Yield()
232 self.CallTipShow(self.GetCurrentPos(), symbol)
232 self.CallTipShow(self.GetCurrentPos(), symbol)
233 except:
233 except:
234 # The retrieve symbol couldn't be converted to a string
234 # The retrieve symbol couldn't be converted to a string
235 pass
235 pass
236
236
237
237
238 def _popup_completion(self, create=False):
238 def _popup_completion(self, create=False):
239 """ Updates the popup completion menu if it exists. If create is
239 """ Updates the popup completion menu if it exists. If create is
240 true, open the menu.
240 true, open the menu.
241 """
241 """
242 if self.debug:
242 if self.debug:
243 print >>sys.__stdout__, "_popup_completion"
243 print >>sys.__stdout__, "_popup_completion"
244 line = self.input_buffer
244 line = self.input_buffer
245 if (self.AutoCompActive() and line and not line[-1] == '.') \
245 if (self.AutoCompActive() and line and not line[-1] == '.') \
246 or create==True:
246 or create==True:
247 suggestion, completions = self.complete(line)
247 suggestion, completions = self.complete(line)
248 offset=0
248 offset=0
249 if completions:
249 if completions:
250 complete_sep = re.compile('[\s\{\}\[\]\(\)\= ,:]')
250 complete_sep = re.compile('[\s\{\}\[\]\(\)\= ,:]')
251 residual = complete_sep.split(line)[-1]
251 residual = complete_sep.split(line)[-1]
252 offset = len(residual)
252 offset = len(residual)
253 self.pop_completion(completions, offset=offset)
253 self.pop_completion(completions, offset=offset)
254 if self.debug:
254 if self.debug:
255 print >>sys.__stdout__, completions
255 print >>sys.__stdout__, completions
256
256
257
257
258 def buffered_write(self, text):
258 def buffered_write(self, text):
259 """ A write method for streams, that caches the stream in order
259 """ A write method for streams, that caches the stream in order
260 to avoid flooding the event loop.
260 to avoid flooding the event loop.
261
261
262 This can be called outside of the main loop, in separate
262 This can be called outside of the main loop, in separate
263 threads.
263 threads.
264 """
264 """
265 self._out_buffer_lock.acquire()
265 self._out_buffer_lock.acquire()
266 self._out_buffer.append(text)
266 self._out_buffer.append(text)
267 self._out_buffer_lock.release()
267 self._out_buffer_lock.release()
268 if not self._buffer_flush_timer.IsRunning():
268 if not self._buffer_flush_timer.IsRunning():
269 wx.CallAfter(self._buffer_flush_timer.Start,
269 wx.CallAfter(self._buffer_flush_timer.Start,
270 milliseconds=100, oneShot=True)
270 milliseconds=100, oneShot=True)
271
271
272
272
273 #--------------------------------------------------------------------------
273 #--------------------------------------------------------------------------
274 # LineFrontEnd interface
274 # LineFrontEnd interface
275 #--------------------------------------------------------------------------
275 #--------------------------------------------------------------------------
276
276
277 def execute(self, python_string, raw_string=None):
277 def execute(self, python_string, raw_string=None):
278 self._input_state = 'buffering'
278 self._input_state = 'buffering'
279 self.CallTipCancel()
279 self.CallTipCancel()
280 self._cursor = wx.BusyCursor()
280 self._cursor = wx.BusyCursor()
281 if raw_string is None:
281 if raw_string is None:
282 raw_string = python_string
282 raw_string = python_string
283 end_line = self.current_prompt_line \
283 end_line = self.current_prompt_line \
284 + max(1, len(raw_string.split('\n'))-1)
284 + max(1, len(raw_string.split('\n'))-1)
285 for i in range(self.current_prompt_line, end_line):
285 for i in range(self.current_prompt_line, end_line):
286 if i in self._markers:
286 if i in self._markers:
287 self.MarkerDeleteHandle(self._markers[i])
287 self.MarkerDeleteHandle(self._markers[i])
288 self._markers[i] = self.MarkerAdd(i, _COMPLETE_BUFFER_MARKER)
288 self._markers[i] = self.MarkerAdd(i, _COMPLETE_BUFFER_MARKER)
289 # Update the display:
289 # Update the display:
290 wx.Yield()
290 wx.Yield()
291 self.GotoPos(self.GetLength())
291 self.GotoPos(self.GetLength())
292 PrefilterFrontEnd.execute(self, python_string, raw_string=raw_string)
292 PrefilterFrontEnd.execute(self, python_string, raw_string=raw_string)
293
293
294 def save_output_hooks(self):
294 def save_output_hooks(self):
295 self.__old_raw_input = __builtin__.raw_input
295 self.__old_raw_input = __builtin__.raw_input
296 PrefilterFrontEnd.save_output_hooks(self)
296 PrefilterFrontEnd.save_output_hooks(self)
297
297
298 def capture_output(self):
298 def capture_output(self):
299 __builtin__.raw_input = self.raw_input
299 __builtin__.raw_input = self.raw_input
300 self.SetLexer(stc.STC_LEX_NULL)
300 self.SetLexer(stc.STC_LEX_NULL)
301 PrefilterFrontEnd.capture_output(self)
301 PrefilterFrontEnd.capture_output(self)
302
302
303
303
304 def release_output(self):
304 def release_output(self):
305 __builtin__.raw_input = self.__old_raw_input
305 __builtin__.raw_input = self.__old_raw_input
306 PrefilterFrontEnd.release_output(self)
306 PrefilterFrontEnd.release_output(self)
307 self.SetLexer(stc.STC_LEX_PYTHON)
307 self.SetLexer(stc.STC_LEX_PYTHON)
308
308
309
309
310 def after_execute(self):
310 def after_execute(self):
311 PrefilterFrontEnd.after_execute(self)
311 PrefilterFrontEnd.after_execute(self)
312 # Clear the wait cursor
312 # Clear the wait cursor
313 if hasattr(self, '_cursor'):
313 if hasattr(self, '_cursor'):
314 del self._cursor
314 del self._cursor
315 self.SetCursor(wx.StockCursor(wx.CURSOR_CHAR))
315 self.SetCursor(wx.StockCursor(wx.CURSOR_CHAR))
316
316
317
317
318 def show_traceback(self):
318 def show_traceback(self):
319 start_line = self.GetCurrentLine()
319 start_line = self.GetCurrentLine()
320 PrefilterFrontEnd.show_traceback(self)
320 PrefilterFrontEnd.show_traceback(self)
321 wx.Yield()
321 wx.Yield()
322 for i in range(start_line, self.GetCurrentLine()):
322 for i in range(start_line, self.GetCurrentLine()):
323 self._markers[i] = self.MarkerAdd(i, _ERROR_MARKER)
323 self._markers[i] = self.MarkerAdd(i, _ERROR_MARKER)
324
324
325
325
326 #--------------------------------------------------------------------------
326 #--------------------------------------------------------------------------
327 # FrontEndBase interface
327 # FrontEndBase interface
328 #--------------------------------------------------------------------------
328 #--------------------------------------------------------------------------
329
329
330 def render_error(self, e):
330 def render_error(self, e):
331 start_line = self.GetCurrentLine()
331 start_line = self.GetCurrentLine()
332 self.write('\n' + e + '\n')
332 self.write('\n' + e + '\n')
333 for i in range(start_line, self.GetCurrentLine()):
333 for i in range(start_line, self.GetCurrentLine()):
334 self._markers[i] = self.MarkerAdd(i, _ERROR_MARKER)
334 self._markers[i] = self.MarkerAdd(i, _ERROR_MARKER)
335
335
336
336
337 #--------------------------------------------------------------------------
337 #--------------------------------------------------------------------------
338 # ConsoleWidget interface
338 # ConsoleWidget interface
339 #--------------------------------------------------------------------------
339 #--------------------------------------------------------------------------
340
340
341 def new_prompt(self, prompt):
341 def new_prompt(self, prompt):
342 """ Display a new prompt, and start a new input buffer.
342 """ Display a new prompt, and start a new input buffer.
343 """
343 """
344 self._input_state = 'readline'
344 self._input_state = 'readline'
345 ConsoleWidget.new_prompt(self, prompt)
345 ConsoleWidget.new_prompt(self, prompt)
346 i = self.current_prompt_line
346 i = self.current_prompt_line
347 self._markers[i] = self.MarkerAdd(i, _INPUT_MARKER)
347 self._markers[i] = self.MarkerAdd(i, _INPUT_MARKER)
348
348
349
349
350 def write(self, *args, **kwargs):
350 def write(self, *args, **kwargs):
351 # Avoid multiple inheritence, be explicit about which
351 # Avoid multiple inheritence, be explicit about which
352 # parent method class gets called
352 # parent method class gets called
353 ConsoleWidget.write(self, *args, **kwargs)
353 ConsoleWidget.write(self, *args, **kwargs)
354
354
355
355
356 def _on_key_down(self, event, skip=True):
356 def _on_key_down(self, event, skip=True):
357 """ Capture the character events, let the parent
357 """ Capture the character events, let the parent
358 widget handle them, and put our logic afterward.
358 widget handle them, and put our logic afterward.
359 """
359 """
360 # FIXME: This method needs to be broken down in smaller ones.
360 # FIXME: This method needs to be broken down in smaller ones.
361 current_line_number = self.GetCurrentLine()
361 current_line_number = self.GetCurrentLine()
362 if event.KeyCode in (ord('c'), ord('C')) and event.ControlDown():
362 if event.KeyCode in (ord('c'), ord('C')) and event.ControlDown():
363 # Capture Control-C
363 # Capture Control-C
364 if self._input_state == 'subprocess':
364 if self._input_state == 'subprocess':
365 if self.debug:
365 if self.debug:
366 print >>sys.__stderr__, 'Killing running process'
366 print >>sys.__stderr__, 'Killing running process'
367 self._running_process.process.kill()
367 if hasattr(self._running_process, 'process'):
368 self._running_process.process.kill()
368 elif self._input_state == 'buffering':
369 elif self._input_state == 'buffering':
369 if self.debug:
370 if self.debug:
370 print >>sys.__stderr__, 'Raising KeyboardInterrupt'
371 print >>sys.__stderr__, 'Raising KeyboardInterrupt'
371 raise KeyboardInterrupt
372 raise KeyboardInterrupt
372 # XXX: We need to make really sure we
373 # XXX: We need to make really sure we
373 # get back to a prompt.
374 # get back to a prompt.
374 elif self._input_state == 'subprocess' and (
375 elif self._input_state == 'subprocess' and (
375 ( event.KeyCode<256 and
376 ( event.KeyCode<256 and
376 not event.ControlDown() )
377 not event.ControlDown() )
377 or
378 or
378 ( event.KeyCode in (ord('d'), ord('D')) and
379 ( event.KeyCode in (ord('d'), ord('D')) and
379 event.ControlDown())):
380 event.ControlDown())):
380 # We are running a process, we redirect keys.
381 # We are running a process, we redirect keys.
381 ConsoleWidget._on_key_down(self, event, skip=skip)
382 ConsoleWidget._on_key_down(self, event, skip=skip)
382 char = chr(event.KeyCode)
383 char = chr(event.KeyCode)
383 # Deal with some inconsistency in wx keycodes:
384 # Deal with some inconsistency in wx keycodes:
384 if char == '\r':
385 if char == '\r':
385 char = '\n'
386 char = '\n'
386 elif not event.ShiftDown():
387 elif not event.ShiftDown():
387 char = char.lower()
388 char = char.lower()
388 if event.ControlDown() and event.KeyCode in (ord('d'), ord('D')):
389 if event.ControlDown() and event.KeyCode in (ord('d'), ord('D')):
389 char = '\04'
390 char = '\04'
390 self._running_process.process.stdin.write(char)
391 self._running_process.process.stdin.write(char)
391 self._running_process.process.stdin.flush()
392 self._running_process.process.stdin.flush()
392 elif event.KeyCode in (ord('('), 57):
393 elif event.KeyCode in (ord('('), 57):
393 # Calltips
394 # Calltips
394 event.Skip()
395 event.Skip()
395 self.do_calltip()
396 self.do_calltip()
396 elif self.AutoCompActive() and not event.KeyCode == ord('\t'):
397 elif self.AutoCompActive() and not event.KeyCode == ord('\t'):
397 event.Skip()
398 event.Skip()
398 if event.KeyCode in (wx.WXK_BACK, wx.WXK_DELETE):
399 if event.KeyCode in (wx.WXK_BACK, wx.WXK_DELETE):
399 wx.CallAfter(self._popup_completion, create=True)
400 wx.CallAfter(self._popup_completion, create=True)
400 elif not event.KeyCode in (wx.WXK_UP, wx.WXK_DOWN, wx.WXK_LEFT,
401 elif not event.KeyCode in (wx.WXK_UP, wx.WXK_DOWN, wx.WXK_LEFT,
401 wx.WXK_RIGHT, wx.WXK_ESCAPE):
402 wx.WXK_RIGHT, wx.WXK_ESCAPE):
402 wx.CallAfter(self._popup_completion)
403 wx.CallAfter(self._popup_completion)
403 else:
404 else:
404 # Up history
405 # Up history
405 if event.KeyCode == wx.WXK_UP and (
406 if event.KeyCode == wx.WXK_UP and (
406 ( current_line_number == self.current_prompt_line and
407 ( current_line_number == self.current_prompt_line and
407 event.Modifiers in (wx.MOD_NONE, wx.MOD_WIN) )
408 event.Modifiers in (wx.MOD_NONE, wx.MOD_WIN) )
408 or event.ControlDown() ):
409 or event.ControlDown() ):
409 new_buffer = self.get_history_previous(
410 new_buffer = self.get_history_previous(
410 self.input_buffer)
411 self.input_buffer)
411 if new_buffer is not None:
412 if new_buffer is not None:
412 self.input_buffer = new_buffer
413 self.input_buffer = new_buffer
413 if self.GetCurrentLine() > self.current_prompt_line:
414 if self.GetCurrentLine() > self.current_prompt_line:
414 # Go to first line, for seemless history up.
415 # Go to first line, for seemless history up.
415 self.GotoPos(self.current_prompt_pos)
416 self.GotoPos(self.current_prompt_pos)
416 # Down history
417 # Down history
417 elif event.KeyCode == wx.WXK_DOWN and (
418 elif event.KeyCode == wx.WXK_DOWN and (
418 ( current_line_number == self.LineCount -1 and
419 ( current_line_number == self.LineCount -1 and
419 event.Modifiers in (wx.MOD_NONE, wx.MOD_WIN) )
420 event.Modifiers in (wx.MOD_NONE, wx.MOD_WIN) )
420 or event.ControlDown() ):
421 or event.ControlDown() ):
421 new_buffer = self.get_history_next()
422 new_buffer = self.get_history_next()
422 if new_buffer is not None:
423 if new_buffer is not None:
423 self.input_buffer = new_buffer
424 self.input_buffer = new_buffer
424 # Tab-completion
425 # Tab-completion
425 elif event.KeyCode == ord('\t'):
426 elif event.KeyCode == ord('\t'):
426 last_line = self.input_buffer.split('\n')[-1]
427 last_line = self.input_buffer.split('\n')[-1]
427 if not re.match(r'^\s*$', last_line):
428 if not re.match(r'^\s*$', last_line):
428 self.complete_current_input()
429 self.complete_current_input()
429 if self.AutoCompActive():
430 if self.AutoCompActive():
430 wx.CallAfter(self._popup_completion, create=True)
431 wx.CallAfter(self._popup_completion, create=True)
431 else:
432 else:
432 event.Skip()
433 event.Skip()
433 else:
434 else:
434 ConsoleWidget._on_key_down(self, event, skip=skip)
435 ConsoleWidget._on_key_down(self, event, skip=skip)
435
436
436
437
437 def _on_key_up(self, event, skip=True):
438 def _on_key_up(self, event, skip=True):
438 """ Called when any key is released.
439 """ Called when any key is released.
439 """
440 """
440 if event.KeyCode in (59, ord('.')):
441 if event.KeyCode in (59, ord('.')):
441 # Intercepting '.'
442 # Intercepting '.'
442 event.Skip()
443 event.Skip()
443 wx.CallAfter(self._popup_completion, create=True)
444 wx.CallAfter(self._popup_completion, create=True)
444 else:
445 else:
445 ConsoleWidget._on_key_up(self, event, skip=skip)
446 ConsoleWidget._on_key_up(self, event, skip=skip)
446
447
447
448
448 def _on_enter(self):
449 def _on_enter(self):
449 """ Called on return key down, in readline input_state.
450 """ Called on return key down, in readline input_state.
450 """
451 """
451 if self.debug:
452 if self.debug:
452 print >>sys.__stdout__, repr(self.input_buffer)
453 print >>sys.__stdout__, repr(self.input_buffer)
453 PrefilterFrontEnd._on_enter(self)
454 PrefilterFrontEnd._on_enter(self)
454
455
455
456
456 #--------------------------------------------------------------------------
457 #--------------------------------------------------------------------------
457 # EditWindow API
458 # EditWindow API
458 #--------------------------------------------------------------------------
459 #--------------------------------------------------------------------------
459
460
460 def OnUpdateUI(self, event):
461 def OnUpdateUI(self, event):
461 """ Override the OnUpdateUI of the EditWindow class, to prevent
462 """ Override the OnUpdateUI of the EditWindow class, to prevent
462 syntax highlighting both for faster redraw, and for more
463 syntax highlighting both for faster redraw, and for more
463 consistent look and feel.
464 consistent look and feel.
464 """
465 """
465 if not self._input_state == 'readline':
466 if not self._input_state == 'readline':
466 ConsoleWidget.OnUpdateUI(self, event)
467 ConsoleWidget.OnUpdateUI(self, event)
467
468
468 #--------------------------------------------------------------------------
469 #--------------------------------------------------------------------------
469 # Private API
470 # Private API
470 #--------------------------------------------------------------------------
471 #--------------------------------------------------------------------------
471
472
472 def _end_system_call(self):
473 def _end_system_call(self):
473 """ Called at the end of a system call.
474 """ Called at the end of a system call.
474 """
475 """
475 self._input_state = 'buffering'
476 self._input_state = 'buffering'
476 self._running_process = False
477 self._running_process = False
477
478
478
479
479 def _buffer_flush(self, event):
480 def _buffer_flush(self, event):
480 """ Called by the timer to flush the write buffer.
481 """ Called by the timer to flush the write buffer.
481
482
482 This is always called in the mainloop, by the wx timer.
483 This is always called in the mainloop, by the wx timer.
483 """
484 """
484 self._out_buffer_lock.acquire()
485 self._out_buffer_lock.acquire()
485 _out_buffer = self._out_buffer
486 _out_buffer = self._out_buffer
486 self._out_buffer = []
487 self._out_buffer = []
487 self._out_buffer_lock.release()
488 self._out_buffer_lock.release()
488 self.write(''.join(_out_buffer), refresh=False)
489 self.write(''.join(_out_buffer), refresh=False)
489
490
490
491
491 def _colorize_input_buffer(self):
492 def _colorize_input_buffer(self):
492 """ Keep the input buffer lines at a bright color.
493 """ Keep the input buffer lines at a bright color.
493 """
494 """
494 if not self._input_state in ('readline', 'raw_input'):
495 if not self._input_state in ('readline', 'raw_input'):
495 return
496 return
496 end_line = self.GetCurrentLine()
497 end_line = self.GetCurrentLine()
497 if not sys.platform == 'win32':
498 if not sys.platform == 'win32':
498 end_line += 1
499 end_line += 1
499 for i in range(self.current_prompt_line, end_line):
500 for i in range(self.current_prompt_line, end_line):
500 if i in self._markers:
501 if i in self._markers:
501 self.MarkerDeleteHandle(self._markers[i])
502 self.MarkerDeleteHandle(self._markers[i])
502 self._markers[i] = self.MarkerAdd(i, _INPUT_MARKER)
503 self._markers[i] = self.MarkerAdd(i, _INPUT_MARKER)
503
504
504
505
505 if __name__ == '__main__':
506 if __name__ == '__main__':
506 class MainWindow(wx.Frame):
507 class MainWindow(wx.Frame):
507 def __init__(self, parent, id, title):
508 def __init__(self, parent, id, title):
508 wx.Frame.__init__(self, parent, id, title, size=(300,250))
509 wx.Frame.__init__(self, parent, id, title, size=(300,250))
509 self._sizer = wx.BoxSizer(wx.VERTICAL)
510 self._sizer = wx.BoxSizer(wx.VERTICAL)
510 self.shell = WxController(self)
511 self.shell = WxController(self)
511 self._sizer.Add(self.shell, 1, wx.EXPAND)
512 self._sizer.Add(self.shell, 1, wx.EXPAND)
512 self.SetSizer(self._sizer)
513 self.SetSizer(self._sizer)
513 self.SetAutoLayout(1)
514 self.SetAutoLayout(1)
514 self.Show(True)
515 self.Show(True)
515
516
516 app = wx.PySimpleApp()
517 app = wx.PySimpleApp()
517 frame = MainWindow(None, wx.ID_ANY, 'Ipython')
518 frame = MainWindow(None, wx.ID_ANY, 'Ipython')
518 frame.shell.SetFocus()
519 frame.shell.SetFocus()
519 frame.SetSize((680, 460))
520 frame.SetSize((680, 460))
520 self = frame.shell
521 self = frame.shell
521
522
522 app.MainLoop()
523 app.MainLoop()
523
524
General Comments 0
You need to be logged in to leave comments. Login now