##// END OF EJS Templates
Bind Ctrl-C to kill process, when in process execution.
gvaroquaux -
Show More
@@ -0,0 +1,168 b''
1 # Addapted from killableprocess.py.
2 #______________________________________________________________________________
3 #
4 # killableprocess - subprocesses which can be reliably killed
5 #
6 # Parts of this module are copied from the subprocess.py file contained
7 # in the Python distribution.
8 #
9 # Copyright (c) 2003-2004 by Peter Astrand <astrand@lysator.liu.se>
10 #
11 # Additions and modifications written by Benjamin Smedberg
12 # <benjamin@smedbergs.us> are Copyright (c) 2006 by the Mozilla Foundation
13 # <http://www.mozilla.org/>
14 #
15 # By obtaining, using, and/or copying this software and/or its
16 # associated documentation, you agree that you have read, understood,
17 # and will comply with the following terms and conditions:
18 #
19 # Permission to use, copy, modify, and distribute this software and
20 # its associated documentation for any purpose and without fee is
21 # hereby granted, provided that the above copyright notice appears in
22 # all copies, and that both that copyright notice and this permission
23 # notice appear in supporting documentation, and that the name of the
24 # author not be used in advertising or publicity pertaining to
25 # distribution of the software without specific, written prior
26 # permission.
27 #
28 # THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
29 # INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS.
30 # IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, INDIRECT OR
31 # CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
32 # OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
33 # NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
34 # WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
35
36 r"""killableprocess - Subprocesses which can be reliably killed
37
38 This module is a subclass of the builtin "subprocess" module. It allows
39 processes that launch subprocesses to be reliably killed on Windows (via the Popen.kill() method.
40
41 It also adds a timeout argument to Wait() for a limited period of time before
42 forcefully killing the process.
43
44 Note: On Windows, this module requires Windows 2000 or higher (no support for
45 Windows 95, 98, or NT 4.0). It also requires ctypes, which is bundled with
46 Python 2.5+ or available from http://python.net/crew/theller/ctypes/
47 """
48
49 import subprocess
50 from subprocess import PIPE
51 import sys
52 import os
53 import time
54 import types
55
56 try:
57 from subprocess import CalledProcessError
58 except ImportError:
59 # Python 2.4 doesn't implement CalledProcessError
60 class CalledProcessError(Exception):
61 """This exception is raised when a process run by check_call() returns
62 a non-zero exit status. The exit status will be stored in the
63 returncode attribute."""
64 def __init__(self, returncode, cmd):
65 self.returncode = returncode
66 self.cmd = cmd
67 def __str__(self):
68 return "Command '%s' returned non-zero exit status %d" % (self.cmd, self.returncode)
69
70 mswindows = (sys.platform == "win32")
71
72 if mswindows:
73 import winprocess
74 else:
75 import signal
76
77 if not mswindows:
78 def DoNothing(*args):
79 pass
80
81 class Popen(subprocess.Popen):
82 if not mswindows:
83 # Override __init__ to set a preexec_fn
84 def __init__(self, *args, **kwargs):
85 if len(args) >= 7:
86 raise Exception("Arguments preexec_fn and after must be passed by keyword.")
87
88 real_preexec_fn = kwargs.pop("preexec_fn", None)
89 def setpgid_preexec_fn():
90 os.setpgid(0, 0)
91 if real_preexec_fn:
92 apply(real_preexec_fn)
93
94 kwargs['preexec_fn'] = setpgid_preexec_fn
95
96 subprocess.Popen.__init__(self, *args, **kwargs)
97
98 if mswindows:
99 def _execute_child(self, args, executable, preexec_fn, close_fds,
100 cwd, env, universal_newlines, startupinfo,
101 creationflags, shell,
102 p2cread, p2cwrite,
103 c2pread, c2pwrite,
104 errread, errwrite):
105 if not isinstance(args, types.StringTypes):
106 args = subprocess.list2cmdline(args)
107
108 if startupinfo is None:
109 startupinfo = winprocess.STARTUPINFO()
110
111 if None not in (p2cread, c2pwrite, errwrite):
112 startupinfo.dwFlags |= winprocess.STARTF_USESTDHANDLES
113
114 startupinfo.hStdInput = int(p2cread)
115 startupinfo.hStdOutput = int(c2pwrite)
116 startupinfo.hStdError = int(errwrite)
117 if shell:
118 startupinfo.dwFlags |= winprocess.STARTF_USESHOWWINDOW
119 startupinfo.wShowWindow = winprocess.SW_HIDE
120 comspec = os.environ.get("COMSPEC", "cmd.exe")
121 args = comspec + " /c " + args
122
123 # We create a new job for this process, so that we can kill
124 # the process and any sub-processes
125 self._job = winprocess.CreateJobObject()
126
127 creationflags |= winprocess.CREATE_SUSPENDED
128 creationflags |= winprocess.CREATE_UNICODE_ENVIRONMENT
129
130 hp, ht, pid, tid = winprocess.CreateProcess(
131 executable, args,
132 None, None, # No special security
133 1, # Must inherit handles!
134 creationflags,
135 winprocess.EnvironmentBlock(env),
136 cwd, startupinfo)
137
138 self._child_created = True
139 self._handle = hp
140 self._thread = ht
141 self.pid = pid
142
143 winprocess.AssignProcessToJobObject(self._job, hp)
144 winprocess.ResumeThread(ht)
145
146 if p2cread is not None:
147 p2cread.Close()
148 if c2pwrite is not None:
149 c2pwrite.Close()
150 if errwrite is not None:
151 errwrite.Close()
152
153 def kill(self, group=True):
154 """Kill the process. If group=True, all sub-processes will also be killed."""
155 if mswindows:
156 if group:
157 winprocess.TerminateJobObject(self._job, 127)
158 else:
159 winprocess.TerminateProcess(self._handle, 127)
160 self.returncode = 127
161 else:
162 if group:
163 os.killpg(self.pid, signal.SIGKILL)
164 else:
165 os.kill(self.pid, signal.SIGKILL)
166 self.returncode = -9
167
168
@@ -1,55 +1,55 b''
1 # encoding: utf-8
1 # encoding: utf-8
2 """
2 """
3 Object for encapsulating process execution by using callbacks for stdout,
3 Object for encapsulating process execution by using callbacks for stdout,
4 stderr and stdin.
4 stderr and stdin.
5 """
5 """
6 __docformat__ = "restructuredtext en"
6 __docformat__ = "restructuredtext en"
7
7
8 #-------------------------------------------------------------------------------
8 #-------------------------------------------------------------------------------
9 # Copyright (C) 2008 The IPython Development Team
9 # Copyright (C) 2008 The IPython Development Team
10 #
10 #
11 # Distributed under the terms of the BSD License. The full license is in
11 # Distributed under the terms of the BSD License. The full license is in
12 # the file COPYING, distributed as part of this software.
12 # the file COPYING, distributed as part of this software.
13 #-------------------------------------------------------------------------------
13 #-------------------------------------------------------------------------------
14
14
15 #-------------------------------------------------------------------------------
15 #-------------------------------------------------------------------------------
16 # Imports
16 # Imports
17 #-------------------------------------------------------------------------------
17 #-------------------------------------------------------------------------------
18 from subprocess import Popen, PIPE
18 from killable_process import Popen, PIPE
19 from threading import Thread
19 from threading import Thread
20 from time import sleep
20 from time import sleep
21
21
22 class PipedProcess(Thread):
22 class PipedProcess(Thread):
23
23
24 def __init__(self, command_string, out_callback,
24 def __init__(self, command_string, out_callback,
25 end_callback=None,):
25 end_callback=None,):
26 self.command_string = command_string
26 self.command_string = command_string
27 self.out_callback = out_callback
27 self.out_callback = out_callback
28 self.end_callback = end_callback
28 self.end_callback = end_callback
29 Thread.__init__(self)
29 Thread.__init__(self)
30
30
31
31
32 def run(self):
32 def run(self):
33 """ Start the process and hook up the callbacks.
33 """ Start the process and hook up the callbacks.
34 """
34 """
35 process = Popen((self.command_string + ' 2>&1', ), shell=True,
35 process = Popen((self.command_string + ' 2>&1', ), shell=True,
36 universal_newlines=True,
36 universal_newlines=True,
37 stdout=PIPE, stdin=PIPE)
37 stdout=PIPE, stdin=PIPE)
38 self.process = process
38 self.process = process
39 while True:
39 while True:
40 out_char = process.stdout.read(1)
40 out_char = process.stdout.read(1)
41 if out_char == '':
41 if out_char == '':
42 if process.poll() is not None:
42 if process.poll() is not None:
43 # The process has finished
43 # The process has finished
44 break
44 break
45 else:
45 else:
46 # The process is not giving any interesting
46 # The process is not giving any interesting
47 # output. No use polling it immediatly.
47 # output. No use polling it immediatly.
48 sleep(0.1)
48 sleep(0.1)
49 else:
49 else:
50 self.out_callback(out_char)
50 self.out_callback(out_char)
51
51
52 if self.end_callback is not None:
52 if self.end_callback is not None:
53 self.end_callback()
53 self.end_callback()
54
54
55
55
@@ -1,355 +1,397 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.cocoa.tests.test_cocoa_frontend -*-
3 # ipython1.frontend.cocoa.tests.test_cocoa_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 """
8 """
9
9
10 __docformat__ = "restructuredtext en"
10 __docformat__ = "restructuredtext en"
11
11
12 #-------------------------------------------------------------------------------
12 #-------------------------------------------------------------------------------
13 # Copyright (C) 2008 The IPython Development Team
13 # Copyright (C) 2008 The IPython Development Team
14 #
14 #
15 # Distributed under the terms of the BSD License. The full license is in
15 # Distributed under the terms of the BSD License. The full license is in
16 # the file COPYING, distributed as part of this software.
16 # the file COPYING, distributed as part of this software.
17 #-------------------------------------------------------------------------------
17 #-------------------------------------------------------------------------------
18
18
19 #-------------------------------------------------------------------------------
19 #-------------------------------------------------------------------------------
20 # Imports
20 # Imports
21 #-------------------------------------------------------------------------------
21 #-------------------------------------------------------------------------------
22
22
23
23
24 import wx
24 import wx
25 import re
25 import re
26 from wx import stc
26 from wx import stc
27 from console_widget import ConsoleWidget
27 from console_widget import ConsoleWidget
28 import __builtin__
28 import __builtin__
29 from time import sleep
29 from time import sleep
30 import sys
30 import sys
31 import signal
31
32
32 from threading import Lock
33 from threading import Lock
33
34
34 from IPython.frontend.piped_process import PipedProcess
35 from IPython.frontend.piped_process import PipedProcess
35 from IPython.frontend.prefilterfrontend import PrefilterFrontEnd
36 from IPython.frontend.prefilterfrontend import PrefilterFrontEnd
36
37
37 #_COMMAND_BG = '#FAFAF1' # Nice green
38 #_COMMAND_BG = '#FAFAF1' # Nice green
38 _RUNNING_BUFFER_BG = '#FDFFD3' # Nice yellow
39 _RUNNING_BUFFER_BG = '#FDFFD3' # Nice yellow
39 _ERROR_BG = '#FFF1F1' # Nice red
40 _ERROR_BG = '#FFF1F1' # Nice red
40
41
41 _RUNNING_BUFFER_MARKER = 31
42 _RUNNING_BUFFER_MARKER = 31
42 _ERROR_MARKER = 30
43 _ERROR_MARKER = 30
43
44
44 #-------------------------------------------------------------------------------
45 #-------------------------------------------------------------------------------
45 # Classes to implement the Wx frontend
46 # Classes to implement the Wx frontend
46 #-------------------------------------------------------------------------------
47 #-------------------------------------------------------------------------------
47 class WxController(PrefilterFrontEnd, ConsoleWidget):
48 class WxController(PrefilterFrontEnd, ConsoleWidget):
48
49
49 output_prompt = \
50 output_prompt = \
50 '\x01\x1b[0;31m\x02Out[\x01\x1b[1;31m\x02%i\x01\x1b[0;31m\x02]: \x01\x1b[0m\x02'
51 '\x01\x1b[0;31m\x02Out[\x01\x1b[1;31m\x02%i\x01\x1b[0;31m\x02]: \x01\x1b[0m\x02'
51
52
53 # Print debug info on what is happening to the console.
52 debug = True
54 debug = True
53
55
56 # The title of the terminal, as captured through the ANSI escape
57 # sequences.
58
59 def _set_title(self, title):
60 return self.Parent.SetTitle(title)
61
62 def _get_title(self):
63 return self.Parent.GetTitle()
64
65 title = property(_get_title, _set_title)
66
67 #--------------------------------------------------------------------------
68 # Private Attributes
69 #--------------------------------------------------------------------------
70
71 # A flag governing the behavior of the input. Can be:
72 #
73 # 'readline' for readline-like behavior with a prompt
74 # and an edit buffer.
75 # 'subprocess' for sending the raw input directly to a
76 # subprocess.
77 # 'buffering' for buffering of the input, that will be used
78 # when the input state switches back to another state.
79 _input_state = 'readline'
80
54 # Attribute to store reference to the pipes of a subprocess, if we
81 # Attribute to store reference to the pipes of a subprocess, if we
55 # are running any.
82 # are running any.
56 running_process = False
83 _running_process = False
57
84
58 # A queue for writing fast streams to the screen without flooding the
85 # A queue for writing fast streams to the screen without flooding the
59 # event loop
86 # event loop
60 write_buffer = []
87 _out_buffer = []
61
88
62 # A lock to lock the write_buffer to make sure we don't empty it
89 # A lock to lock the _out_buffer to make sure we don't empty it
63 # while it is being swapped
90 # while it is being swapped
64 write_buffer_lock = Lock()
91 _out_buffer_lock = Lock()
65
92
66 #--------------------------------------------------------------------------
93 #--------------------------------------------------------------------------
67 # Public API
94 # Public API
68 #--------------------------------------------------------------------------
95 #--------------------------------------------------------------------------
69
96
70 def __init__(self, parent, id=wx.ID_ANY, pos=wx.DefaultPosition,
97 def __init__(self, parent, id=wx.ID_ANY, pos=wx.DefaultPosition,
71 size=wx.DefaultSize, style=wx.CLIP_CHILDREN,
98 size=wx.DefaultSize, style=wx.CLIP_CHILDREN,
72 *args, **kwds):
99 *args, **kwds):
73 """ Create Shell instance.
100 """ Create Shell instance.
74 """
101 """
75 ConsoleWidget.__init__(self, parent, id, pos, size, style)
102 ConsoleWidget.__init__(self, parent, id, pos, size, style)
76 PrefilterFrontEnd.__init__(self)
103 PrefilterFrontEnd.__init__(self)
77
104
78 # Marker for running buffer.
105 # Marker for running buffer.
79 self.MarkerDefine(_RUNNING_BUFFER_MARKER, stc.STC_MARK_BACKGROUND,
106 self.MarkerDefine(_RUNNING_BUFFER_MARKER, stc.STC_MARK_BACKGROUND,
80 background=_RUNNING_BUFFER_BG)
107 background=_RUNNING_BUFFER_BG)
81 # Marker for tracebacks.
108 # Marker for tracebacks.
82 self.MarkerDefine(_ERROR_MARKER, stc.STC_MARK_BACKGROUND,
109 self.MarkerDefine(_ERROR_MARKER, stc.STC_MARK_BACKGROUND,
83 background=_ERROR_BG)
110 background=_ERROR_BG)
84
111
85 # A time for flushing the write buffer
112 # A time for flushing the write buffer
86 BUFFER_FLUSH_TIMER_ID = 100
113 BUFFER_FLUSH_TIMER_ID = 100
87 self._buffer_flush_timer = wx.Timer(self, BUFFER_FLUSH_TIMER_ID)
114 self._buffer_flush_timer = wx.Timer(self, BUFFER_FLUSH_TIMER_ID)
88 wx.EVT_TIMER(self, BUFFER_FLUSH_TIMER_ID, self._buffer_flush)
115 wx.EVT_TIMER(self, BUFFER_FLUSH_TIMER_ID, self._buffer_flush)
89
116
90 def do_completion(self):
117 def do_completion(self):
91 """ Do code completion.
118 """ Do code completion.
92 """
119 """
93 if self.debug:
120 if self.debug:
94 print >>sys.__stdout__, "do_completion",
121 print >>sys.__stdout__, "do_completion",
95 line = self.get_current_edit_buffer()
122 line = self.get_current_edit_buffer()
96 new_line, completions = self.complete(line)
123 new_line, completions = self.complete(line)
97 if len(completions)>1:
124 if len(completions)>1:
98 self.write_completion(completions)
125 self.write_completion(completions)
99 self.replace_current_edit_buffer(new_line)
126 self.replace_current_edit_buffer(new_line)
100 if self.debug:
127 if self.debug:
101 print >>sys.__stdout__, completions
128 print >>sys.__stdout__, completions
102
129
103
130
104 def do_calltip(self):
131 def do_calltip(self):
105 if self.debug:
132 if self.debug:
106 print >>sys.__stdout__, "do_calltip"
133 print >>sys.__stdout__, "do_calltip"
107 separators = re.compile('[\s\{\}\[\]\(\)\= ,:]')
134 separators = re.compile('[\s\{\}\[\]\(\)\= ,:]')
108 symbol = self.get_current_edit_buffer()
135 symbol = self.get_current_edit_buffer()
109 symbol_string = separators.split(symbol)[-1]
136 symbol_string = separators.split(symbol)[-1]
110 base_symbol_string = symbol_string.split('.')[0]
137 base_symbol_string = symbol_string.split('.')[0]
111 if base_symbol_string in self.shell.user_ns:
138 if base_symbol_string in self.shell.user_ns:
112 symbol = self.shell.user_ns[base_symbol_string]
139 symbol = self.shell.user_ns[base_symbol_string]
113 elif base_symbol_string in self.shell.user_global_ns:
140 elif base_symbol_string in self.shell.user_global_ns:
114 symbol = self.shell.user_global_ns[base_symbol_string]
141 symbol = self.shell.user_global_ns[base_symbol_string]
115 elif base_symbol_string in __builtin__.__dict__:
142 elif base_symbol_string in __builtin__.__dict__:
116 symbol = __builtin__.__dict__[base_symbol_string]
143 symbol = __builtin__.__dict__[base_symbol_string]
117 else:
144 else:
118 return False
145 return False
119 for name in symbol_string.split('.')[1:] + ['__doc__']:
146 for name in symbol_string.split('.')[1:] + ['__doc__']:
120 symbol = getattr(symbol, name)
147 symbol = getattr(symbol, name)
121 try:
148 try:
122 self.AutoCompCancel()
149 self.AutoCompCancel()
123 wx.Yield()
150 wx.Yield()
124 self.CallTipShow(self.GetCurrentPos(), symbol)
151 self.CallTipShow(self.GetCurrentPos(), symbol)
125 except:
152 except:
126 # The retrieve symbol couldn't be converted to a string
153 # The retrieve symbol couldn't be converted to a string
127 pass
154 pass
128
155
129
156
130 def popup_completion(self, create=False):
157 def popup_completion(self, create=False):
131 """ Updates the popup completion menu if it exists. If create is
158 """ Updates the popup completion menu if it exists. If create is
132 true, open the menu.
159 true, open the menu.
133 """
160 """
134 if self.debug:
161 if self.debug:
135 print >>sys.__stdout__, "popup_completion",
162 print >>sys.__stdout__, "popup_completion",
136 line = self.get_current_edit_buffer()
163 line = self.get_current_edit_buffer()
137 if (self.AutoCompActive() and not line[-1] == '.') \
164 if (self.AutoCompActive() and not line[-1] == '.') \
138 or create==True:
165 or create==True:
139 suggestion, completions = self.complete(line)
166 suggestion, completions = self.complete(line)
140 offset=0
167 offset=0
141 if completions:
168 if completions:
142 complete_sep = re.compile('[\s\{\}\[\]\(\)\= ,:]')
169 complete_sep = re.compile('[\s\{\}\[\]\(\)\= ,:]')
143 residual = complete_sep.split(line)[-1]
170 residual = complete_sep.split(line)[-1]
144 offset = len(residual)
171 offset = len(residual)
145 self.pop_completion(completions, offset=offset)
172 self.pop_completion(completions, offset=offset)
146 if self.debug:
173 if self.debug:
147 print >>sys.__stdout__, completions
174 print >>sys.__stdout__, completions
148
175
149
176
177 def new_prompt(self, prompt):
178 self._input_state = 'readline'
179 ConsoleWidget.new_prompt(self, prompt)
180
181
150 def raw_input(self, prompt):
182 def raw_input(self, prompt):
151 """ A replacement from python's raw_input.
183 """ A replacement from python's raw_input.
152 """
184 """
153 self.new_prompt(prompt)
185 self.new_prompt(prompt)
154 self.waiting = True
186 self.waiting = True
155 self.__old_on_enter = self._on_enter
187 self.__old_on_enter = self._on_enter
156 def my_on_enter():
188 def my_on_enter():
157 self.waiting = False
189 self.waiting = False
158 self._on_enter = my_on_enter
190 self._on_enter = my_on_enter
159 # XXX: Busy waiting, ugly.
191 # XXX: Busy waiting, ugly.
160 while self.waiting:
192 while self.waiting:
161 wx.Yield()
193 wx.Yield()
162 sleep(0.1)
194 sleep(0.1)
163 self._on_enter = self.__old_on_enter
195 self._on_enter = self.__old_on_enter
196 self._input_state = 'buffering'
164 return self.get_current_edit_buffer().rstrip('\n')
197 return self.get_current_edit_buffer().rstrip('\n')
165
198
166
199
167 def execute(self, python_string, raw_string=None):
200 def execute(self, python_string, raw_string=None):
201 self._input_state = 'buffering'
168 self.CallTipCancel()
202 self.CallTipCancel()
169 self._cursor = wx.BusyCursor()
203 self._cursor = wx.BusyCursor()
170 if raw_string is None:
204 if raw_string is None:
171 raw_string = python_string
205 raw_string = python_string
172 end_line = self.current_prompt_line \
206 end_line = self.current_prompt_line \
173 + max(1, len(raw_string.split('\n'))-1)
207 + max(1, len(raw_string.split('\n'))-1)
174 for i in range(self.current_prompt_line, end_line):
208 for i in range(self.current_prompt_line, end_line):
175 self.MarkerAdd(i, _RUNNING_BUFFER_MARKER)
209 self.MarkerAdd(i, _RUNNING_BUFFER_MARKER)
176 # Update the display:
210 # Update the display:
177 wx.Yield()
211 wx.Yield()
178 self.GotoPos(self.GetLength())
212 self.GotoPos(self.GetLength())
179 PrefilterFrontEnd.execute(self, python_string, raw_string=raw_string)
213 PrefilterFrontEnd.execute(self, python_string, raw_string=raw_string)
180
214
181
215
182 def capture_output(self):
216 def capture_output(self):
183 self.__old_raw_input = __builtin__.raw_input
217 self.__old_raw_input = __builtin__.raw_input
184 __builtin__.raw_input = self.raw_input
218 __builtin__.raw_input = self.raw_input
185 PrefilterFrontEnd.capture_output(self)
219 PrefilterFrontEnd.capture_output(self)
186
220
187
221
188 def release_output(self):
222 def release_output(self):
189 __builtin__.raw_input = self.__old_raw_input
223 __builtin__.raw_input = self.__old_raw_input
190 PrefilterFrontEnd.capture_output(self)
224 PrefilterFrontEnd.capture_output(self)
191
225
192
226
193 def after_execute(self):
227 def after_execute(self):
194 PrefilterFrontEnd.after_execute(self)
228 PrefilterFrontEnd.after_execute(self)
195 if hasattr(self, '_cursor'):
229 if hasattr(self, '_cursor'):
196 del self._cursor
230 del self._cursor
197
231
198
232
199 def system_call(self, command_string):
233 def system_call(self, command_string):
200 self.running_process = True
234 self._input_state = 'subprocess'
201 self.running_process = PipedProcess(command_string,
235 self._running_process = PipedProcess(command_string,
202 out_callback=self.buffered_write,
236 out_callback=self.buffered_write,
203 end_callback = self._end_system_call)
237 end_callback = self._end_system_call)
204 self.running_process.start()
238 self._running_process.start()
205 # XXX: another one of these polling loops to have a blocking
239 # XXX: another one of these polling loops to have a blocking
206 # call
240 # call
207 wx.Yield()
241 wx.Yield()
208 while self.running_process:
242 while self._running_process:
209 wx.Yield()
243 wx.Yield()
210 sleep(0.1)
244 sleep(0.1)
211 # Be sure to flush the buffer.
245 # Be sure to flush the buffer.
212 self._buffer_flush(event=None)
246 self._buffer_flush(event=None)
213
247
214
248
215 def buffered_write(self, text):
249 def buffered_write(self, text):
216 """ A write method for streams, that caches the stream in order
250 """ A write method for streams, that caches the stream in order
217 to avoid flooding the event loop.
251 to avoid flooding the event loop.
218
252
219 This can be called outside of the main loop, in separate
253 This can be called outside of the main loop, in separate
220 threads.
254 threads.
221 """
255 """
222 self.write_buffer_lock.acquire()
256 self._out_buffer_lock.acquire()
223 self.write_buffer.append(text)
257 self._out_buffer.append(text)
224 self.write_buffer_lock.release()
258 self._out_buffer_lock.release()
225 if not self._buffer_flush_timer.IsRunning():
259 if not self._buffer_flush_timer.IsRunning():
226 self._buffer_flush_timer.Start(100) # milliseconds
260 self._buffer_flush_timer.Start(100) # milliseconds
227
261
228
262
229 def _end_system_call(self):
230 """ Called at the end of a system call.
231 """
232 self.running_process = False
233
234
235 def _buffer_flush(self, event):
236 """ Called by the timer to flush the write buffer.
237
238 This is always called in the mainloop, by the wx timer.
239 """
240 self.write_buffer_lock.acquire()
241 write_buffer = self.write_buffer
242 self.write_buffer = []
243 self.write_buffer_lock.release()
244 self.write(''.join(write_buffer))
245 self._buffer_flush_timer.Stop()
246
247
248 def show_traceback(self):
263 def show_traceback(self):
249 start_line = self.GetCurrentLine()
264 start_line = self.GetCurrentLine()
250 PrefilterFrontEnd.show_traceback(self)
265 PrefilterFrontEnd.show_traceback(self)
251 wx.Yield()
266 wx.Yield()
252 for i in range(start_line, self.GetCurrentLine()):
267 for i in range(start_line, self.GetCurrentLine()):
253 self.MarkerAdd(i, _ERROR_MARKER)
268 self.MarkerAdd(i, _ERROR_MARKER)
254
269
255
270
256 #--------------------------------------------------------------------------
271 #--------------------------------------------------------------------------
257 # Private API
272 # Private API
258 #--------------------------------------------------------------------------
273 #--------------------------------------------------------------------------
259
274
260 def _on_key_down(self, event, skip=True):
275 def _on_key_down(self, event, skip=True):
261 """ Capture the character events, let the parent
276 """ Capture the character events, let the parent
262 widget handle them, and put our logic afterward.
277 widget handle them, and put our logic afterward.
263 """
278 """
279 print >>sys.__stderr__, event.KeyCode
264 current_line_number = self.GetCurrentLine()
280 current_line_number = self.GetCurrentLine()
265 if self.running_process and event.KeyCode<256 \
281 if event.KeyCode in (ord('c'), ord('C')) and event.ControlDown():
282 # Capture Control-C
283 if self._input_state == 'subprocess':
284 if self.debug:
285 print >>sys.__stderr__, 'Killing running process'
286 self._running_process.process.kill()
287 elif self._input_state == 'buffering':
288 if self.debug:
289 print >>sys.__stderr__, 'Raising KeyboardException'
290 raise KeyboardException
291 # XXX: We need to make really sure we
292 # get back to a prompt.
293 elif self._input_state == 'subprocess' and event.KeyCode<256 \
266 and event.Modifiers in (wx.MOD_NONE, wx.MOD_WIN):
294 and event.Modifiers in (wx.MOD_NONE, wx.MOD_WIN):
267 # We are running a process, let us not be too clever.
295 # We are running a process, we redirect keys.
268 ConsoleWidget._on_key_down(self, event, skip=skip)
296 ConsoleWidget._on_key_down(self, event, skip=skip)
269 if self.debug:
297 if self.debug:
270 print >>sys.__stderr__, chr(event.KeyCode)
298 print >>sys.__stderr__, chr(event.KeyCode)
271 self.running_process.process.stdin.write(chr(event.KeyCode))
299 self._running_process.process.stdin.write(chr(event.KeyCode))
272 elif event.KeyCode in (ord('('), 57):
300 elif event.KeyCode in (ord('('), 57):
273 # Calltips
301 # Calltips
274 event.Skip()
302 event.Skip()
275 self.do_calltip()
303 self.do_calltip()
276 elif self.AutoCompActive():
304 elif self.AutoCompActive():
277 event.Skip()
305 event.Skip()
278 if event.KeyCode in (wx.WXK_BACK, wx.WXK_DELETE):
306 if event.KeyCode in (wx.WXK_BACK, wx.WXK_DELETE):
279 wx.CallAfter(self.popup_completion, create=True)
307 wx.CallAfter(self.popup_completion, create=True)
280 elif not event.KeyCode in (wx.WXK_UP, wx.WXK_DOWN, wx.WXK_LEFT,
308 elif not event.KeyCode in (wx.WXK_UP, wx.WXK_DOWN, wx.WXK_LEFT,
281 wx.WXK_RIGHT):
309 wx.WXK_RIGHT):
282 wx.CallAfter(self.popup_completion)
310 wx.CallAfter(self.popup_completion)
283 else:
311 else:
284 # Up history
312 # Up history
285 if event.KeyCode == wx.WXK_UP and (
313 if event.KeyCode == wx.WXK_UP and (
286 ( current_line_number == self.current_prompt_line and
314 ( current_line_number == self.current_prompt_line and
287 event.Modifiers in (wx.MOD_NONE, wx.MOD_WIN) )
315 event.Modifiers in (wx.MOD_NONE, wx.MOD_WIN) )
288 or event.ControlDown() ):
316 or event.ControlDown() ):
289 new_buffer = self.get_history_previous(
317 new_buffer = self.get_history_previous(
290 self.get_current_edit_buffer())
318 self.get_current_edit_buffer())
291 if new_buffer is not None:
319 if new_buffer is not None:
292 self.replace_current_edit_buffer(new_buffer)
320 self.replace_current_edit_buffer(new_buffer)
293 if self.GetCurrentLine() > self.current_prompt_line:
321 if self.GetCurrentLine() > self.current_prompt_line:
294 # Go to first line, for seemless history up.
322 # Go to first line, for seemless history up.
295 self.GotoPos(self.current_prompt_pos)
323 self.GotoPos(self.current_prompt_pos)
296 # Down history
324 # Down history
297 elif event.KeyCode == wx.WXK_DOWN and (
325 elif event.KeyCode == wx.WXK_DOWN and (
298 ( current_line_number == self.LineCount -1 and
326 ( current_line_number == self.LineCount -1 and
299 event.Modifiers in (wx.MOD_NONE, wx.MOD_WIN) )
327 event.Modifiers in (wx.MOD_NONE, wx.MOD_WIN) )
300 or event.ControlDown() ):
328 or event.ControlDown() ):
301 new_buffer = self.get_history_next()
329 new_buffer = self.get_history_next()
302 if new_buffer is not None:
330 if new_buffer is not None:
303 self.replace_current_edit_buffer(new_buffer)
331 self.replace_current_edit_buffer(new_buffer)
304 # Tab-completion
332 # Tab-completion
305 elif event.KeyCode == ord('\t'):
333 elif event.KeyCode == ord('\t'):
306 last_line = self.get_current_edit_buffer().split('\n')[-1]
334 last_line = self.get_current_edit_buffer().split('\n')[-1]
307 if not re.match(r'^\s*$', last_line):
335 if not re.match(r'^\s*$', last_line):
308 self.do_completion()
336 self.do_completion()
309 else:
337 else:
310 event.Skip()
338 event.Skip()
311 else:
339 else:
312 ConsoleWidget._on_key_down(self, event, skip=skip)
340 ConsoleWidget._on_key_down(self, event, skip=skip)
313
341
314
342
315 def _on_key_up(self, event, skip=True):
343 def _on_key_up(self, event, skip=True):
316 if event.KeyCode in (59, ord('.')):
344 if event.KeyCode in (59, ord('.')):
317 # Intercepting '.'
345 # Intercepting '.'
318 event.Skip()
346 event.Skip()
319 self.popup_completion(create=True)
347 self.popup_completion(create=True)
320 else:
348 else:
321 ConsoleWidget._on_key_up(self, event, skip=skip)
349 ConsoleWidget._on_key_up(self, event, skip=skip)
322
350
351
323 def _on_enter(self):
352 def _on_enter(self):
324 if self.debug:
353 if self.debug:
325 print >>sys.__stdout__, repr(self.get_current_edit_buffer())
354 print >>sys.__stdout__, repr(self.get_current_edit_buffer())
326 PrefilterFrontEnd._on_enter(self)
355 PrefilterFrontEnd._on_enter(self)
327
356
328 def _set_title(self, title):
329 return self.Parent.SetTitle(title)
330
357
331 def _get_title(self):
358 def _end_system_call(self):
332 return self.Parent.GetTitle()
359 """ Called at the end of a system call.
360 """
361 print >>sys.__stderr__, 'End of system call'
362 self._input_state = 'buffering'
363 self._running_process = False
333
364
334 title = property(_get_title, _set_title)
365
366 def _buffer_flush(self, event):
367 """ Called by the timer to flush the write buffer.
368
369 This is always called in the mainloop, by the wx timer.
370 """
371 self._out_buffer_lock.acquire()
372 _out_buffer = self._out_buffer
373 self._out_buffer = []
374 self._out_buffer_lock.release()
375 self.write(''.join(_out_buffer))
376 self._buffer_flush_timer.Stop()
335
377
336
378
337 if __name__ == '__main__':
379 if __name__ == '__main__':
338 class MainWindow(wx.Frame):
380 class MainWindow(wx.Frame):
339 def __init__(self, parent, id, title):
381 def __init__(self, parent, id, title):
340 wx.Frame.__init__(self, parent, id, title, size=(300,250))
382 wx.Frame.__init__(self, parent, id, title, size=(300,250))
341 self._sizer = wx.BoxSizer(wx.VERTICAL)
383 self._sizer = wx.BoxSizer(wx.VERTICAL)
342 self.shell = WxController(self)
384 self.shell = WxController(self)
343 self._sizer.Add(self.shell, 1, wx.EXPAND)
385 self._sizer.Add(self.shell, 1, wx.EXPAND)
344 self.SetSizer(self._sizer)
386 self.SetSizer(self._sizer)
345 self.SetAutoLayout(1)
387 self.SetAutoLayout(1)
346 self.Show(True)
388 self.Show(True)
347
389
348 app = wx.PySimpleApp()
390 app = wx.PySimpleApp()
349 frame = MainWindow(None, wx.ID_ANY, 'Ipython')
391 frame = MainWindow(None, wx.ID_ANY, 'Ipython')
350 frame.shell.SetFocus()
392 frame.shell.SetFocus()
351 frame.SetSize((680, 460))
393 frame.SetSize((680, 460))
352 self = frame.shell
394 self = frame.shell
353
395
354 app.MainLoop()
396 app.MainLoop()
355
397
General Comments 0
You need to be logged in to leave comments. Login now