##// END OF EJS Templates
Make process execution work under windows.
gvaroquaux -
Show More
@@ -0,0 +1,264 b''
1 # A module to expose various thread/process/job related structures and
2 # methods from kernel32
3 #
4 # The MIT License
5 #
6 # Copyright (c) 2006 the Mozilla Foundation <http://www.mozilla.org>
7 #
8 # Permission is hereby granted, free of charge, to any person obtaining a
9 # copy of this software and associated documentation files (the "Software"),
10 # to deal in the Software without restriction, including without limitation
11 # the rights to use, copy, modify, merge, publish, distribute, sublicense,
12 # and/or sell copies of the Software, and to permit persons to whom the
13 # Software is furnished to do so, subject to the following conditions:
14 #
15 # The above copyright notice and this permission notice shall be included in
16 # all copies or substantial portions of the Software.
17 #
18 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21 # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22 # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
23 # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
24 # DEALINGS IN THE SOFTWARE.
25
26 from ctypes import c_void_p, POINTER, sizeof, Structure, windll, WinError, WINFUNCTYPE
27 from ctypes.wintypes import BOOL, BYTE, DWORD, HANDLE, LPCWSTR, LPWSTR, UINT, WORD
28
29 LPVOID = c_void_p
30 LPBYTE = POINTER(BYTE)
31 LPDWORD = POINTER(DWORD)
32
33 SW_HIDE = 0
34
35 def ErrCheckBool(result, func, args):
36 """errcheck function for Windows functions that return a BOOL True
37 on success"""
38 if not result:
39 raise WinError()
40 return args
41
42 # CloseHandle()
43
44 CloseHandleProto = WINFUNCTYPE(BOOL, HANDLE)
45 CloseHandle = CloseHandleProto(("CloseHandle", windll.kernel32))
46 CloseHandle.errcheck = ErrCheckBool
47
48 # AutoHANDLE
49
50 class AutoHANDLE(HANDLE):
51 """Subclass of HANDLE which will call CloseHandle() on deletion."""
52 def Close(self):
53 if self.value:
54 CloseHandle(self)
55 self.value = 0
56
57 def __del__(self):
58 self.Close()
59
60 def __int__(self):
61 return self.value
62
63 def ErrCheckHandle(result, func, args):
64 """errcheck function for Windows functions that return a HANDLE."""
65 if not result:
66 raise WinError()
67 return AutoHANDLE(result)
68
69 # PROCESS_INFORMATION structure
70
71 class PROCESS_INFORMATION(Structure):
72 _fields_ = [("hProcess", HANDLE),
73 ("hThread", HANDLE),
74 ("dwProcessID", DWORD),
75 ("dwThreadID", DWORD)]
76
77 def __init__(self):
78 Structure.__init__(self)
79
80 self.cb = sizeof(self)
81
82 LPPROCESS_INFORMATION = POINTER(PROCESS_INFORMATION)
83
84 # STARTUPINFO structure
85
86 class STARTUPINFO(Structure):
87 _fields_ = [("cb", DWORD),
88 ("lpReserved", LPWSTR),
89 ("lpDesktop", LPWSTR),
90 ("lpTitle", LPWSTR),
91 ("dwX", DWORD),
92 ("dwY", DWORD),
93 ("dwXSize", DWORD),
94 ("dwYSize", DWORD),
95 ("dwXCountChars", DWORD),
96 ("dwYCountChars", DWORD),
97 ("dwFillAttribute", DWORD),
98 ("dwFlags", DWORD),
99 ("wShowWindow", WORD),
100 ("cbReserved2", WORD),
101 ("lpReserved2", LPBYTE),
102 ("hStdInput", HANDLE),
103 ("hStdOutput", HANDLE),
104 ("hStdError", HANDLE)
105 ]
106 LPSTARTUPINFO = POINTER(STARTUPINFO)
107
108 STARTF_USESHOWWINDOW = 0x01
109 STARTF_USESIZE = 0x02
110 STARTF_USEPOSITION = 0x04
111 STARTF_USECOUNTCHARS = 0x08
112 STARTF_USEFILLATTRIBUTE = 0x10
113 STARTF_RUNFULLSCREEN = 0x20
114 STARTF_FORCEONFEEDBACK = 0x40
115 STARTF_FORCEOFFFEEDBACK = 0x80
116 STARTF_USESTDHANDLES = 0x100
117
118 # EnvironmentBlock
119
120 class EnvironmentBlock:
121 """An object which can be passed as the lpEnv parameter of CreateProcess.
122 It is initialized with a dictionary."""
123
124 def __init__(self, dict):
125 if not dict:
126 self._as_parameter_ = None
127 else:
128 values = ["%s=%s" % (key, value)
129 for (key, value) in dict.iteritems()]
130 values.append("")
131 self._as_parameter_ = LPCWSTR("\0".join(values))
132
133 # CreateProcess()
134
135 CreateProcessProto = WINFUNCTYPE(BOOL, # Return type
136 LPCWSTR, # lpApplicationName
137 LPWSTR, # lpCommandLine
138 LPVOID, # lpProcessAttributes
139 LPVOID, # lpThreadAttributes
140 BOOL, # bInheritHandles
141 DWORD, # dwCreationFlags
142 LPVOID, # lpEnvironment
143 LPCWSTR, # lpCurrentDirectory
144 LPSTARTUPINFO, # lpStartupInfo
145 LPPROCESS_INFORMATION # lpProcessInformation
146 )
147
148 CreateProcessFlags = ((1, "lpApplicationName", None),
149 (1, "lpCommandLine"),
150 (1, "lpProcessAttributes", None),
151 (1, "lpThreadAttributes", None),
152 (1, "bInheritHandles", True),
153 (1, "dwCreationFlags", 0),
154 (1, "lpEnvironment", None),
155 (1, "lpCurrentDirectory", None),
156 (1, "lpStartupInfo"),
157 (2, "lpProcessInformation"))
158
159 def ErrCheckCreateProcess(result, func, args):
160 ErrCheckBool(result, func, args)
161 # return a tuple (hProcess, hThread, dwProcessID, dwThreadID)
162 pi = args[9]
163 return AutoHANDLE(pi.hProcess), AutoHANDLE(pi.hThread), pi.dwProcessID, pi.dwThreadID
164
165 CreateProcess = CreateProcessProto(("CreateProcessW", windll.kernel32),
166 CreateProcessFlags)
167 CreateProcess.errcheck = ErrCheckCreateProcess
168
169 CREATE_BREAKAWAY_FROM_JOB = 0x01000000
170 CREATE_DEFAULT_ERROR_MODE = 0x04000000
171 CREATE_NEW_CONSOLE = 0x00000010
172 CREATE_NEW_PROCESS_GROUP = 0x00000200
173 CREATE_NO_WINDOW = 0x08000000
174 CREATE_SUSPENDED = 0x00000004
175 CREATE_UNICODE_ENVIRONMENT = 0x00000400
176 DEBUG_ONLY_THIS_PROCESS = 0x00000002
177 DEBUG_PROCESS = 0x00000001
178 DETACHED_PROCESS = 0x00000008
179
180 # CreateJobObject()
181
182 CreateJobObjectProto = WINFUNCTYPE(HANDLE, # Return type
183 LPVOID, # lpJobAttributes
184 LPCWSTR # lpName
185 )
186
187 CreateJobObjectFlags = ((1, "lpJobAttributes", None),
188 (1, "lpName", None))
189
190 CreateJobObject = CreateJobObjectProto(("CreateJobObjectW", windll.kernel32),
191 CreateJobObjectFlags)
192 CreateJobObject.errcheck = ErrCheckHandle
193
194 # AssignProcessToJobObject()
195
196 AssignProcessToJobObjectProto = WINFUNCTYPE(BOOL, # Return type
197 HANDLE, # hJob
198 HANDLE # hProcess
199 )
200 AssignProcessToJobObjectFlags = ((1, "hJob"),
201 (1, "hProcess"))
202 AssignProcessToJobObject = AssignProcessToJobObjectProto(
203 ("AssignProcessToJobObject", windll.kernel32),
204 AssignProcessToJobObjectFlags)
205 AssignProcessToJobObject.errcheck = ErrCheckBool
206
207 # ResumeThread()
208
209 def ErrCheckResumeThread(result, func, args):
210 if result == -1:
211 raise WinError()
212
213 return args
214
215 ResumeThreadProto = WINFUNCTYPE(DWORD, # Return type
216 HANDLE # hThread
217 )
218 ResumeThreadFlags = ((1, "hThread"),)
219 ResumeThread = ResumeThreadProto(("ResumeThread", windll.kernel32),
220 ResumeThreadFlags)
221 ResumeThread.errcheck = ErrCheckResumeThread
222
223 # TerminateJobObject()
224
225 TerminateJobObjectProto = WINFUNCTYPE(BOOL, # Return type
226 HANDLE, # hJob
227 UINT # uExitCode
228 )
229 TerminateJobObjectFlags = ((1, "hJob"),
230 (1, "uExitCode", 127))
231 TerminateJobObject = TerminateJobObjectProto(
232 ("TerminateJobObject", windll.kernel32),
233 TerminateJobObjectFlags)
234 TerminateJobObject.errcheck = ErrCheckBool
235
236 # WaitForSingleObject()
237
238 WaitForSingleObjectProto = WINFUNCTYPE(DWORD, # Return type
239 HANDLE, # hHandle
240 DWORD, # dwMilliseconds
241 )
242 WaitForSingleObjectFlags = ((1, "hHandle"),
243 (1, "dwMilliseconds", -1))
244 WaitForSingleObject = WaitForSingleObjectProto(
245 ("WaitForSingleObject", windll.kernel32),
246 WaitForSingleObjectFlags)
247
248 INFINITE = -1
249 WAIT_TIMEOUT = 0x0102
250 WAIT_OBJECT_0 = 0x0
251 WAIT_ABANDONED = 0x0080
252
253 # GetExitCodeProcess()
254
255 GetExitCodeProcessProto = WINFUNCTYPE(BOOL, # Return type
256 HANDLE, # hProcess
257 LPDWORD, # lpExitCode
258 )
259 GetExitCodeProcessFlags = ((1, "hProcess"),
260 (2, "lpExitCode"))
261 GetExitCodeProcess = GetExitCodeProcessProto(
262 ("GetExitCodeProcess", windll.kernel32),
263 GetExitCodeProcessFlags)
264 GetExitCodeProcess.errcheck = ErrCheckBool
@@ -1,168 +1,168 b''
1 1 # Addapted from killableprocess.py.
2 2 #______________________________________________________________________________
3 3 #
4 4 # killableprocess - subprocesses which can be reliably killed
5 5 #
6 6 # Parts of this module are copied from the subprocess.py file contained
7 7 # in the Python distribution.
8 8 #
9 9 # Copyright (c) 2003-2004 by Peter Astrand <astrand@lysator.liu.se>
10 10 #
11 11 # Additions and modifications written by Benjamin Smedberg
12 12 # <benjamin@smedbergs.us> are Copyright (c) 2006 by the Mozilla Foundation
13 13 # <http://www.mozilla.org/>
14 14 #
15 15 # By obtaining, using, and/or copying this software and/or its
16 16 # associated documentation, you agree that you have read, understood,
17 17 # and will comply with the following terms and conditions:
18 18 #
19 19 # Permission to use, copy, modify, and distribute this software and
20 20 # its associated documentation for any purpose and without fee is
21 21 # hereby granted, provided that the above copyright notice appears in
22 22 # all copies, and that both that copyright notice and this permission
23 23 # notice appear in supporting documentation, and that the name of the
24 24 # author not be used in advertising or publicity pertaining to
25 25 # distribution of the software without specific, written prior
26 26 # permission.
27 27 #
28 28 # THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
29 29 # INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS.
30 30 # IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, INDIRECT OR
31 31 # CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
32 32 # OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
33 33 # NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
34 34 # WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
35 35
36 36 r"""killableprocess - Subprocesses which can be reliably killed
37 37
38 38 This module is a subclass of the builtin "subprocess" module. It allows
39 39 processes that launch subprocesses to be reliably killed on Windows (via the Popen.kill() method.
40 40
41 41 It also adds a timeout argument to Wait() for a limited period of time before
42 42 forcefully killing the process.
43 43
44 44 Note: On Windows, this module requires Windows 2000 or higher (no support for
45 45 Windows 95, 98, or NT 4.0). It also requires ctypes, which is bundled with
46 46 Python 2.5+ or available from http://python.net/crew/theller/ctypes/
47 47 """
48 48
49 49 import subprocess
50 50 from subprocess import PIPE
51 51 import sys
52 52 import os
53 53 import time
54 54 import types
55 55
56 56 try:
57 57 from subprocess import CalledProcessError
58 58 except ImportError:
59 59 # Python 2.4 doesn't implement CalledProcessError
60 60 class CalledProcessError(Exception):
61 61 """This exception is raised when a process run by check_call() returns
62 62 a non-zero exit status. The exit status will be stored in the
63 63 returncode attribute."""
64 64 def __init__(self, returncode, cmd):
65 65 self.returncode = returncode
66 66 self.cmd = cmd
67 67 def __str__(self):
68 68 return "Command '%s' returned non-zero exit status %d" % (self.cmd, self.returncode)
69 69
70 70 mswindows = (sys.platform == "win32")
71 71
72 72 if mswindows:
73 73 import winprocess
74 74 else:
75 75 import signal
76 76
77 77 if not mswindows:
78 78 def DoNothing(*args):
79 79 pass
80 80
81 81 class Popen(subprocess.Popen):
82 82 if not mswindows:
83 83 # Override __init__ to set a preexec_fn
84 84 def __init__(self, *args, **kwargs):
85 85 if len(args) >= 7:
86 86 raise Exception("Arguments preexec_fn and after must be passed by keyword.")
87 87
88 88 real_preexec_fn = kwargs.pop("preexec_fn", None)
89 89 def setpgid_preexec_fn():
90 90 os.setpgid(0, 0)
91 91 if real_preexec_fn:
92 92 apply(real_preexec_fn)
93 93
94 94 kwargs['preexec_fn'] = setpgid_preexec_fn
95 95
96 96 subprocess.Popen.__init__(self, *args, **kwargs)
97 97
98 98 if mswindows:
99 99 def _execute_child(self, args, executable, preexec_fn, close_fds,
100 100 cwd, env, universal_newlines, startupinfo,
101 101 creationflags, shell,
102 102 p2cread, p2cwrite,
103 103 c2pread, c2pwrite,
104 104 errread, errwrite):
105 105 if not isinstance(args, types.StringTypes):
106 106 args = subprocess.list2cmdline(args)
107 107
108 108 if startupinfo is None:
109 109 startupinfo = winprocess.STARTUPINFO()
110
110
111 111 if None not in (p2cread, c2pwrite, errwrite):
112 112 startupinfo.dwFlags |= winprocess.STARTF_USESTDHANDLES
113 113
114 114 startupinfo.hStdInput = int(p2cread)
115 115 startupinfo.hStdOutput = int(c2pwrite)
116 116 startupinfo.hStdError = int(errwrite)
117 117 if shell:
118 118 startupinfo.dwFlags |= winprocess.STARTF_USESHOWWINDOW
119 119 startupinfo.wShowWindow = winprocess.SW_HIDE
120 120 comspec = os.environ.get("COMSPEC", "cmd.exe")
121 121 args = comspec + " /c " + args
122 122
123 123 # We create a new job for this process, so that we can kill
124 124 # the process and any sub-processes
125 125 self._job = winprocess.CreateJobObject()
126 126
127 127 creationflags |= winprocess.CREATE_SUSPENDED
128 128 creationflags |= winprocess.CREATE_UNICODE_ENVIRONMENT
129 129
130 130 hp, ht, pid, tid = winprocess.CreateProcess(
131 131 executable, args,
132 132 None, None, # No special security
133 133 1, # Must inherit handles!
134 134 creationflags,
135 135 winprocess.EnvironmentBlock(env),
136 136 cwd, startupinfo)
137 137
138 138 self._child_created = True
139 139 self._handle = hp
140 140 self._thread = ht
141 141 self.pid = pid
142 142
143 143 winprocess.AssignProcessToJobObject(self._job, hp)
144 144 winprocess.ResumeThread(ht)
145 145
146 146 if p2cread is not None:
147 147 p2cread.Close()
148 148 if c2pwrite is not None:
149 149 c2pwrite.Close()
150 150 if errwrite is not None:
151 151 errwrite.Close()
152 152
153 153 def kill(self, group=True):
154 154 """Kill the process. If group=True, all sub-processes will also be killed."""
155 155 if mswindows:
156 156 if group:
157 157 winprocess.TerminateJobObject(self._job, 127)
158 158 else:
159 159 winprocess.TerminateProcess(self._handle, 127)
160 160 self.returncode = 127
161 161 else:
162 162 if group:
163 163 os.killpg(self.pid, signal.SIGKILL)
164 164 else:
165 165 os.kill(self.pid, signal.SIGKILL)
166 166 self.returncode = -9
167 167
168 168
@@ -1,432 +1,433 b''
1 1 # encoding: utf-8
2 2 """
3 3 A Wx widget to act as a console and input commands.
4 4
5 5 This widget deals with prompts and provides an edit buffer
6 6 restricted to after the last prompt.
7 7 """
8 8
9 9 __docformat__ = "restructuredtext en"
10 10
11 11 #-------------------------------------------------------------------------------
12 12 # Copyright (C) 2008 The IPython Development Team
13 13 #
14 14 # Distributed under the terms of the BSD License. The full license is
15 15 # in the file COPYING, distributed as part of this software.
16 16 #-------------------------------------------------------------------------------
17 17
18 18 #-------------------------------------------------------------------------------
19 19 # Imports
20 20 #-------------------------------------------------------------------------------
21 21
22 22 import wx
23 23 import wx.stc as stc
24 24
25 25 from wx.py import editwindow
26 26 import sys
27 27 LINESEP = '\n'
28 28 if sys.platform == 'win32':
29 29 LINESEP = '\n\r'
30 30
31 31 import re
32 32
33 33 # FIXME: Need to provide an API for non user-generated display on the
34 34 # screen: this should not be editable by the user.
35 35
36 36 _DEFAULT_SIZE = 10
37 37
38 38 _DEFAULT_STYLE = {
39 39 'stdout' : 'fore:#0000FF',
40 40 'stderr' : 'fore:#007f00',
41 41 'trace' : 'fore:#FF0000',
42 42
43 43 'default' : 'size:%d' % _DEFAULT_SIZE,
44 44 'bracegood' : 'fore:#FFFFFF,back:#0000FF,bold',
45 45 'bracebad' : 'fore:#000000,back:#FF0000,bold',
46 46
47 47 # properties for the various Python lexer styles
48 48 'comment' : 'fore:#007F00',
49 49 'number' : 'fore:#007F7F',
50 50 'string' : 'fore:#7F007F,italic',
51 51 'char' : 'fore:#7F007F,italic',
52 52 'keyword' : 'fore:#00007F,bold',
53 53 'triple' : 'fore:#7F0000',
54 54 'tripledouble' : 'fore:#7F0000',
55 55 'class' : 'fore:#0000FF,bold,underline',
56 56 'def' : 'fore:#007F7F,bold',
57 57 'operator' : 'bold'
58 58 }
59 59
60 60 # new style numbers
61 61 _STDOUT_STYLE = 15
62 62 _STDERR_STYLE = 16
63 63 _TRACE_STYLE = 17
64 64
65 65
66 66 # system colors
67 67 #SYS_COLOUR_BACKGROUND = wx.SystemSettings.GetColour(wx.SYS_COLOUR_BACKGROUND)
68 68
69 69 #-------------------------------------------------------------------------------
70 70 # The console widget class
71 71 #-------------------------------------------------------------------------------
72 72 class ConsoleWidget(editwindow.EditWindow):
73 73 """ Specialized styled text control view for console-like workflow.
74 74
75 75 This widget is mainly interested in dealing with the prompt and
76 76 keeping the cursor inside the editing line.
77 77 """
78 78
79 79 title = 'Console'
80 80
81 81 style = _DEFAULT_STYLE.copy()
82 82
83 83 # Translation table from ANSI escape sequences to color. Override
84 84 # this to specify your colors.
85 85 ANSI_STYLES = {'0;30': [0, 'BLACK'], '0;31': [1, 'RED'],
86 86 '0;32': [2, 'GREEN'], '0;33': [3, 'BROWN'],
87 87 '0;34': [4, 'BLUE'], '0;35': [5, 'PURPLE'],
88 88 '0;36': [6, 'CYAN'], '0;37': [7, 'LIGHT GREY'],
89 89 '1;30': [8, 'DARK GREY'], '1;31': [9, 'RED'],
90 90 '1;32': [10, 'SEA GREEN'], '1;33': [11, 'YELLOW'],
91 91 '1;34': [12, 'LIGHT BLUE'], '1;35':
92 92 [13, 'MEDIUM VIOLET RED'],
93 93 '1;36': [14, 'LIGHT STEEL BLUE'], '1;37': [15, 'YELLOW']}
94 94
95 95 # The color of the carret (call _apply_style() after setting)
96 96 carret_color = 'BLACK'
97 97
98 98 #--------------------------------------------------------------------------
99 99 # Public API
100 100 #--------------------------------------------------------------------------
101 101
102 102 def __init__(self, parent, id=wx.ID_ANY, pos=wx.DefaultPosition,
103 103 size=wx.DefaultSize, style=0, ):
104 104 editwindow.EditWindow.__init__(self, parent, id, pos, size, style)
105 105 self.configure_scintilla()
106 106
107 107 # FIXME: we need to retrieve this from the interpreter.
108 108 self.prompt = \
109 109 '\n\x01\x1b[0;34m\x02In [\x01\x1b[1;34m\x02%i\x01\x1b[0;34m\x02]: \x01\x1b[0m\x02'
110 110 self.new_prompt(self.prompt % 1)
111 111
112 112 self.Bind(wx.EVT_KEY_DOWN, self._on_key_down)
113 113 self.Bind(wx.EVT_KEY_UP, self._on_key_up)
114 114
115 115
116 116 def configure_scintilla(self):
117 117 self.SetEOLMode(stc.STC_EOL_LF)
118 118
119 119 # Ctrl"+" or Ctrl "-" can be used to zoomin/zoomout the text inside
120 120 # the widget
121 121 self.CmdKeyAssign(ord('+'), stc.STC_SCMOD_CTRL, stc.STC_CMD_ZOOMIN)
122 122 self.CmdKeyAssign(ord('-'), stc.STC_SCMOD_CTRL, stc.STC_CMD_ZOOMOUT)
123 123 # Also allow Ctrl Shift "=" for poor non US keyboard users.
124 124 self.CmdKeyAssign(ord('='), stc.STC_SCMOD_CTRL|stc.STC_SCMOD_SHIFT,
125 125 stc.STC_CMD_ZOOMIN)
126 126
127 127 #self.CmdKeyAssign(stc.STC_KEY_PRIOR, stc.STC_SCMOD_SHIFT,
128 128 # stc.STC_CMD_PAGEUP)
129 129
130 130 #self.CmdKeyAssign(stc.STC_KEY_NEXT, stc.STC_SCMOD_SHIFT,
131 131 # stc.STC_CMD_PAGEDOWN)
132 132
133 133 # Keys: we need to clear some of the keys the that don't play
134 134 # well with a console.
135 135 self.CmdKeyClear(ord('D'), stc.STC_SCMOD_CTRL)
136 136 self.CmdKeyClear(ord('L'), stc.STC_SCMOD_CTRL)
137 137 self.CmdKeyClear(ord('T'), stc.STC_SCMOD_CTRL)
138 138
139 139
140 140 self.SetEOLMode(stc.STC_EOL_CRLF)
141 141 self.SetWrapMode(stc.STC_WRAP_CHAR)
142 142 self.SetWrapMode(stc.STC_WRAP_WORD)
143 143 self.SetBufferedDraw(True)
144 144 self.SetUseAntiAliasing(True)
145 145 self.SetLayoutCache(stc.STC_CACHE_PAGE)
146 146 self.SetUndoCollection(False)
147 147 self.SetUseTabs(True)
148 148 self.SetIndent(4)
149 149 self.SetTabWidth(4)
150 150
151 151 self.EnsureCaretVisible()
152 152 # we don't want scintilla's autocompletion to choose
153 153 # automaticaly out of a single choice list, as we pop it up
154 154 # automaticaly
155 155 self.AutoCompSetChooseSingle(False)
156 156 self.AutoCompSetMaxHeight(10)
157 157
158 158 self.SetMargins(3, 3) #text is moved away from border with 3px
159 159 # Suppressing Scintilla margins
160 160 self.SetMarginWidth(0, 0)
161 161 self.SetMarginWidth(1, 0)
162 162 self.SetMarginWidth(2, 0)
163 163
164 164 self._apply_style()
165 165
166 166 # Xterm escape sequences
167 167 self.color_pat = re.compile('\x01?\x1b\[(.*?)m\x02?')
168 168 self.title_pat = re.compile('\x1b]0;(.*?)\x07')
169 169
170 170 #self.SetEdgeMode(stc.STC_EDGE_LINE)
171 171 #self.SetEdgeColumn(80)
172 172
173 173 # styles
174 174 p = self.style
175 175 self.StyleSetSpec(stc.STC_STYLE_DEFAULT, p['default'])
176 176 self.StyleClearAll()
177 177 self.StyleSetSpec(_STDOUT_STYLE, p['stdout'])
178 178 self.StyleSetSpec(_STDERR_STYLE, p['stderr'])
179 179 self.StyleSetSpec(_TRACE_STYLE, p['trace'])
180 180
181 181 self.StyleSetSpec(stc.STC_STYLE_BRACELIGHT, p['bracegood'])
182 182 self.StyleSetSpec(stc.STC_STYLE_BRACEBAD, p['bracebad'])
183 183 self.StyleSetSpec(stc.STC_P_COMMENTLINE, p['comment'])
184 184 self.StyleSetSpec(stc.STC_P_NUMBER, p['number'])
185 185 self.StyleSetSpec(stc.STC_P_STRING, p['string'])
186 186 self.StyleSetSpec(stc.STC_P_CHARACTER, p['char'])
187 187 self.StyleSetSpec(stc.STC_P_WORD, p['keyword'])
188 188 self.StyleSetSpec(stc.STC_P_WORD2, p['keyword'])
189 189 self.StyleSetSpec(stc.STC_P_TRIPLE, p['triple'])
190 190 self.StyleSetSpec(stc.STC_P_TRIPLEDOUBLE, p['tripledouble'])
191 191 self.StyleSetSpec(stc.STC_P_CLASSNAME, p['class'])
192 192 self.StyleSetSpec(stc.STC_P_DEFNAME, p['def'])
193 193 self.StyleSetSpec(stc.STC_P_OPERATOR, p['operator'])
194 194 self.StyleSetSpec(stc.STC_P_COMMENTBLOCK, p['comment'])
195 195
196 196
197 def write(self, text):
197 def write(self, text, refresh=True):
198 198 """ Write given text to buffer, while translating the ansi escape
199 199 sequences.
200 200 """
201 201 # XXX: do not put print statements to sys.stdout/sys.stderr in
202 202 # this method, the print statements will call this method, as
203 203 # you will end up with an infinit loop
204 204 if self.debug:
205 205 print >>sys.__stderr__, text
206 206 title = self.title_pat.split(text)
207 207 if len(title)>1:
208 208 self.title = title[-2]
209 209
210 210 text = self.title_pat.sub('', text)
211 211 segments = self.color_pat.split(text)
212 212 segment = segments.pop(0)
213 213 self.GotoPos(self.GetLength())
214 214 self.StartStyling(self.GetLength(), 0xFF)
215 215 self.AppendText(segment)
216 216
217 217 if segments:
218 218 for ansi_tag, text in zip(segments[::2], segments[1::2]):
219 219 self.StartStyling(self.GetLength(), 0xFF)
220 220 self.AppendText(text)
221 221
222 222 if ansi_tag not in self.ANSI_STYLES:
223 223 style = 0
224 224 else:
225 225 style = self.ANSI_STYLES[ansi_tag][0]
226 226
227 227 self.SetStyling(len(text), style)
228 228
229 229 self.GotoPos(self.GetLength())
230 wx.Yield()
230 if refresh:
231 wx.Yield()
231 232
232 233
233 234 def new_prompt(self, prompt):
234 235 """ Prints a prompt at start of line, and move the start of the
235 236 current block there.
236 237
237 238 The prompt can be give with ascii escape sequences.
238 239 """
239 240 self.write(prompt)
240 241 # now we update our cursor giving end of prompt
241 242 self.current_prompt_pos = self.GetLength()
242 243 self.current_prompt_line = self.GetCurrentLine()
243 244 wx.Yield()
244 245 self.EnsureCaretVisible()
245 246
246 247
247 248 def replace_current_edit_buffer(self, text):
248 249 """ Replace currently entered command line with given text.
249 250 """
250 251 self.SetSelection(self.current_prompt_pos, self.GetLength())
251 252 self.ReplaceSelection(text)
252 253 self.GotoPos(self.GetLength())
253 254
254 255
255 256 def get_current_edit_buffer(self):
256 257 """ Returns the text in current edit buffer.
257 258 """
258 259 current_edit_buffer = self.GetTextRange(self.current_prompt_pos,
259 260 self.GetLength())
260 261 current_edit_buffer = current_edit_buffer.replace(LINESEP, '\n')
261 262 return current_edit_buffer
262 263
263 264
264 265 #--------------------------------------------------------------------------
265 266 # Private API
266 267 #--------------------------------------------------------------------------
267 268
268 269 def _apply_style(self):
269 270 """ Applies the colors for the different text elements and the
270 271 carret.
271 272 """
272 273 self.SetCaretForeground(self.carret_color)
273 274
274 275 #self.StyleClearAll()
275 276 self.StyleSetSpec(stc.STC_STYLE_BRACELIGHT,
276 277 "fore:#FF0000,back:#0000FF,bold")
277 278 self.StyleSetSpec(stc.STC_STYLE_BRACEBAD,
278 279 "fore:#000000,back:#FF0000,bold")
279 280
280 281 for style in self.ANSI_STYLES.values():
281 282 self.StyleSetSpec(style[0], "bold,fore:%s" % style[1])
282 283
283 284
284 285 def write_completion(self, possibilities):
285 286 # FIXME: This is non Wx specific and needs to be moved into
286 287 # the base class.
287 288 current_buffer = self.get_current_edit_buffer()
288 289
289 290 self.write('\n')
290 291 max_len = len(max(possibilities, key=len)) + 1
291 292
292 293 #now we check how much symbol we can put on a line...
293 294 chars_per_line = self.GetSize()[0]/self.GetCharWidth()
294 295 symbols_per_line = max(1, chars_per_line/max_len)
295 296
296 297 pos = 1
297 298 buf = []
298 299 for symbol in possibilities:
299 300 if pos < symbols_per_line:
300 301 buf.append(symbol.ljust(max_len))
301 302 pos += 1
302 303 else:
303 304 buf.append(symbol.rstrip() + '\n')
304 305 pos = 1
305 306 self.write(''.join(buf))
306 307 self.new_prompt(self.prompt % (self.last_result['number'] + 1))
307 308 self.replace_current_edit_buffer(current_buffer)
308 309
309 310
310 311 def pop_completion(self, possibilities, offset=0):
311 312 """ Pops up an autocompletion menu. Offset is the offset
312 313 in characters of the position at which the menu should
313 314 appear, relativ to the cursor.
314 315 """
315 316 self.AutoCompSetIgnoreCase(False)
316 317 self.AutoCompSetAutoHide(False)
317 318 self.AutoCompSetMaxHeight(len(possibilities))
318 319 self.AutoCompShow(offset, " ".join(possibilities))
319 320
320 321
321 322 def scroll_to_bottom(self):
322 323 maxrange = self.GetScrollRange(wx.VERTICAL)
323 324 self.ScrollLines(maxrange)
324 325
325 326
326 327 def _on_key_down(self, event, skip=True):
327 328 """ Key press callback used for correcting behavior for
328 329 console-like interfaces: the cursor is constraint to be after
329 330 the last prompt.
330 331
331 332 Return True if event as been catched.
332 333 """
333 334 catched = True
334 335 # Intercept some specific keys.
335 336 if event.KeyCode == ord('L') and event.ControlDown() :
336 337 self.scroll_to_bottom()
337 338 elif event.KeyCode == ord('K') and event.ControlDown() :
338 339 self.replace_current_edit_buffer('')
339 340 elif event.KeyCode == wx.WXK_PAGEUP and event.ShiftDown():
340 341 self.ScrollPages(-1)
341 342 elif event.KeyCode == wx.WXK_PAGEDOWN and event.ShiftDown():
342 343 self.ScrollPages(1)
343 344 elif event.KeyCode == wx.WXK_UP and event.ShiftDown():
344 345 self.ScrollLines(-1)
345 346 elif event.KeyCode == wx.WXK_DOWN and event.ShiftDown():
346 347 self.ScrollLines(1)
347 348 else:
348 349 catched = False
349 350
350 351 if self.AutoCompActive():
351 352 event.Skip()
352 353 else:
353 354 if event.KeyCode in (13, wx.WXK_NUMPAD_ENTER) and \
354 355 event.Modifiers in (wx.MOD_NONE, wx.MOD_WIN):
355 356 catched = True
356 357 self.CallTipCancel()
357 358 self.write('\n')
358 359 # Under windows scintilla seems to be doing funny stuff to the
359 360 # line returns here, but get_current_edit_buffer filters this
360 361 # out.
361 362 if sys.platform == 'win32':
362 363 self.replace_current_edit_buffer(
363 364 self.get_current_edit_buffer())
364 365 self._on_enter()
365 366
366 367 elif event.KeyCode == wx.WXK_HOME:
367 368 if event.Modifiers in (wx.MOD_NONE, wx.MOD_WIN):
368 369 self.GotoPos(self.current_prompt_pos)
369 370 catched = True
370 371
371 372 elif event.Modifiers in (wx.MOD_SHIFT, wx.MOD_WIN) :
372 373 # FIXME: This behavior is not ideal: if the selection
373 374 # is already started, it will jump.
374 375 self.SetSelectionStart(self.current_prompt_pos)
375 376 self.SetSelectionEnd(self.GetCurrentPos())
376 377 catched = True
377 378
378 379 elif event.KeyCode == wx.WXK_UP:
379 380 if self.GetCurrentLine() > self.current_prompt_line:
380 381 if self.GetCurrentLine() == self.current_prompt_line + 1 \
381 382 and self.GetColumn(self.GetCurrentPos()) < \
382 383 self.GetColumn(self.current_prompt_pos):
383 384 self.GotoPos(self.current_prompt_pos)
384 385 else:
385 386 event.Skip()
386 387 catched = True
387 388
388 389 elif event.KeyCode in (wx.WXK_LEFT, wx.WXK_BACK):
389 390 if self.GetCurrentPos() > self.current_prompt_pos:
390 391 event.Skip()
391 392 catched = True
392 393
393 394 if skip and not catched:
394 395 # Put the cursor back in the edit region
395 396 if self.GetCurrentPos() < self.current_prompt_pos:
396 397 self.GotoPos(self.current_prompt_pos)
397 398 else:
398 399 event.Skip()
399 400
400 401 return catched
401 402
402 403
403 404 def _on_key_up(self, event, skip=True):
404 405 """ If cursor is outside the editing region, put it back.
405 406 """
406 407 event.Skip()
407 408 if self.GetCurrentPos() < self.current_prompt_pos:
408 409 self.GotoPos(self.current_prompt_pos)
409 410
410 411
411 412
412 413
413 414 if __name__ == '__main__':
414 415 # Some simple code to test the console widget.
415 416 class MainWindow(wx.Frame):
416 417 def __init__(self, parent, id, title):
417 418 wx.Frame.__init__(self, parent, id, title, size=(300,250))
418 419 self._sizer = wx.BoxSizer(wx.VERTICAL)
419 420 self.console_widget = ConsoleWidget(self)
420 421 self._sizer.Add(self.console_widget, 1, wx.EXPAND)
421 422 self.SetSizer(self._sizer)
422 423 self.SetAutoLayout(1)
423 424 self.Show(True)
424 425
425 426 app = wx.PySimpleApp()
426 427 w = MainWindow(None, wx.ID_ANY, 'ConsoleWidget')
427 428 w.SetSize((780, 460))
428 429 w.Show()
429 430
430 431 app.MainLoop()
431 432
432 433
@@ -1,75 +1,75 b''
1 1 """
2 2 Entry point for a simple application giving a graphical frontend to
3 3 ipython.
4 4 """
5 5
6 6 import wx
7 7 from wx_frontend import WxController
8 8 import __builtin__
9 9
10 10 class IPythonXController(WxController):
11 11 """ Sub class of WxController that adds some application-specific
12 12 bindings.
13 13 """
14 14
15 15 def __init__(self, *args, **kwargs):
16 16 WxController.__init__(self, *args, **kwargs)
17 17 self.ipython0.ask_exit = self.do_exit
18 18
19 19
20 20 def _on_key_down(self, event, skip=True):
21 21 # Intercept Ctrl-D to quit
22 22 if event.KeyCode == ord('D') and event.ControlDown() and \
23 23 self.get_current_edit_buffer()=='' and \
24 not self.raw_input == __builtin__.raw_input:
24 self._input_state == 'readline':
25 25 wx.CallAfter(self.ask_exit)
26 26 else:
27 27 WxController._on_key_down(self, event, skip=skip)
28 28
29 29
30 30 def ask_exit(self):
31 31 """ Ask the user whether to exit.
32 32 """
33 33 self.write('\n')
34 34 self.capture_output()
35 35 self.ipython0.shell.exit()
36 36 self.release_output()
37 37 wx.Yield()
38 38 if not self.ipython0.exit_now:
39 39 self.new_prompt(self.prompt % (self.last_result['number'] + 1))
40 40
41 41
42 42 def do_exit(self):
43 43 """ Exits the interpreter, kills the windows.
44 44 """
45 45 WxController.do_exit(self)
46 46 self.release_output()
47 47 wx.CallAfter(wx.Exit)
48 48
49 49
50 50
51 51 class IPythonX(wx.Frame):
52 52 """ Main frame of the IPythonX app.
53 53 """
54 54
55 55 def __init__(self, parent, id, title):
56 56 wx.Frame.__init__(self, parent, id, title, size=(300,250))
57 57 self._sizer = wx.BoxSizer(wx.VERTICAL)
58 58 self.shell = IPythonXController(self)
59 59 self._sizer.Add(self.shell, 1, wx.EXPAND)
60 60 self.SetSizer(self._sizer)
61 61 self.SetAutoLayout(1)
62 62 self.Show(True)
63 63
64 64
65 65 def main():
66 66 app = wx.PySimpleApp()
67 67 frame = IPythonX(None, wx.ID_ANY, 'IPythonX')
68 68 frame.shell.SetFocus()
69 69 frame.shell.app = app
70 70 frame.SetSize((680, 460))
71 71
72 72 app.MainLoop()
73 73
74 74 if __name__ == '__main__':
75 75 main()
@@ -1,397 +1,398 b''
1 1 # encoding: utf-8 -*- test-case-name:
2 2 # FIXME: Need to add tests.
3 3 # ipython1.frontend.cocoa.tests.test_cocoa_frontend -*-
4 4
5 5 """Classes to provide a Wx frontend to the
6 6 IPython.kernel.core.interpreter.
7 7
8 8 """
9 9
10 10 __docformat__ = "restructuredtext en"
11 11
12 12 #-------------------------------------------------------------------------------
13 13 # Copyright (C) 2008 The IPython Development Team
14 14 #
15 15 # Distributed under the terms of the BSD License. The full license is in
16 16 # the file COPYING, distributed as part of this software.
17 17 #-------------------------------------------------------------------------------
18 18
19 19 #-------------------------------------------------------------------------------
20 20 # Imports
21 21 #-------------------------------------------------------------------------------
22 22
23 23
24 24 import wx
25 25 import re
26 26 from wx import stc
27 27 from console_widget import ConsoleWidget
28 28 import __builtin__
29 29 from time import sleep
30 30 import sys
31 31 import signal
32 32
33 33 from threading import Lock
34 34
35 35 from IPython.frontend.piped_process import PipedProcess
36 36 from IPython.frontend.prefilterfrontend import PrefilterFrontEnd
37 37
38 38 #_COMMAND_BG = '#FAFAF1' # Nice green
39 39 _RUNNING_BUFFER_BG = '#FDFFD3' # Nice yellow
40 40 _ERROR_BG = '#FFF1F1' # Nice red
41 41
42 42 _RUNNING_BUFFER_MARKER = 31
43 43 _ERROR_MARKER = 30
44 44
45 45 #-------------------------------------------------------------------------------
46 46 # Classes to implement the Wx frontend
47 47 #-------------------------------------------------------------------------------
48 48 class WxController(PrefilterFrontEnd, ConsoleWidget):
49 49
50 50 output_prompt = \
51 51 '\x01\x1b[0;31m\x02Out[\x01\x1b[1;31m\x02%i\x01\x1b[0;31m\x02]: \x01\x1b[0m\x02'
52 52
53 53 # Print debug info on what is happening to the console.
54 54 debug = True
55 55
56 56 # The title of the terminal, as captured through the ANSI escape
57 57 # sequences.
58 58
59 59 def _set_title(self, title):
60 60 return self.Parent.SetTitle(title)
61 61
62 62 def _get_title(self):
63 63 return self.Parent.GetTitle()
64 64
65 65 title = property(_get_title, _set_title)
66 66
67 67 #--------------------------------------------------------------------------
68 68 # Private Attributes
69 69 #--------------------------------------------------------------------------
70 70
71 71 # A flag governing the behavior of the input. Can be:
72 72 #
73 73 # 'readline' for readline-like behavior with a prompt
74 74 # and an edit buffer.
75 75 # 'subprocess' for sending the raw input directly to a
76 76 # subprocess.
77 77 # 'buffering' for buffering of the input, that will be used
78 78 # when the input state switches back to another state.
79 79 _input_state = 'readline'
80 80
81 81 # Attribute to store reference to the pipes of a subprocess, if we
82 82 # are running any.
83 83 _running_process = False
84 84
85 85 # A queue for writing fast streams to the screen without flooding the
86 86 # event loop
87 87 _out_buffer = []
88 88
89 89 # A lock to lock the _out_buffer to make sure we don't empty it
90 90 # while it is being swapped
91 91 _out_buffer_lock = Lock()
92 92
93 93 #--------------------------------------------------------------------------
94 94 # Public API
95 95 #--------------------------------------------------------------------------
96 96
97 97 def __init__(self, parent, id=wx.ID_ANY, pos=wx.DefaultPosition,
98 98 size=wx.DefaultSize, style=wx.CLIP_CHILDREN,
99 99 *args, **kwds):
100 100 """ Create Shell instance.
101 101 """
102 102 ConsoleWidget.__init__(self, parent, id, pos, size, style)
103 103 PrefilterFrontEnd.__init__(self)
104 104
105 105 # Marker for running buffer.
106 106 self.MarkerDefine(_RUNNING_BUFFER_MARKER, stc.STC_MARK_BACKGROUND,
107 107 background=_RUNNING_BUFFER_BG)
108 108 # Marker for tracebacks.
109 109 self.MarkerDefine(_ERROR_MARKER, stc.STC_MARK_BACKGROUND,
110 110 background=_ERROR_BG)
111 111
112 112 # A time for flushing the write buffer
113 113 BUFFER_FLUSH_TIMER_ID = 100
114 114 self._buffer_flush_timer = wx.Timer(self, BUFFER_FLUSH_TIMER_ID)
115 115 wx.EVT_TIMER(self, BUFFER_FLUSH_TIMER_ID, self._buffer_flush)
116 116
117 117 def do_completion(self):
118 118 """ Do code completion.
119 119 """
120 120 if self.debug:
121 121 print >>sys.__stdout__, "do_completion",
122 122 line = self.get_current_edit_buffer()
123 123 new_line, completions = self.complete(line)
124 124 if len(completions)>1:
125 125 self.write_completion(completions)
126 126 self.replace_current_edit_buffer(new_line)
127 127 if self.debug:
128 128 print >>sys.__stdout__, completions
129 129
130 130
131 131 def do_calltip(self):
132 132 if self.debug:
133 133 print >>sys.__stdout__, "do_calltip"
134 134 separators = re.compile('[\s\{\}\[\]\(\)\= ,:]')
135 135 symbol = self.get_current_edit_buffer()
136 136 symbol_string = separators.split(symbol)[-1]
137 137 base_symbol_string = symbol_string.split('.')[0]
138 138 if base_symbol_string in self.shell.user_ns:
139 139 symbol = self.shell.user_ns[base_symbol_string]
140 140 elif base_symbol_string in self.shell.user_global_ns:
141 141 symbol = self.shell.user_global_ns[base_symbol_string]
142 142 elif base_symbol_string in __builtin__.__dict__:
143 143 symbol = __builtin__.__dict__[base_symbol_string]
144 144 else:
145 145 return False
146 146 for name in symbol_string.split('.')[1:] + ['__doc__']:
147 147 symbol = getattr(symbol, name)
148 148 try:
149 149 self.AutoCompCancel()
150 150 wx.Yield()
151 151 self.CallTipShow(self.GetCurrentPos(), symbol)
152 152 except:
153 153 # The retrieve symbol couldn't be converted to a string
154 154 pass
155 155
156 156
157 157 def popup_completion(self, create=False):
158 158 """ Updates the popup completion menu if it exists. If create is
159 159 true, open the menu.
160 160 """
161 161 if self.debug:
162 162 print >>sys.__stdout__, "popup_completion",
163 163 line = self.get_current_edit_buffer()
164 164 if (self.AutoCompActive() and not line[-1] == '.') \
165 165 or create==True:
166 166 suggestion, completions = self.complete(line)
167 167 offset=0
168 168 if completions:
169 169 complete_sep = re.compile('[\s\{\}\[\]\(\)\= ,:]')
170 170 residual = complete_sep.split(line)[-1]
171 171 offset = len(residual)
172 172 self.pop_completion(completions, offset=offset)
173 173 if self.debug:
174 174 print >>sys.__stdout__, completions
175 175
176 176
177 177 def new_prompt(self, prompt):
178 178 self._input_state = 'readline'
179 179 ConsoleWidget.new_prompt(self, prompt)
180 180
181 181
182 182 def raw_input(self, prompt):
183 183 """ A replacement from python's raw_input.
184 184 """
185 185 self.new_prompt(prompt)
186 186 self.waiting = True
187 187 self.__old_on_enter = self._on_enter
188 188 def my_on_enter():
189 189 self.waiting = False
190 190 self._on_enter = my_on_enter
191 191 # XXX: Busy waiting, ugly.
192 192 while self.waiting:
193 193 wx.Yield()
194 194 sleep(0.1)
195 195 self._on_enter = self.__old_on_enter
196 196 self._input_state = 'buffering'
197 197 return self.get_current_edit_buffer().rstrip('\n')
198 198
199 199
200 200 def execute(self, python_string, raw_string=None):
201 201 self._input_state = 'buffering'
202 202 self.CallTipCancel()
203 203 self._cursor = wx.BusyCursor()
204 204 if raw_string is None:
205 205 raw_string = python_string
206 206 end_line = self.current_prompt_line \
207 207 + max(1, len(raw_string.split('\n'))-1)
208 208 for i in range(self.current_prompt_line, end_line):
209 209 self.MarkerAdd(i, _RUNNING_BUFFER_MARKER)
210 210 # Update the display:
211 211 wx.Yield()
212 212 self.GotoPos(self.GetLength())
213 213 PrefilterFrontEnd.execute(self, python_string, raw_string=raw_string)
214 214
215 215
216 216 def capture_output(self):
217 217 self.__old_raw_input = __builtin__.raw_input
218 218 __builtin__.raw_input = self.raw_input
219 219 PrefilterFrontEnd.capture_output(self)
220 220
221 221
222 222 def release_output(self):
223 223 __builtin__.raw_input = self.__old_raw_input
224 224 PrefilterFrontEnd.capture_output(self)
225 225
226 226
227 227 def after_execute(self):
228 228 PrefilterFrontEnd.after_execute(self)
229 229 if hasattr(self, '_cursor'):
230 230 del self._cursor
231 231
232 232
233 233 def system_call(self, command_string):
234 234 self._input_state = 'subprocess'
235 235 self._running_process = PipedProcess(command_string,
236 236 out_callback=self.buffered_write,
237 237 end_callback = self._end_system_call)
238 238 self._running_process.start()
239 239 # XXX: another one of these polling loops to have a blocking
240 240 # call
241 241 wx.Yield()
242 242 while self._running_process:
243 243 wx.Yield()
244 244 sleep(0.1)
245 245 # Be sure to flush the buffer.
246 246 self._buffer_flush(event=None)
247 247
248 248
249 249 def buffered_write(self, text):
250 250 """ A write method for streams, that caches the stream in order
251 251 to avoid flooding the event loop.
252 252
253 253 This can be called outside of the main loop, in separate
254 254 threads.
255 255 """
256 256 self._out_buffer_lock.acquire()
257 257 self._out_buffer.append(text)
258 258 self._out_buffer_lock.release()
259 259 if not self._buffer_flush_timer.IsRunning():
260 self._buffer_flush_timer.Start(100) # milliseconds
260 wx.CallAfter(self._buffer_flush_timer.Start, 100) # milliseconds
261 261
262 262
263 263 def show_traceback(self):
264 264 start_line = self.GetCurrentLine()
265 265 PrefilterFrontEnd.show_traceback(self)
266 266 wx.Yield()
267 267 for i in range(start_line, self.GetCurrentLine()):
268 268 self.MarkerAdd(i, _ERROR_MARKER)
269 269
270 270
271 271 #--------------------------------------------------------------------------
272 272 # Private API
273 273 #--------------------------------------------------------------------------
274 274
275 275 def _on_key_down(self, event, skip=True):
276 276 """ Capture the character events, let the parent
277 277 widget handle them, and put our logic afterward.
278 278 """
279 279 print >>sys.__stderr__, event.KeyCode
280 280 current_line_number = self.GetCurrentLine()
281 281 if event.KeyCode in (ord('c'), ord('C')) and event.ControlDown():
282 282 # Capture Control-C
283 283 if self._input_state == 'subprocess':
284 284 if self.debug:
285 285 print >>sys.__stderr__, 'Killing running process'
286 286 self._running_process.process.kill()
287 287 elif self._input_state == 'buffering':
288 288 if self.debug:
289 289 print >>sys.__stderr__, 'Raising KeyboardException'
290 290 raise KeyboardException
291 291 # XXX: We need to make really sure we
292 292 # get back to a prompt.
293 293 elif self._input_state == 'subprocess' and event.KeyCode<256 \
294 294 and event.Modifiers in (wx.MOD_NONE, wx.MOD_WIN):
295 295 # We are running a process, we redirect keys.
296 296 ConsoleWidget._on_key_down(self, event, skip=skip)
297 297 if self.debug:
298 298 print >>sys.__stderr__, chr(event.KeyCode)
299 299 self._running_process.process.stdin.write(chr(event.KeyCode))
300 self._running_process.process.stdin.flush()
300 301 elif event.KeyCode in (ord('('), 57):
301 302 # Calltips
302 303 event.Skip()
303 304 self.do_calltip()
304 305 elif self.AutoCompActive():
305 306 event.Skip()
306 307 if event.KeyCode in (wx.WXK_BACK, wx.WXK_DELETE):
307 308 wx.CallAfter(self.popup_completion, create=True)
308 309 elif not event.KeyCode in (wx.WXK_UP, wx.WXK_DOWN, wx.WXK_LEFT,
309 310 wx.WXK_RIGHT):
310 311 wx.CallAfter(self.popup_completion)
311 312 else:
312 313 # Up history
313 314 if event.KeyCode == wx.WXK_UP and (
314 315 ( current_line_number == self.current_prompt_line and
315 316 event.Modifiers in (wx.MOD_NONE, wx.MOD_WIN) )
316 317 or event.ControlDown() ):
317 318 new_buffer = self.get_history_previous(
318 319 self.get_current_edit_buffer())
319 320 if new_buffer is not None:
320 321 self.replace_current_edit_buffer(new_buffer)
321 322 if self.GetCurrentLine() > self.current_prompt_line:
322 323 # Go to first line, for seemless history up.
323 324 self.GotoPos(self.current_prompt_pos)
324 325 # Down history
325 326 elif event.KeyCode == wx.WXK_DOWN and (
326 327 ( current_line_number == self.LineCount -1 and
327 328 event.Modifiers in (wx.MOD_NONE, wx.MOD_WIN) )
328 329 or event.ControlDown() ):
329 330 new_buffer = self.get_history_next()
330 331 if new_buffer is not None:
331 332 self.replace_current_edit_buffer(new_buffer)
332 333 # Tab-completion
333 334 elif event.KeyCode == ord('\t'):
334 335 last_line = self.get_current_edit_buffer().split('\n')[-1]
335 336 if not re.match(r'^\s*$', last_line):
336 337 self.do_completion()
337 338 else:
338 339 event.Skip()
339 340 else:
340 341 ConsoleWidget._on_key_down(self, event, skip=skip)
341 342
342 343
343 344 def _on_key_up(self, event, skip=True):
344 345 if event.KeyCode in (59, ord('.')):
345 346 # Intercepting '.'
346 347 event.Skip()
347 348 self.popup_completion(create=True)
348 349 else:
349 350 ConsoleWidget._on_key_up(self, event, skip=skip)
350 351
351 352
352 353 def _on_enter(self):
353 354 if self.debug:
354 355 print >>sys.__stdout__, repr(self.get_current_edit_buffer())
355 356 PrefilterFrontEnd._on_enter(self)
356 357
357 358
358 359 def _end_system_call(self):
359 360 """ Called at the end of a system call.
360 361 """
361 362 print >>sys.__stderr__, 'End of system call'
362 363 self._input_state = 'buffering'
363 364 self._running_process = False
364 365
365 366
366 367 def _buffer_flush(self, event):
367 368 """ Called by the timer to flush the write buffer.
368 369
369 370 This is always called in the mainloop, by the wx timer.
370 371 """
371 372 self._out_buffer_lock.acquire()
372 373 _out_buffer = self._out_buffer
373 374 self._out_buffer = []
374 375 self._out_buffer_lock.release()
375 self.write(''.join(_out_buffer))
376 self.write(''.join(_out_buffer), refresh=False)
376 377 self._buffer_flush_timer.Stop()
377 378
378 379
379 380 if __name__ == '__main__':
380 381 class MainWindow(wx.Frame):
381 382 def __init__(self, parent, id, title):
382 383 wx.Frame.__init__(self, parent, id, title, size=(300,250))
383 384 self._sizer = wx.BoxSizer(wx.VERTICAL)
384 385 self.shell = WxController(self)
385 386 self._sizer.Add(self.shell, 1, wx.EXPAND)
386 387 self.SetSizer(self._sizer)
387 388 self.SetAutoLayout(1)
388 389 self.Show(True)
389 390
390 391 app = wx.PySimpleApp()
391 392 frame = MainWindow(None, wx.ID_ANY, 'Ipython')
392 393 frame.shell.SetFocus()
393 394 frame.SetSize((680, 460))
394 395 self = frame.shell
395 396
396 397 app.MainLoop()
397 398
General Comments 0
You need to be logged in to leave comments. Login now