##// END OF EJS Templates
Merge with upstream
Fernando Perez -
r1507:2d65d859 merge
parent child Browse files
Show More
@@ -0,0 +1,19 b''
1 """
2 Package for dealing for process execution in a callback environment, in a
3 portable way.
4
5 killable_process.py is a wrapper of subprocess.Popen that allows the
6 subprocess and its children to be killed in a reliable way, including
7 under windows.
8
9 winprocess.py is required by killable_process.py to kill processes under
10 windows.
11
12 piped_process.py wraps process execution with callbacks to print output,
13 in a non-blocking way. It can be used to interact with a subprocess in eg
14 a GUI event loop.
15 """
16
17 from pipedprocess import PipedProcess
18
19
@@ -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
@@ -0,0 +1,74 b''
1 # encoding: utf-8
2 """
3 Object for encapsulating process execution by using callbacks for stdout,
4 stderr and stdin.
5 """
6 __docformat__ = "restructuredtext en"
7
8 #-------------------------------------------------------------------------------
9 # Copyright (C) 2008 The IPython Development Team
10 #
11 # Distributed under the terms of the BSD License. The full license is in
12 # the file COPYING, distributed as part of this software.
13 #-------------------------------------------------------------------------------
14
15 #-------------------------------------------------------------------------------
16 # Imports
17 #-------------------------------------------------------------------------------
18 from killableprocess import Popen, PIPE
19 from threading import Thread
20 from time import sleep
21 import os
22
23 class PipedProcess(Thread):
24 """ Class that encapsulates process execution by using callbacks for
25 stdout, stderr and stdin, and providing a reliable way of
26 killing it.
27 """
28
29 def __init__(self, command_string, out_callback,
30 end_callback=None,):
31 """ command_string: the command line executed to start the
32 process.
33
34 out_callback: the python callable called on stdout/stderr.
35
36 end_callback: an optional callable called when the process
37 finishes.
38
39 These callbacks are called from a different thread as the
40 thread from which is started.
41 """
42 self.command_string = command_string
43 self.out_callback = out_callback
44 self.end_callback = end_callback
45 Thread.__init__(self)
46
47
48 def run(self):
49 """ Start the process and hook up the callbacks.
50 """
51 env = os.environ
52 env['TERM'] = 'xterm'
53 process = Popen((self.command_string + ' 2>&1', ), shell=True,
54 env=env,
55 universal_newlines=True,
56 stdout=PIPE, stdin=PIPE, )
57 self.process = process
58 while True:
59 out_char = process.stdout.read(1)
60 if out_char == '':
61 if process.poll() is not None:
62 # The process has finished
63 break
64 else:
65 # The process is not giving any interesting
66 # output. No use polling it immediatly.
67 sleep(0.1)
68 else:
69 self.out_callback(out_char)
70
71 if self.end_callback is not None:
72 self.end_callback()
73
74
@@ -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
@@ -0,0 +1,92 b''
1 """
2 Base front end class for all async frontends.
3 """
4 __docformat__ = "restructuredtext en"
5
6 #-------------------------------------------------------------------------------
7 # Copyright (C) 2008 The IPython Development Team
8 #
9 # Distributed under the terms of the BSD License. The full license is in
10 # the file COPYING, distributed as part of this software.
11 #-------------------------------------------------------------------------------
12
13
14 #-------------------------------------------------------------------------------
15 # Imports
16 #-------------------------------------------------------------------------------
17 import uuid
18
19 try:
20 from zope.interface import Interface, Attribute, implements, classProvides
21 except ImportError, e:
22 e.message = """%s
23 ________________________________________________________________________________
24 zope.interface is required to run asynchronous frontends.""" % e.message
25 e.args = (e.message, ) + e.args[1:]
26
27 from frontendbase import FrontEndBase, IFrontEnd, IFrontEndFactory
28
29 from IPython.kernel.engineservice import IEngineCore
30 from IPython.kernel.core.history import FrontEndHistory
31
32 try:
33 from twisted.python.failure import Failure
34 except ImportError, e:
35 e.message = """%s
36 ________________________________________________________________________________
37 twisted is required to run asynchronous frontends.""" % e.message
38 e.args = (e.message, ) + e.args[1:]
39
40
41
42
43 class AsyncFrontEndBase(FrontEndBase):
44 """
45 Overrides FrontEndBase to wrap execute in a deferred result.
46 All callbacks are made as callbacks on the deferred result.
47 """
48
49 implements(IFrontEnd)
50 classProvides(IFrontEndFactory)
51
52 def __init__(self, engine=None, history=None):
53 assert(engine==None or IEngineCore.providedBy(engine))
54 self.engine = IEngineCore(engine)
55 if history is None:
56 self.history = FrontEndHistory(input_cache=[''])
57 else:
58 self.history = history
59
60
61 def execute(self, block, blockID=None):
62 """Execute the block and return the deferred result.
63
64 Parameters:
65 block : {str, AST}
66 blockID : any
67 Caller may provide an ID to identify this block.
68 result['blockID'] := blockID
69
70 Result:
71 Deferred result of self.interpreter.execute
72 """
73
74 if(not self.is_complete(block)):
75 return Failure(Exception("Block is not compilable"))
76
77 if(blockID == None):
78 blockID = uuid.uuid4() #random UUID
79
80 d = self.engine.execute(block)
81 d.addCallback(self._add_history, block=block)
82 d.addCallbacks(self._add_block_id_for_result,
83 errback=self._add_block_id_for_failure,
84 callbackArgs=(blockID,),
85 errbackArgs=(blockID,))
86 d.addBoth(self.update_cell_prompt, blockID=blockID)
87 d.addCallbacks(self.render_result,
88 errback=self.render_error)
89
90 return d
91
92
@@ -0,0 +1,294 b''
1 """
2 Base front end class for all line-oriented frontends, rather than
3 block-oriented.
4
5 Currently this focuses on synchronous frontends.
6 """
7 __docformat__ = "restructuredtext en"
8
9 #-------------------------------------------------------------------------------
10 # Copyright (C) 2008 The IPython Development Team
11 #
12 # Distributed under the terms of the BSD License. The full license is in
13 # the file COPYING, distributed as part of this software.
14 #-------------------------------------------------------------------------------
15
16 #-------------------------------------------------------------------------------
17 # Imports
18 #-------------------------------------------------------------------------------
19 import re
20
21 import IPython
22 import sys
23
24 from frontendbase import FrontEndBase
25 from IPython.kernel.core.interpreter import Interpreter
26
27 def common_prefix(strings):
28 """ Given a list of strings, return the common prefix between all
29 these strings.
30 """
31 ref = strings[0]
32 prefix = ''
33 for size in range(len(ref)):
34 test_prefix = ref[:size+1]
35 for string in strings[1:]:
36 if not string.startswith(test_prefix):
37 return prefix
38 prefix = test_prefix
39
40 return prefix
41
42 #-------------------------------------------------------------------------------
43 # Base class for the line-oriented front ends
44 #-------------------------------------------------------------------------------
45 class LineFrontEndBase(FrontEndBase):
46 """ Concrete implementation of the FrontEndBase class. This is meant
47 to be the base class behind all the frontend that are line-oriented,
48 rather than block-oriented.
49 """
50
51 # We need to keep the prompt number, to be able to increment
52 # it when there is an exception.
53 prompt_number = 1
54
55 # We keep a reference to the last result: it helps testing and
56 # programatic control of the frontend.
57 last_result = dict(number=0)
58
59 # The input buffer being edited
60 input_buffer = ''
61
62 # Set to true for debug output
63 debug = False
64
65 # A banner to print at startup
66 banner = None
67
68 #--------------------------------------------------------------------------
69 # FrontEndBase interface
70 #--------------------------------------------------------------------------
71
72 def __init__(self, shell=None, history=None, banner=None, *args, **kwargs):
73 if shell is None:
74 shell = Interpreter()
75 FrontEndBase.__init__(self, shell=shell, history=history)
76
77 if banner is not None:
78 self.banner = banner
79 if self.banner is not None:
80 self.write(self.banner, refresh=False)
81
82 self.new_prompt(self.input_prompt_template.substitute(number=1))
83
84
85 def complete(self, line):
86 """Complete line in engine's user_ns
87
88 Parameters
89 ----------
90 line : string
91
92 Result
93 ------
94 The replacement for the line and the list of possible completions.
95 """
96 completions = self.shell.complete(line)
97 complete_sep = re.compile('[\s\{\}\[\]\(\)\=]')
98 if completions:
99 prefix = common_prefix(completions)
100 residual = complete_sep.split(line)[:-1]
101 line = line[:-len(residual)] + prefix
102 return line, completions
103
104
105 def render_result(self, result):
106 """ Frontend-specific rendering of the result of a calculation
107 that has been sent to an engine.
108 """
109 if 'stdout' in result and result['stdout']:
110 self.write('\n' + result['stdout'])
111 if 'display' in result and result['display']:
112 self.write("%s%s\n" % (
113 self.output_prompt_template.substitute(
114 number=result['number']),
115 result['display']['pprint']
116 ) )
117
118
119 def render_error(self, failure):
120 """ Frontend-specific rendering of error.
121 """
122 self.write('\n\n'+str(failure)+'\n\n')
123 return failure
124
125
126 def is_complete(self, string):
127 """ Check if a string forms a complete, executable set of
128 commands.
129
130 For the line-oriented frontend, multi-line code is not executed
131 as soon as it is complete: the users has to enter two line
132 returns.
133 """
134 if string in ('', '\n'):
135 # Prefiltering, eg through ipython0, may return an empty
136 # string although some operations have been accomplished. We
137 # thus want to consider an empty string as a complete
138 # statement.
139 return True
140 elif ( len(self.input_buffer.split('\n'))>2
141 and not re.findall(r"\n[\t ]*\n[\t ]*$", string)):
142 return False
143 else:
144 # Add line returns here, to make sure that the statement is
145 # complete.
146 return FrontEndBase.is_complete(self, string.rstrip() + '\n\n')
147
148
149 def write(self, string, refresh=True):
150 """ Write some characters to the display.
151
152 Subclass should overide this method.
153
154 The refresh keyword argument is used in frontends with an
155 event loop, to choose whether the write should trigget an UI
156 refresh, and thus be syncrhonous, or not.
157 """
158 print >>sys.__stderr__, string
159
160
161 def execute(self, python_string, raw_string=None):
162 """ Stores the raw_string in the history, and sends the
163 python string to the interpreter.
164 """
165 if raw_string is None:
166 raw_string = python_string
167 # Create a false result, in case there is an exception
168 self.last_result = dict(number=self.prompt_number)
169 try:
170 self.history.input_cache[-1] = raw_string.rstrip()
171 result = self.shell.execute(python_string)
172 self.last_result = result
173 self.render_result(result)
174 except:
175 self.show_traceback()
176 finally:
177 self.after_execute()
178
179 #--------------------------------------------------------------------------
180 # LineFrontEndBase interface
181 #--------------------------------------------------------------------------
182
183 def prefilter_input(self, string):
184 """ Priflter the input to turn it in valid python.
185 """
186 string = string.replace('\r\n', '\n')
187 string = string.replace('\t', 4*' ')
188 # Clean the trailing whitespace
189 string = '\n'.join(l.rstrip() for l in string.split('\n'))
190 return string
191
192
193 def after_execute(self):
194 """ All the operations required after an execution to put the
195 terminal back in a shape where it is usable.
196 """
197 self.prompt_number += 1
198 self.new_prompt(self.input_prompt_template.substitute(
199 number=(self.last_result['number'] + 1)))
200 # Start a new empty history entry
201 self._add_history(None, '')
202 self.history_cursor = len(self.history.input_cache) - 1
203
204
205 def complete_current_input(self):
206 """ Do code completion on current line.
207 """
208 if self.debug:
209 print >>sys.__stdout__, "complete_current_input",
210 line = self.input_buffer
211 new_line, completions = self.complete(line)
212 if len(completions)>1:
213 self.write_completion(completions)
214 self.input_buffer = new_line
215 if self.debug:
216 print >>sys.__stdout__, completions
217
218
219 def get_line_width(self):
220 """ Return the width of the line in characters.
221 """
222 return 80
223
224
225 def write_completion(self, possibilities):
226 """ Write the list of possible completions.
227 """
228 current_buffer = self.input_buffer
229
230 self.write('\n')
231 max_len = len(max(possibilities, key=len)) + 1
232
233 # Now we check how much symbol we can put on a line...
234 chars_per_line = self.get_line_width()
235 symbols_per_line = max(1, chars_per_line/max_len)
236
237 pos = 1
238 buf = []
239 for symbol in possibilities:
240 if pos < symbols_per_line:
241 buf.append(symbol.ljust(max_len))
242 pos += 1
243 else:
244 buf.append(symbol.rstrip() + '\n')
245 pos = 1
246 self.write(''.join(buf))
247 self.new_prompt(self.input_prompt_template.substitute(
248 number=self.last_result['number'] + 1))
249 self.input_buffer = current_buffer
250
251
252 def new_prompt(self, prompt):
253 """ Prints a prompt and starts a new editing buffer.
254
255 Subclasses should use this method to make sure that the
256 terminal is put in a state favorable for a new line
257 input.
258 """
259 self.input_buffer = ''
260 self.write(prompt)
261
262
263 #--------------------------------------------------------------------------
264 # Private API
265 #--------------------------------------------------------------------------
266
267 def _on_enter(self):
268 """ Called when the return key is pressed in a line editing
269 buffer.
270 """
271 current_buffer = self.input_buffer
272 cleaned_buffer = self.prefilter_input(current_buffer)
273 if self.is_complete(cleaned_buffer):
274 self.execute(cleaned_buffer, raw_string=current_buffer)
275 else:
276 self.input_buffer += self._get_indent_string(
277 current_buffer[:-1])
278 if current_buffer[:-1].split('\n')[-1].rstrip().endswith(':'):
279 self.input_buffer += '\t'
280
281
282 def _get_indent_string(self, string):
283 """ Return the string of whitespace that prefixes a line. Used to
284 add the right amount of indendation when creating a new line.
285 """
286 string = string.replace('\t', ' '*4)
287 string = string.split('\n')[-1]
288 indent_chars = len(string) - len(string.lstrip())
289 indent_string = '\t'*(indent_chars // 4) + \
290 ' '*(indent_chars % 4)
291
292 return indent_string
293
294
@@ -0,0 +1,221 b''
1 """
2 Frontend class that uses IPython0 to prefilter the inputs.
3
4 Using the IPython0 mechanism gives us access to the magics.
5
6 This is a transitory class, used here to do the transition between
7 ipython0 and ipython1. This class is meant to be short-lived as more
8 functionnality is abstracted out of ipython0 in reusable functions and
9 is added on the interpreter. This class can be a used to guide this
10 refactoring.
11 """
12 __docformat__ = "restructuredtext en"
13
14 #-------------------------------------------------------------------------------
15 # Copyright (C) 2008 The IPython Development Team
16 #
17 # Distributed under the terms of the BSD License. The full license is in
18 # the file COPYING, distributed as part of this software.
19 #-------------------------------------------------------------------------------
20
21 #-------------------------------------------------------------------------------
22 # Imports
23 #-------------------------------------------------------------------------------
24 import sys
25
26 from linefrontendbase import LineFrontEndBase, common_prefix
27
28 from IPython.ipmaker import make_IPython
29 from IPython.ipapi import IPApi
30 from IPython.kernel.core.redirector_output_trap import RedirectorOutputTrap
31
32 from IPython.kernel.core.sync_traceback_trap import SyncTracebackTrap
33
34 from IPython.genutils import Term
35 import pydoc
36 import os
37
38
39 def mk_system_call(system_call_function, command):
40 """ given a os.system replacement, and a leading string command,
41 returns a function that will execute the command with the given
42 argument string.
43 """
44 def my_system_call(args):
45 system_call_function("%s %s" % (command, args))
46 return my_system_call
47
48 #-------------------------------------------------------------------------------
49 # Frontend class using ipython0 to do the prefiltering.
50 #-------------------------------------------------------------------------------
51 class PrefilterFrontEnd(LineFrontEndBase):
52 """ Class that uses ipython0 to do prefilter the input, do the
53 completion and the magics.
54
55 The core trick is to use an ipython0 instance to prefilter the
56 input, and share the namespace between the interpreter instance used
57 to execute the statements and the ipython0 used for code
58 completion...
59 """
60
61 def __init__(self, ipython0=None, *args, **kwargs):
62 """ Parameters:
63 -----------
64
65 ipython0: an optional ipython0 instance to use for command
66 prefiltering and completion.
67 """
68 self.save_output_hooks()
69 if ipython0 is None:
70 # Instanciate an IPython0 interpreter to be able to use the
71 # prefiltering.
72 # XXX: argv=[] is a bit bold.
73 ipython0 = make_IPython(argv=[])
74 self.ipython0 = ipython0
75 # Set the pager:
76 self.ipython0.set_hook('show_in_pager',
77 lambda s, string: self.write("\n" + string))
78 self.ipython0.write = self.write
79 self._ip = _ip = IPApi(self.ipython0)
80 # Make sure the raw system call doesn't get called, as we don't
81 # have a stdin accessible.
82 self._ip.system = self.system_call
83 # XXX: Muck around with magics so that they work better
84 # in our environment
85 self.ipython0.magic_ls = mk_system_call(self.system_call,
86 'ls -CF')
87 # And now clean up the mess created by ipython0
88 self.release_output()
89 if not 'banner' in kwargs and self.banner is None:
90 kwargs['banner'] = self.ipython0.BANNER + """
91 This is the wx frontend, by Gael Varoquaux. This is EXPERIMENTAL code."""
92
93 LineFrontEndBase.__init__(self, *args, **kwargs)
94 # XXX: Hack: mix the two namespaces
95 self.shell.user_ns = self.ipython0.user_ns
96 self.shell.user_global_ns = self.ipython0.user_global_ns
97
98 self.shell.output_trap = RedirectorOutputTrap(
99 out_callback=self.write,
100 err_callback=self.write,
101 )
102 self.shell.traceback_trap = SyncTracebackTrap(
103 formatters=self.shell.traceback_trap.formatters,
104 )
105
106 #--------------------------------------------------------------------------
107 # FrontEndBase interface
108 #--------------------------------------------------------------------------
109
110 def show_traceback(self):
111 """ Use ipython0 to capture the last traceback and display it.
112 """
113 self.capture_output()
114 self.ipython0.showtraceback()
115 self.release_output()
116
117
118 def execute(self, python_string, raw_string=None):
119 if self.debug:
120 print 'Executing Python code:', repr(python_string)
121 self.capture_output()
122 LineFrontEndBase.execute(self, python_string,
123 raw_string=raw_string)
124 self.release_output()
125
126
127 def save_output_hooks(self):
128 """ Store all the output hooks we can think of, to be able to
129 restore them.
130
131 We need to do this early, as starting the ipython0 instance will
132 screw ouput hooks.
133 """
134 self.__old_cout_write = Term.cout.write
135 self.__old_cerr_write = Term.cerr.write
136 self.__old_stdout = sys.stdout
137 self.__old_stderr= sys.stderr
138 self.__old_help_output = pydoc.help.output
139 self.__old_display_hook = sys.displayhook
140
141
142 def capture_output(self):
143 """ Capture all the output mechanisms we can think of.
144 """
145 self.save_output_hooks()
146 Term.cout.write = self.write
147 Term.cerr.write = self.write
148 sys.stdout = Term.cout
149 sys.stderr = Term.cerr
150 pydoc.help.output = self.shell.output_trap.out
151
152
153 def release_output(self):
154 """ Release all the different captures we have made.
155 """
156 Term.cout.write = self.__old_cout_write
157 Term.cerr.write = self.__old_cerr_write
158 sys.stdout = self.__old_stdout
159 sys.stderr = self.__old_stderr
160 pydoc.help.output = self.__old_help_output
161 sys.displayhook = self.__old_display_hook
162
163
164 def complete(self, line):
165 word = line.split('\n')[-1].split(' ')[-1]
166 completions = self.ipython0.complete(word)
167 # FIXME: The proper sort should be done in the complete method.
168 key = lambda x: x.replace('_', '')
169 completions.sort(key=key)
170 if completions:
171 prefix = common_prefix(completions)
172 line = line[:-len(word)] + prefix
173 return line, completions
174
175
176 #--------------------------------------------------------------------------
177 # LineFrontEndBase interface
178 #--------------------------------------------------------------------------
179
180 def prefilter_input(self, input_string):
181 """ Using IPython0 to prefilter the commands to turn them
182 in executable statements that are valid Python strings.
183 """
184 input_string = LineFrontEndBase.prefilter_input(self, input_string)
185 filtered_lines = []
186 # The IPython0 prefilters sometime produce output. We need to
187 # capture it.
188 self.capture_output()
189 self.last_result = dict(number=self.prompt_number)
190 try:
191 for line in input_string.split('\n'):
192 filtered_lines.append(
193 self.ipython0.prefilter(line, False).rstrip())
194 except:
195 # XXX: probably not the right thing to do.
196 self.ipython0.showsyntaxerror()
197 self.after_execute()
198 finally:
199 self.release_output()
200
201 # Clean up the trailing whitespace, to avoid indentation errors
202 filtered_string = '\n'.join(filtered_lines)
203 return filtered_string
204
205
206 #--------------------------------------------------------------------------
207 # PrefilterFrontEnd interface
208 #--------------------------------------------------------------------------
209
210 def system_call(self, command_string):
211 """ Allows for frontend to define their own system call, to be
212 able capture output and redirect input.
213 """
214 return os.system(command_string)
215
216
217 def do_exit(self):
218 """ Exit the shell, cleanup and save the history.
219 """
220 self.ipython0.atexit_operations()
221
@@ -0,0 +1,157 b''
1 # encoding: utf-8
2 """
3 Test process execution and IO redirection.
4 """
5
6 __docformat__ = "restructuredtext en"
7
8 #-------------------------------------------------------------------------------
9 # Copyright (C) 2008 The IPython Development Team
10 #
11 # Distributed under the terms of the BSD License. The full license is
12 # in the file COPYING, distributed as part of this software.
13 #-------------------------------------------------------------------------------
14
15 from cStringIO import StringIO
16 import string
17
18 from IPython.ipapi import get as get_ipython0
19 from IPython.frontend.prefilterfrontend import PrefilterFrontEnd
20
21 class TestPrefilterFrontEnd(PrefilterFrontEnd):
22
23 input_prompt_template = string.Template('')
24 output_prompt_template = string.Template('')
25 banner = ''
26
27 def __init__(self):
28 ipython0 = get_ipython0().IP
29 self.out = StringIO()
30 PrefilterFrontEnd.__init__(self, ipython0=ipython0)
31 # Clean up the namespace for isolation between tests
32 user_ns = self.ipython0.user_ns
33 # We need to keep references to things so that they don't
34 # get garbage collected (this stinks).
35 self.shadow_ns = dict()
36 for i in self.ipython0.magic_who_ls():
37 self.shadow_ns[i] = user_ns.pop(i)
38 # Some more code for isolation (yeah, crazy)
39 self._on_enter()
40 self.out.flush()
41 self.out.reset()
42 self.out.truncate()
43
44 def write(self, string, *args, **kwargs):
45 self.out.write(string)
46
47 def _on_enter(self):
48 self.input_buffer += '\n'
49 PrefilterFrontEnd._on_enter(self)
50
51
52 def test_execution():
53 """ Test execution of a command.
54 """
55 f = TestPrefilterFrontEnd()
56 f.input_buffer = 'print 1'
57 f._on_enter()
58 out_value = f.out.getvalue()
59 assert out_value == '1\n'
60
61
62 def test_multiline():
63 """ Test execution of a multiline command.
64 """
65 f = TestPrefilterFrontEnd()
66 f.input_buffer = 'if True:'
67 f._on_enter()
68 f.input_buffer += 'print 1'
69 f._on_enter()
70 out_value = f.out.getvalue()
71 assert out_value == ''
72 f._on_enter()
73 out_value = f.out.getvalue()
74 assert out_value == '1\n'
75 f = TestPrefilterFrontEnd()
76 f.input_buffer='(1 +'
77 f._on_enter()
78 f.input_buffer += '0)'
79 f._on_enter()
80 out_value = f.out.getvalue()
81 assert out_value == ''
82 f._on_enter()
83 out_value = f.out.getvalue()
84 assert out_value == '1\n'
85
86
87 def test_capture():
88 """ Test the capture of output in different channels.
89 """
90 # Test on the OS-level stdout, stderr.
91 f = TestPrefilterFrontEnd()
92 f.input_buffer = \
93 'import os; out=os.fdopen(1, "w"); out.write("1") ; out.flush()'
94 f._on_enter()
95 out_value = f.out.getvalue()
96 assert out_value == '1'
97 f = TestPrefilterFrontEnd()
98 f.input_buffer = \
99 'import os; out=os.fdopen(2, "w"); out.write("1") ; out.flush()'
100 f._on_enter()
101 out_value = f.out.getvalue()
102 assert out_value == '1'
103
104
105 def test_magic():
106 """ Test the magic expansion and history.
107
108 This test is fairly fragile and will break when magics change.
109 """
110 f = TestPrefilterFrontEnd()
111 f.input_buffer += '%who'
112 f._on_enter()
113 out_value = f.out.getvalue()
114 assert out_value == 'Interactive namespace is empty.\n'
115
116
117 def test_help():
118 """ Test object inspection.
119 """
120 f = TestPrefilterFrontEnd()
121 f.input_buffer += "def f():"
122 f._on_enter()
123 f.input_buffer += "'foobar'"
124 f._on_enter()
125 f.input_buffer += "pass"
126 f._on_enter()
127 f._on_enter()
128 f.input_buffer += "f?"
129 f._on_enter()
130 assert 'traceback' not in f.last_result
131 ## XXX: ipython doctest magic breaks this. I have no clue why
132 #out_value = f.out.getvalue()
133 #assert out_value.split()[-1] == 'foobar'
134
135
136 def test_completion():
137 """ Test command-line completion.
138 """
139 f = TestPrefilterFrontEnd()
140 f.input_buffer = 'zzza = 1'
141 f._on_enter()
142 f.input_buffer = 'zzzb = 2'
143 f._on_enter()
144 f.input_buffer = 'zz'
145 f.complete_current_input()
146 out_value = f.out.getvalue()
147 assert out_value == '\nzzza zzzb '
148 assert f.input_buffer == 'zzz'
149
150
151 if __name__ == '__main__':
152 test_magic()
153 test_help()
154 test_execution()
155 test_multiline()
156 test_capture()
157 test_completion()
@@ -0,0 +1,63 b''
1 # encoding: utf-8
2 """
3 Test process execution and IO redirection.
4 """
5
6 __docformat__ = "restructuredtext en"
7
8 #-------------------------------------------------------------------------------
9 # Copyright (C) 2008 The IPython Development Team
10 #
11 # Distributed under the terms of the BSD License. The full license is
12 # in the file COPYING, distributed as part of this software.
13 #-------------------------------------------------------------------------------
14
15 from cStringIO import StringIO
16 from time import sleep
17 import sys
18
19 from IPython.frontend._process import PipedProcess
20
21 def test_capture_out():
22 """ A simple test to see if we can execute a process and get the output.
23 """
24 s = StringIO()
25 p = PipedProcess('echo 1', out_callback=s.write, )
26 p.start()
27 p.join()
28 assert s.getvalue() == '1\n'
29
30
31 def test_io():
32 """ Checks that we can send characters on stdin to the process.
33 """
34 s = StringIO()
35 p = PipedProcess(sys.executable + ' -c "a = raw_input(); print a"',
36 out_callback=s.write, )
37 p.start()
38 test_string = '12345\n'
39 while not hasattr(p, 'process'):
40 sleep(0.1)
41 p.process.stdin.write(test_string)
42 p.join()
43 assert s.getvalue() == test_string
44
45
46 def test_kill():
47 """ Check that we can kill a process, and its subprocess.
48 """
49 s = StringIO()
50 p = PipedProcess(sys.executable + ' -c "a = raw_input();"',
51 out_callback=s.write, )
52 p.start()
53 while not hasattr(p, 'process'):
54 sleep(0.1)
55 p.process.kill()
56 assert p.process.poll() is not None
57
58
59 if __name__ == '__main__':
60 test_capture_out()
61 test_io()
62 test_kill()
63
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
@@ -0,0 +1,428 b''
1 # encoding: utf-8
2 """
3 A Wx widget to act as a console and input commands.
4
5 This widget deals with prompts and provides an edit buffer
6 restricted to after the last prompt.
7 """
8
9 __docformat__ = "restructuredtext en"
10
11 #-------------------------------------------------------------------------------
12 # Copyright (C) 2008 The IPython Development Team
13 #
14 # Distributed under the terms of the BSD License. The full license is
15 # in the file COPYING, distributed as part of this software.
16 #-------------------------------------------------------------------------------
17
18 #-------------------------------------------------------------------------------
19 # Imports
20 #-------------------------------------------------------------------------------
21
22 import wx
23 import wx.stc as stc
24
25 from wx.py import editwindow
26 import sys
27 LINESEP = '\n'
28 if sys.platform == 'win32':
29 LINESEP = '\n\r'
30
31 import re
32
33 # FIXME: Need to provide an API for non user-generated display on the
34 # screen: this should not be editable by the user.
35
36 _DEFAULT_SIZE = 10
37 if sys.platform == 'darwin':
38 _DEFAULT_STYLE = 12
39
40 _DEFAULT_STYLE = {
41 'stdout' : 'fore:#0000FF',
42 'stderr' : 'fore:#007f00',
43 'trace' : 'fore:#FF0000',
44
45 'default' : 'size:%d' % _DEFAULT_SIZE,
46 'bracegood' : 'fore:#00AA00,back:#000000,bold',
47 'bracebad' : 'fore:#FF0000,back:#000000,bold',
48
49 # properties for the various Python lexer styles
50 'comment' : 'fore:#007F00',
51 'number' : 'fore:#007F7F',
52 'string' : 'fore:#7F007F,italic',
53 'char' : 'fore:#7F007F,italic',
54 'keyword' : 'fore:#00007F,bold',
55 'triple' : 'fore:#7F0000',
56 'tripledouble' : 'fore:#7F0000',
57 'class' : 'fore:#0000FF,bold,underline',
58 'def' : 'fore:#007F7F,bold',
59 'operator' : 'bold'
60 }
61
62 # new style numbers
63 _STDOUT_STYLE = 15
64 _STDERR_STYLE = 16
65 _TRACE_STYLE = 17
66
67
68 # system colors
69 #SYS_COLOUR_BACKGROUND = wx.SystemSettings.GetColour(wx.SYS_COLOUR_BACKGROUND)
70
71 #-------------------------------------------------------------------------------
72 # The console widget class
73 #-------------------------------------------------------------------------------
74 class ConsoleWidget(editwindow.EditWindow):
75 """ Specialized styled text control view for console-like workflow.
76
77 This widget is mainly interested in dealing with the prompt and
78 keeping the cursor inside the editing line.
79 """
80
81 # This is where the title captured from the ANSI escape sequences are
82 # stored.
83 title = 'Console'
84
85 # The buffer being edited.
86 def _set_input_buffer(self, string):
87 self.SetSelection(self.current_prompt_pos, self.GetLength())
88 self.ReplaceSelection(string)
89 self.GotoPos(self.GetLength())
90
91 def _get_input_buffer(self):
92 """ Returns the text in current edit buffer.
93 """
94 input_buffer = self.GetTextRange(self.current_prompt_pos,
95 self.GetLength())
96 input_buffer = input_buffer.replace(LINESEP, '\n')
97 return input_buffer
98
99 input_buffer = property(_get_input_buffer, _set_input_buffer)
100
101 style = _DEFAULT_STYLE.copy()
102
103 # Translation table from ANSI escape sequences to color. Override
104 # this to specify your colors.
105 ANSI_STYLES = {'0;30': [0, 'BLACK'], '0;31': [1, 'RED'],
106 '0;32': [2, 'GREEN'], '0;33': [3, 'BROWN'],
107 '0;34': [4, 'BLUE'], '0;35': [5, 'PURPLE'],
108 '0;36': [6, 'CYAN'], '0;37': [7, 'LIGHT GREY'],
109 '1;30': [8, 'DARK GREY'], '1;31': [9, 'RED'],
110 '1;32': [10, 'SEA GREEN'], '1;33': [11, 'YELLOW'],
111 '1;34': [12, 'LIGHT BLUE'], '1;35':
112 [13, 'MEDIUM VIOLET RED'],
113 '1;36': [14, 'LIGHT STEEL BLUE'], '1;37': [15, 'YELLOW']}
114
115 # The color of the carret (call _apply_style() after setting)
116 carret_color = 'BLACK'
117
118 #--------------------------------------------------------------------------
119 # Public API
120 #--------------------------------------------------------------------------
121
122 def __init__(self, parent, id=wx.ID_ANY, pos=wx.DefaultPosition,
123 size=wx.DefaultSize, style=0, ):
124 editwindow.EditWindow.__init__(self, parent, id, pos, size, style)
125 self._configure_scintilla()
126
127 self.Bind(wx.EVT_KEY_DOWN, self._on_key_down)
128 self.Bind(wx.EVT_KEY_UP, self._on_key_up)
129
130
131 def write(self, text, refresh=True):
132 """ Write given text to buffer, while translating the ansi escape
133 sequences.
134 """
135 # XXX: do not put print statements to sys.stdout/sys.stderr in
136 # this method, the print statements will call this method, as
137 # you will end up with an infinit loop
138 title = self.title_pat.split(text)
139 if len(title)>1:
140 self.title = title[-2]
141
142 text = self.title_pat.sub('', text)
143 segments = self.color_pat.split(text)
144 segment = segments.pop(0)
145 self.GotoPos(self.GetLength())
146 self.StartStyling(self.GetLength(), 0xFF)
147 try:
148 self.AppendText(segment)
149 except UnicodeDecodeError:
150 # XXX: Do I really want to skip the exception?
151 pass
152
153 if segments:
154 for ansi_tag, text in zip(segments[::2], segments[1::2]):
155 self.StartStyling(self.GetLength(), 0xFF)
156 try:
157 self.AppendText(text)
158 except UnicodeDecodeError:
159 # XXX: Do I really want to skip the exception?
160 pass
161
162 if ansi_tag not in self.ANSI_STYLES:
163 style = 0
164 else:
165 style = self.ANSI_STYLES[ansi_tag][0]
166
167 self.SetStyling(len(text), style)
168
169 self.GotoPos(self.GetLength())
170 if refresh:
171 # Maybe this is faster than wx.Yield()
172 self.ProcessEvent(wx.PaintEvent())
173 #wx.Yield()
174
175
176 def new_prompt(self, prompt):
177 """ Prints a prompt at start of line, and move the start of the
178 current block there.
179
180 The prompt can be given with ascii escape sequences.
181 """
182 self.write(prompt, refresh=False)
183 # now we update our cursor giving end of prompt
184 self.current_prompt_pos = self.GetLength()
185 self.current_prompt_line = self.GetCurrentLine()
186 wx.Yield()
187 self.EnsureCaretVisible()
188
189
190 def scroll_to_bottom(self):
191 maxrange = self.GetScrollRange(wx.VERTICAL)
192 self.ScrollLines(maxrange)
193
194
195 def pop_completion(self, possibilities, offset=0):
196 """ Pops up an autocompletion menu. Offset is the offset
197 in characters of the position at which the menu should
198 appear, relativ to the cursor.
199 """
200 self.AutoCompSetIgnoreCase(False)
201 self.AutoCompSetAutoHide(False)
202 self.AutoCompSetMaxHeight(len(possibilities))
203 self.AutoCompShow(offset, " ".join(possibilities))
204
205
206 def get_line_width(self):
207 """ Return the width of the line in characters.
208 """
209 return self.GetSize()[0]/self.GetCharWidth()
210
211 #--------------------------------------------------------------------------
212 # EditWindow API
213 #--------------------------------------------------------------------------
214
215 def OnUpdateUI(self, event):
216 """ Override the OnUpdateUI of the EditWindow class, to prevent
217 syntax highlighting both for faster redraw, and for more
218 consistent look and feel.
219 """
220
221 #--------------------------------------------------------------------------
222 # Private API
223 #--------------------------------------------------------------------------
224
225 def _apply_style(self):
226 """ Applies the colors for the different text elements and the
227 carret.
228 """
229 self.SetCaretForeground(self.carret_color)
230
231 #self.StyleClearAll()
232 self.StyleSetSpec(stc.STC_STYLE_BRACELIGHT,
233 "fore:#FF0000,back:#0000FF,bold")
234 self.StyleSetSpec(stc.STC_STYLE_BRACEBAD,
235 "fore:#000000,back:#FF0000,bold")
236
237 for style in self.ANSI_STYLES.values():
238 self.StyleSetSpec(style[0], "bold,fore:%s" % style[1])
239
240
241 def _configure_scintilla(self):
242 self.SetEOLMode(stc.STC_EOL_LF)
243
244 # Ctrl"+" or Ctrl "-" can be used to zoomin/zoomout the text inside
245 # the widget
246 self.CmdKeyAssign(ord('+'), stc.STC_SCMOD_CTRL, stc.STC_CMD_ZOOMIN)
247 self.CmdKeyAssign(ord('-'), stc.STC_SCMOD_CTRL, stc.STC_CMD_ZOOMOUT)
248 # Also allow Ctrl Shift "=" for poor non US keyboard users.
249 self.CmdKeyAssign(ord('='), stc.STC_SCMOD_CTRL|stc.STC_SCMOD_SHIFT,
250 stc.STC_CMD_ZOOMIN)
251
252 # Keys: we need to clear some of the keys the that don't play
253 # well with a console.
254 self.CmdKeyClear(ord('D'), stc.STC_SCMOD_CTRL)
255 self.CmdKeyClear(ord('L'), stc.STC_SCMOD_CTRL)
256 self.CmdKeyClear(ord('T'), stc.STC_SCMOD_CTRL)
257 self.CmdKeyClear(ord('A'), stc.STC_SCMOD_CTRL)
258
259 self.SetEOLMode(stc.STC_EOL_CRLF)
260 self.SetWrapMode(stc.STC_WRAP_CHAR)
261 self.SetWrapMode(stc.STC_WRAP_WORD)
262 self.SetBufferedDraw(True)
263 self.SetUseAntiAliasing(True)
264 self.SetLayoutCache(stc.STC_CACHE_PAGE)
265 self.SetUndoCollection(False)
266 self.SetUseTabs(True)
267 self.SetIndent(4)
268 self.SetTabWidth(4)
269
270 # we don't want scintilla's autocompletion to choose
271 # automaticaly out of a single choice list, as we pop it up
272 # automaticaly
273 self.AutoCompSetChooseSingle(False)
274 self.AutoCompSetMaxHeight(10)
275 # XXX: this doesn't seem to have an effect.
276 self.AutoCompSetFillUps('\n')
277
278 self.SetMargins(3, 3) #text is moved away from border with 3px
279 # Suppressing Scintilla margins
280 self.SetMarginWidth(0, 0)
281 self.SetMarginWidth(1, 0)
282 self.SetMarginWidth(2, 0)
283
284 self._apply_style()
285
286 # Xterm escape sequences
287 self.color_pat = re.compile('\x01?\x1b\[(.*?)m\x02?')
288 self.title_pat = re.compile('\x1b]0;(.*?)\x07')
289
290 #self.SetEdgeMode(stc.STC_EDGE_LINE)
291 #self.SetEdgeColumn(80)
292
293 # styles
294 p = self.style
295 self.StyleSetSpec(stc.STC_STYLE_DEFAULT, p['default'])
296 self.StyleClearAll()
297 self.StyleSetSpec(_STDOUT_STYLE, p['stdout'])
298 self.StyleSetSpec(_STDERR_STYLE, p['stderr'])
299 self.StyleSetSpec(_TRACE_STYLE, p['trace'])
300
301 self.StyleSetSpec(stc.STC_STYLE_BRACELIGHT, p['bracegood'])
302 self.StyleSetSpec(stc.STC_STYLE_BRACEBAD, p['bracebad'])
303 self.StyleSetSpec(stc.STC_P_COMMENTLINE, p['comment'])
304 self.StyleSetSpec(stc.STC_P_NUMBER, p['number'])
305 self.StyleSetSpec(stc.STC_P_STRING, p['string'])
306 self.StyleSetSpec(stc.STC_P_CHARACTER, p['char'])
307 self.StyleSetSpec(stc.STC_P_WORD, p['keyword'])
308 self.StyleSetSpec(stc.STC_P_WORD2, p['keyword'])
309 self.StyleSetSpec(stc.STC_P_TRIPLE, p['triple'])
310 self.StyleSetSpec(stc.STC_P_TRIPLEDOUBLE, p['tripledouble'])
311 self.StyleSetSpec(stc.STC_P_CLASSNAME, p['class'])
312 self.StyleSetSpec(stc.STC_P_DEFNAME, p['def'])
313 self.StyleSetSpec(stc.STC_P_OPERATOR, p['operator'])
314 self.StyleSetSpec(stc.STC_P_COMMENTBLOCK, p['comment'])
315
316 def _on_key_down(self, event, skip=True):
317 """ Key press callback used for correcting behavior for
318 console-like interfaces: the cursor is constraint to be after
319 the last prompt.
320
321 Return True if event as been catched.
322 """
323 catched = True
324 # Intercept some specific keys.
325 if event.KeyCode == ord('L') and event.ControlDown() :
326 self.scroll_to_bottom()
327 elif event.KeyCode == ord('K') and event.ControlDown() :
328 self.input_buffer = ''
329 elif event.KeyCode == ord('A') and event.ControlDown() :
330 self.GotoPos(self.GetLength())
331 self.SetSelectionStart(self.current_prompt_pos)
332 self.SetSelectionEnd(self.GetCurrentPos())
333 catched = True
334 elif event.KeyCode == ord('E') and event.ControlDown() :
335 self.GotoPos(self.GetLength())
336 catched = True
337 elif event.KeyCode == wx.WXK_PAGEUP:
338 self.ScrollPages(-1)
339 elif event.KeyCode == wx.WXK_PAGEDOWN:
340 self.ScrollPages(1)
341 elif event.KeyCode == wx.WXK_UP and event.ShiftDown():
342 self.ScrollLines(-1)
343 elif event.KeyCode == wx.WXK_DOWN and event.ShiftDown():
344 self.ScrollLines(1)
345 else:
346 catched = False
347
348 if self.AutoCompActive():
349 event.Skip()
350 else:
351 if event.KeyCode in (13, wx.WXK_NUMPAD_ENTER) and \
352 event.Modifiers in (wx.MOD_NONE, wx.MOD_WIN):
353 catched = True
354 self.CallTipCancel()
355 self.write('\n', refresh=False)
356 # Under windows scintilla seems to be doing funny stuff to the
357 # line returns here, but the getter for input_buffer filters
358 # this out.
359 if sys.platform == 'win32':
360 self.input_buffer = self.input_buffer
361 self._on_enter()
362
363 elif event.KeyCode == wx.WXK_HOME:
364 if event.Modifiers in (wx.MOD_NONE, wx.MOD_WIN):
365 self.GotoPos(self.current_prompt_pos)
366 catched = True
367
368 elif event.Modifiers == wx.MOD_SHIFT:
369 # FIXME: This behavior is not ideal: if the selection
370 # is already started, it will jump.
371 self.SetSelectionStart(self.current_prompt_pos)
372 self.SetSelectionEnd(self.GetCurrentPos())
373 catched = True
374
375 elif event.KeyCode == wx.WXK_UP:
376 if self.GetCurrentLine() > self.current_prompt_line:
377 if self.GetCurrentLine() == self.current_prompt_line + 1 \
378 and self.GetColumn(self.GetCurrentPos()) < \
379 self.GetColumn(self.current_prompt_pos):
380 self.GotoPos(self.current_prompt_pos)
381 else:
382 event.Skip()
383 catched = True
384
385 elif event.KeyCode in (wx.WXK_LEFT, wx.WXK_BACK):
386 if self.GetCurrentPos() > self.current_prompt_pos:
387 event.Skip()
388 catched = True
389
390 if skip and not catched:
391 # Put the cursor back in the edit region
392 if self.GetCurrentPos() < self.current_prompt_pos:
393 self.GotoPos(self.current_prompt_pos)
394 else:
395 event.Skip()
396
397 return catched
398
399
400 def _on_key_up(self, event, skip=True):
401 """ If cursor is outside the editing region, put it back.
402 """
403 event.Skip()
404 if self.GetCurrentPos() < self.current_prompt_pos:
405 self.GotoPos(self.current_prompt_pos)
406
407
408
409 if __name__ == '__main__':
410 # Some simple code to test the console widget.
411 class MainWindow(wx.Frame):
412 def __init__(self, parent, id, title):
413 wx.Frame.__init__(self, parent, id, title, size=(300,250))
414 self._sizer = wx.BoxSizer(wx.VERTICAL)
415 self.console_widget = ConsoleWidget(self)
416 self._sizer.Add(self.console_widget, 1, wx.EXPAND)
417 self.SetSizer(self._sizer)
418 self.SetAutoLayout(1)
419 self.Show(True)
420
421 app = wx.PySimpleApp()
422 w = MainWindow(None, wx.ID_ANY, 'ConsoleWidget')
423 w.SetSize((780, 460))
424 w.Show()
425
426 app.MainLoop()
427
428
@@ -0,0 +1,110 b''
1 """
2 Entry point for a simple application giving a graphical frontend to
3 ipython.
4 """
5
6 try:
7 import wx
8 except ImportError, e:
9 e.message = """%s
10 ________________________________________________________________________________
11 You need wxPython to run this application.
12 """ % e.message
13 e.args = (e.message, ) + e.args[1:]
14 raise e
15
16 from wx_frontend import WxController
17 import __builtin__
18
19
20 class IPythonXController(WxController):
21 """ Sub class of WxController that adds some application-specific
22 bindings.
23 """
24
25 debug = False
26
27 def __init__(self, *args, **kwargs):
28 WxController.__init__(self, *args, **kwargs)
29 self.ipython0.ask_exit = self.do_exit
30 # Scroll to top
31 maxrange = self.GetScrollRange(wx.VERTICAL)
32 self.ScrollLines(-maxrange)
33
34
35 def _on_key_down(self, event, skip=True):
36 # Intercept Ctrl-D to quit
37 if event.KeyCode == ord('D') and event.ControlDown() and \
38 self.input_buffer == '' and \
39 self._input_state == 'readline':
40 wx.CallAfter(self.ask_exit)
41 else:
42 WxController._on_key_down(self, event, skip=skip)
43
44
45 def ask_exit(self):
46 """ Ask the user whether to exit.
47 """
48 self._input_state = 'subprocess'
49 self.write('\n', refresh=False)
50 self.capture_output()
51 self.ipython0.shell.exit()
52 self.release_output()
53 if not self.ipython0.exit_now:
54 wx.CallAfter(self.new_prompt,
55 self.input_prompt_template.substitute(
56 number=self.last_result['number'] + 1))
57 else:
58 wx.CallAfter(wx.GetApp().Exit)
59 self.write('Exiting ...', refresh=False)
60
61
62 def do_exit(self):
63 """ Exits the interpreter, kills the windows.
64 """
65 WxController.do_exit(self)
66 self.release_output()
67 wx.CallAfter(wx.Exit)
68
69
70
71 class IPythonX(wx.Frame):
72 """ Main frame of the IPythonX app.
73 """
74
75 def __init__(self, parent, id, title, debug=False):
76 wx.Frame.__init__(self, parent, id, title, size=(300,250))
77 self._sizer = wx.BoxSizer(wx.VERTICAL)
78 self.shell = IPythonXController(self, debug=debug)
79 self._sizer.Add(self.shell, 1, wx.EXPAND)
80 self.SetSizer(self._sizer)
81 self.SetAutoLayout(1)
82 self.Show(True)
83
84
85 def main():
86 from optparse import OptionParser
87 usage = """usage: %prog [options]
88
89 Simple graphical frontend to IPython, using WxWidgets."""
90 parser = OptionParser(usage=usage)
91 parser.add_option("-d", "--debug",
92 action="store_true", dest="debug", default=False,
93 help="Enable debug message for the wx frontend.")
94
95 options, args = parser.parse_args()
96
97 # Clear the options, to avoid having the ipython0 instance complain
98 import sys
99 sys.argv = sys.argv[:1]
100
101 app = wx.PySimpleApp()
102 frame = IPythonX(None, wx.ID_ANY, 'IPythonX', debug=options.debug)
103 frame.shell.SetFocus()
104 frame.shell.app = app
105 frame.SetSize((680, 460))
106
107 app.MainLoop()
108
109 if __name__ == '__main__':
110 main()
This diff has been collapsed as it changes many lines, (510 lines changed) Show them Hide them
@@ -0,0 +1,510 b''
1 # encoding: utf-8 -*- test-case-name:
2 # FIXME: Need to add tests.
3 # ipython1.frontend.wx.tests.test_wx_frontend -*-
4
5 """Classes to provide a Wx frontend to the
6 IPython.kernel.core.interpreter.
7
8 This class inherits from ConsoleWidget, that provides a console-like
9 widget to provide a text-rendering widget suitable for a terminal.
10 """
11
12 __docformat__ = "restructuredtext en"
13
14 #-------------------------------------------------------------------------------
15 # Copyright (C) 2008 The IPython Development Team
16 #
17 # Distributed under the terms of the BSD License. The full license is in
18 # the file COPYING, distributed as part of this software.
19 #-------------------------------------------------------------------------------
20
21 #-------------------------------------------------------------------------------
22 # Imports
23 #-------------------------------------------------------------------------------
24
25 # Major library imports
26 import re
27 import __builtin__
28 from time import sleep
29 import sys
30 from threading import Lock
31 import string
32
33 import wx
34 from wx import stc
35
36 # Ipython-specific imports.
37 from IPython.frontend._process import PipedProcess
38 from console_widget import ConsoleWidget
39 from IPython.frontend.prefilterfrontend import PrefilterFrontEnd
40
41 #-------------------------------------------------------------------------------
42 # Constants
43 #-------------------------------------------------------------------------------
44
45 _COMPLETE_BUFFER_BG = '#FAFAF1' # Nice green
46 _INPUT_BUFFER_BG = '#FDFFD3' # Nice yellow
47 _ERROR_BG = '#FFF1F1' # Nice red
48
49 _COMPLETE_BUFFER_MARKER = 31
50 _ERROR_MARKER = 30
51 _INPUT_MARKER = 29
52
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'
55
56 prompt_out = \
57 '\x01\x1b[0;31m\x02Out[\x01\x1b[1;31m\x02$number\x01\x1b[0;31m\x02]: \x01\x1b[0m\x02'
58
59 #-------------------------------------------------------------------------------
60 # Classes to implement the Wx frontend
61 #-------------------------------------------------------------------------------
62 class WxController(ConsoleWidget, PrefilterFrontEnd):
63 """Classes to provide a Wx frontend to the
64 IPython.kernel.core.interpreter.
65
66 This class inherits from ConsoleWidget, that provides a console-like
67 widget to provide a text-rendering widget suitable for a terminal.
68 """
69
70 output_prompt_template = string.Template(prompt_out)
71
72 input_prompt_template = string.Template(prompt_in1)
73
74 # Print debug info on what is happening to the console.
75 debug = False
76
77 # The title of the terminal, as captured through the ANSI escape
78 # sequences.
79 def _set_title(self, title):
80 return self.Parent.SetTitle(title)
81
82 def _get_title(self):
83 return self.Parent.GetTitle()
84
85 title = property(_get_title, _set_title)
86
87
88 # The buffer being edited.
89 # We are duplicating the definition here because of multiple
90 # inheritence
91 def _set_input_buffer(self, string):
92 ConsoleWidget._set_input_buffer(self, string)
93 self._colorize_input_buffer()
94
95 def _get_input_buffer(self):
96 """ Returns the text in current edit buffer.
97 """
98 return ConsoleWidget._get_input_buffer(self)
99
100 input_buffer = property(_get_input_buffer, _set_input_buffer)
101
102
103 #--------------------------------------------------------------------------
104 # Private Attributes
105 #--------------------------------------------------------------------------
106
107 # A flag governing the behavior of the input. Can be:
108 #
109 # 'readline' for readline-like behavior with a prompt
110 # and an edit buffer.
111 # 'raw_input' similar to readline, but triggered by a raw-input
112 # call. Can be used by subclasses to act differently.
113 # 'subprocess' for sending the raw input directly to a
114 # subprocess.
115 # 'buffering' for buffering of the input, that will be used
116 # when the input state switches back to another state.
117 _input_state = 'readline'
118
119 # Attribute to store reference to the pipes of a subprocess, if we
120 # are running any.
121 _running_process = False
122
123 # A queue for writing fast streams to the screen without flooding the
124 # event loop
125 _out_buffer = []
126
127 # A lock to lock the _out_buffer to make sure we don't empty it
128 # while it is being swapped
129 _out_buffer_lock = Lock()
130
131 _markers = dict()
132
133 #--------------------------------------------------------------------------
134 # Public API
135 #--------------------------------------------------------------------------
136
137 def __init__(self, parent, id=wx.ID_ANY, pos=wx.DefaultPosition,
138 size=wx.DefaultSize, style=wx.CLIP_CHILDREN,
139 *args, **kwds):
140 """ Create Shell instance.
141 """
142 ConsoleWidget.__init__(self, parent, id, pos, size, style)
143 PrefilterFrontEnd.__init__(self, **kwds)
144
145 # Marker for complete buffer.
146 self.MarkerDefine(_COMPLETE_BUFFER_MARKER, stc.STC_MARK_BACKGROUND,
147 background=_COMPLETE_BUFFER_BG)
148 # Marker for current input buffer.
149 self.MarkerDefine(_INPUT_MARKER, stc.STC_MARK_BACKGROUND,
150 background=_INPUT_BUFFER_BG)
151 # Marker for tracebacks.
152 self.MarkerDefine(_ERROR_MARKER, stc.STC_MARK_BACKGROUND,
153 background=_ERROR_BG)
154
155 # A time for flushing the write buffer
156 BUFFER_FLUSH_TIMER_ID = 100
157 self._buffer_flush_timer = wx.Timer(self, BUFFER_FLUSH_TIMER_ID)
158 wx.EVT_TIMER(self, BUFFER_FLUSH_TIMER_ID, self._buffer_flush)
159
160 if 'debug' in kwds:
161 self.debug = kwds['debug']
162 kwds.pop('debug')
163
164 # Inject self in namespace, for debug
165 if self.debug:
166 self.shell.user_ns['self'] = self
167
168
169 def raw_input(self, prompt):
170 """ A replacement from python's raw_input.
171 """
172 self.new_prompt(prompt)
173 self._input_state = 'raw_input'
174 if hasattr(self, '_cursor'):
175 del self._cursor
176 self.SetCursor(wx.StockCursor(wx.CURSOR_CROSS))
177 self.waiting = True
178 self.__old_on_enter = self._on_enter
179 def my_on_enter():
180 self.waiting = False
181 self._on_enter = my_on_enter
182 # XXX: Busy waiting, ugly.
183 while self.waiting:
184 wx.Yield()
185 sleep(0.1)
186 self._on_enter = self.__old_on_enter
187 self._input_state = 'buffering'
188 self._cursor = wx.BusyCursor()
189 return self.input_buffer.rstrip('\n')
190
191
192 def system_call(self, command_string):
193 self._input_state = 'subprocess'
194 self._running_process = PipedProcess(command_string,
195 out_callback=self.buffered_write,
196 end_callback = self._end_system_call)
197 self._running_process.start()
198 # XXX: another one of these polling loops to have a blocking
199 # call
200 wx.Yield()
201 while self._running_process:
202 wx.Yield()
203 sleep(0.1)
204 # Be sure to flush the buffer.
205 self._buffer_flush(event=None)
206
207
208 def do_calltip(self):
209 """ Analyse current and displays useful calltip for it.
210 """
211 if self.debug:
212 print >>sys.__stdout__, "do_calltip"
213 separators = re.compile('[\s\{\}\[\]\(\)\= ,:]')
214 symbol = self.input_buffer
215 symbol_string = separators.split(symbol)[-1]
216 base_symbol_string = symbol_string.split('.')[0]
217 if base_symbol_string in self.shell.user_ns:
218 symbol = self.shell.user_ns[base_symbol_string]
219 elif base_symbol_string in self.shell.user_global_ns:
220 symbol = self.shell.user_global_ns[base_symbol_string]
221 elif base_symbol_string in __builtin__.__dict__:
222 symbol = __builtin__.__dict__[base_symbol_string]
223 else:
224 return False
225 try:
226 for name in symbol_string.split('.')[1:] + ['__doc__']:
227 symbol = getattr(symbol, name)
228 self.AutoCompCancel()
229 wx.Yield()
230 self.CallTipShow(self.GetCurrentPos(), symbol)
231 except:
232 # The retrieve symbol couldn't be converted to a string
233 pass
234
235
236 def _popup_completion(self, create=False):
237 """ Updates the popup completion menu if it exists. If create is
238 true, open the menu.
239 """
240 if self.debug:
241 print >>sys.__stdout__, "_popup_completion",
242 line = self.input_buffer
243 if (self.AutoCompActive() and not line[-1] == '.') \
244 or create==True:
245 suggestion, completions = self.complete(line)
246 offset=0
247 if completions:
248 complete_sep = re.compile('[\s\{\}\[\]\(\)\= ,:]')
249 residual = complete_sep.split(line)[-1]
250 offset = len(residual)
251 self.pop_completion(completions, offset=offset)
252 if self.debug:
253 print >>sys.__stdout__, completions
254
255
256 def buffered_write(self, text):
257 """ A write method for streams, that caches the stream in order
258 to avoid flooding the event loop.
259
260 This can be called outside of the main loop, in separate
261 threads.
262 """
263 self._out_buffer_lock.acquire()
264 self._out_buffer.append(text)
265 self._out_buffer_lock.release()
266 if not self._buffer_flush_timer.IsRunning():
267 wx.CallAfter(self._buffer_flush_timer.Start,
268 milliseconds=100, oneShot=True)
269
270
271 #--------------------------------------------------------------------------
272 # LineFrontEnd interface
273 #--------------------------------------------------------------------------
274
275 def execute(self, python_string, raw_string=None):
276 self._input_state = 'buffering'
277 self.CallTipCancel()
278 self._cursor = wx.BusyCursor()
279 if raw_string is None:
280 raw_string = python_string
281 end_line = self.current_prompt_line \
282 + max(1, len(raw_string.split('\n'))-1)
283 for i in range(self.current_prompt_line, end_line):
284 if i in self._markers:
285 self.MarkerDeleteHandle(self._markers[i])
286 self._markers[i] = self.MarkerAdd(i, _COMPLETE_BUFFER_MARKER)
287 # Update the display:
288 wx.Yield()
289 self.GotoPos(self.GetLength())
290 PrefilterFrontEnd.execute(self, python_string, raw_string=raw_string)
291
292 def save_output_hooks(self):
293 self.__old_raw_input = __builtin__.raw_input
294 PrefilterFrontEnd.save_output_hooks(self)
295
296 def capture_output(self):
297 __builtin__.raw_input = self.raw_input
298 self.SetLexer(stc.STC_LEX_NULL)
299 PrefilterFrontEnd.capture_output(self)
300
301
302 def release_output(self):
303 __builtin__.raw_input = self.__old_raw_input
304 PrefilterFrontEnd.release_output(self)
305 self.SetLexer(stc.STC_LEX_PYTHON)
306
307
308 def after_execute(self):
309 PrefilterFrontEnd.after_execute(self)
310 # Clear the wait cursor
311 if hasattr(self, '_cursor'):
312 del self._cursor
313 self.SetCursor(wx.StockCursor(wx.CURSOR_CHAR))
314
315
316 def show_traceback(self):
317 start_line = self.GetCurrentLine()
318 PrefilterFrontEnd.show_traceback(self)
319 wx.Yield()
320 for i in range(start_line, self.GetCurrentLine()):
321 self._markers[i] = self.MarkerAdd(i, _ERROR_MARKER)
322
323
324 #--------------------------------------------------------------------------
325 # ConsoleWidget interface
326 #--------------------------------------------------------------------------
327
328 def new_prompt(self, prompt):
329 """ Display a new prompt, and start a new input buffer.
330 """
331 self._input_state = 'readline'
332 ConsoleWidget.new_prompt(self, prompt)
333 i = self.current_prompt_line
334 self._markers[i] = self.MarkerAdd(i, _INPUT_MARKER)
335
336
337 def write(self, *args, **kwargs):
338 # Avoid multiple inheritence, be explicit about which
339 # parent method class gets called
340 ConsoleWidget.write(self, *args, **kwargs)
341
342
343 def _on_key_down(self, event, skip=True):
344 """ Capture the character events, let the parent
345 widget handle them, and put our logic afterward.
346 """
347 # FIXME: This method needs to be broken down in smaller ones.
348 current_line_number = self.GetCurrentLine()
349 if event.KeyCode in (ord('c'), ord('C')) and event.ControlDown():
350 # Capture Control-C
351 if self._input_state == 'subprocess':
352 if self.debug:
353 print >>sys.__stderr__, 'Killing running process'
354 self._running_process.process.kill()
355 elif self._input_state == 'buffering':
356 if self.debug:
357 print >>sys.__stderr__, 'Raising KeyboardInterrupt'
358 raise KeyboardInterrupt
359 # XXX: We need to make really sure we
360 # get back to a prompt.
361 elif self._input_state == 'subprocess' and (
362 ( event.KeyCode<256 and
363 not event.ControlDown() )
364 or
365 ( event.KeyCode in (ord('d'), ord('D')) and
366 event.ControlDown())):
367 # We are running a process, we redirect keys.
368 ConsoleWidget._on_key_down(self, event, skip=skip)
369 char = chr(event.KeyCode)
370 # Deal with some inconsistency in wx keycodes:
371 if char == '\r':
372 char = '\n'
373 elif not event.ShiftDown():
374 char = char.lower()
375 if event.ControlDown() and event.KeyCode in (ord('d'), ord('D')):
376 char = '\04'
377 self._running_process.process.stdin.write(char)
378 self._running_process.process.stdin.flush()
379 elif event.KeyCode in (ord('('), 57):
380 # Calltips
381 event.Skip()
382 self.do_calltip()
383 elif self.AutoCompActive() and not event.KeyCode == ord('\t'):
384 event.Skip()
385 if event.KeyCode in (wx.WXK_BACK, wx.WXK_DELETE):
386 wx.CallAfter(self._popup_completion, create=True)
387 elif not event.KeyCode in (wx.WXK_UP, wx.WXK_DOWN, wx.WXK_LEFT,
388 wx.WXK_RIGHT, wx.WXK_ESCAPE):
389 wx.CallAfter(self._popup_completion)
390 else:
391 # Up history
392 if event.KeyCode == wx.WXK_UP and (
393 ( current_line_number == self.current_prompt_line and
394 event.Modifiers in (wx.MOD_NONE, wx.MOD_WIN) )
395 or event.ControlDown() ):
396 new_buffer = self.get_history_previous(
397 self.input_buffer)
398 if new_buffer is not None:
399 self.input_buffer = new_buffer
400 if self.GetCurrentLine() > self.current_prompt_line:
401 # Go to first line, for seemless history up.
402 self.GotoPos(self.current_prompt_pos)
403 # Down history
404 elif event.KeyCode == wx.WXK_DOWN and (
405 ( current_line_number == self.LineCount -1 and
406 event.Modifiers in (wx.MOD_NONE, wx.MOD_WIN) )
407 or event.ControlDown() ):
408 new_buffer = self.get_history_next()
409 if new_buffer is not None:
410 self.input_buffer = new_buffer
411 # Tab-completion
412 elif event.KeyCode == ord('\t'):
413 last_line = self.input_buffer.split('\n')[-1]
414 if not re.match(r'^\s*$', last_line):
415 self.complete_current_input()
416 if self.AutoCompActive():
417 wx.CallAfter(self._popup_completion, create=True)
418 else:
419 event.Skip()
420 else:
421 ConsoleWidget._on_key_down(self, event, skip=skip)
422
423
424 def _on_key_up(self, event, skip=True):
425 """ Called when any key is released.
426 """
427 if event.KeyCode in (59, ord('.')):
428 # Intercepting '.'
429 event.Skip()
430 self._popup_completion(create=True)
431 else:
432 ConsoleWidget._on_key_up(self, event, skip=skip)
433
434
435 def _on_enter(self):
436 """ Called on return key down, in readline input_state.
437 """
438 if self.debug:
439 print >>sys.__stdout__, repr(self.input_buffer)
440 PrefilterFrontEnd._on_enter(self)
441
442
443 #--------------------------------------------------------------------------
444 # EditWindow API
445 #--------------------------------------------------------------------------
446
447 def OnUpdateUI(self, event):
448 """ Override the OnUpdateUI of the EditWindow class, to prevent
449 syntax highlighting both for faster redraw, and for more
450 consistent look and feel.
451 """
452 if not self._input_state == 'readline':
453 ConsoleWidget.OnUpdateUI(self, event)
454
455 #--------------------------------------------------------------------------
456 # Private API
457 #--------------------------------------------------------------------------
458
459 def _end_system_call(self):
460 """ Called at the end of a system call.
461 """
462 self._input_state = 'buffering'
463 self._running_process = False
464
465
466 def _buffer_flush(self, event):
467 """ Called by the timer to flush the write buffer.
468
469 This is always called in the mainloop, by the wx timer.
470 """
471 self._out_buffer_lock.acquire()
472 _out_buffer = self._out_buffer
473 self._out_buffer = []
474 self._out_buffer_lock.release()
475 self.write(''.join(_out_buffer), refresh=False)
476
477
478 def _colorize_input_buffer(self):
479 """ Keep the input buffer lines at a bright color.
480 """
481 if not self._input_state in ('readline', 'raw_input'):
482 return
483 end_line = self.GetCurrentLine()
484 if not sys.platform == 'win32':
485 end_line += 1
486 for i in range(self.current_prompt_line, end_line):
487 if i in self._markers:
488 self.MarkerDeleteHandle(self._markers[i])
489 self._markers[i] = self.MarkerAdd(i, _INPUT_MARKER)
490
491
492 if __name__ == '__main__':
493 class MainWindow(wx.Frame):
494 def __init__(self, parent, id, title):
495 wx.Frame.__init__(self, parent, id, title, size=(300,250))
496 self._sizer = wx.BoxSizer(wx.VERTICAL)
497 self.shell = WxController(self)
498 self._sizer.Add(self.shell, 1, wx.EXPAND)
499 self.SetSizer(self._sizer)
500 self.SetAutoLayout(1)
501 self.Show(True)
502
503 app = wx.PySimpleApp()
504 frame = MainWindow(None, wx.ID_ANY, 'Ipython')
505 frame.shell.SetFocus()
506 frame.SetSize((680, 460))
507 self = frame.shell
508
509 app.MainLoop()
510
@@ -0,0 +1,34 b''
1 # encoding: utf-8
2 # -*- test-case-name: IPython.frontend.tests.test_frontendbase -*-
3 """
4 zope.interface mock. If zope is installed, this module provides a zope
5 interface classes, if not it provides mocks for them.
6
7 Classes provided:
8 Interface, Attribute, implements, classProvides
9 """
10 __docformat__ = "restructuredtext en"
11
12 #-------------------------------------------------------------------------------
13 # Copyright (C) 2008 The IPython Development Team
14 #
15 # Distributed under the terms of the BSD License. The full license is in
16 # the file COPYING, distributed as part of this software.
17 #-------------------------------------------------------------------------------
18
19 #-------------------------------------------------------------------------------
20 # Imports
21 #-------------------------------------------------------------------------------
22 import string
23 import uuid
24 import _ast
25
26 try:
27 from zope.interface import Interface, Attribute, implements, classProvides
28 except ImportError:
29 #zope.interface is not available
30 Interface = object
31 def Attribute(name, doc): pass
32 def implements(interface): pass
33 def classProvides(interface): pass
34
@@ -0,0 +1,81 b''
1 # encoding: utf-8
2
3 """
4 Stdout/stderr redirector, at the OS level, using file descriptors.
5
6 This also works under windows.
7 """
8
9 __docformat__ = "restructuredtext en"
10
11 #-------------------------------------------------------------------------------
12 # Copyright (C) 2008 The IPython Development Team
13 #
14 # Distributed under the terms of the BSD License. The full license is in
15 # the file COPYING, distributed as part of this software.
16 #-------------------------------------------------------------------------------
17
18
19 import os
20 import sys
21
22 STDOUT = 1
23 STDERR = 2
24
25 class FDRedirector(object):
26 """ Class to redirect output (stdout or stderr) at the OS level using
27 file descriptors.
28 """
29
30 def __init__(self, fd=STDOUT):
31 """ fd is the file descriptor of the outpout you want to capture.
32 It can be STDOUT or STERR.
33 """
34 self.fd = fd
35 self.started = False
36 self.piper = None
37 self.pipew = None
38
39 def start(self):
40 """ Setup the redirection.
41 """
42 if not self.started:
43 self.oldhandle = os.dup(self.fd)
44 self.piper, self.pipew = os.pipe()
45 os.dup2(self.pipew, self.fd)
46 os.close(self.pipew)
47
48 self.started = True
49
50 def flush(self):
51 """ Flush the captured output, similar to the flush method of any
52 stream.
53 """
54 if self.fd == STDOUT:
55 sys.stdout.flush()
56 elif self.fd == STDERR:
57 sys.stderr.flush()
58
59 def stop(self):
60 """ Unset the redirection and return the captured output.
61 """
62 if self.started:
63 self.flush()
64 os.dup2(self.oldhandle, self.fd)
65 os.close(self.oldhandle)
66 f = os.fdopen(self.piper, 'r')
67 output = f.read()
68 f.close()
69
70 self.started = False
71 return output
72 else:
73 return ''
74
75 def getvalue(self):
76 """ Return the output captured since the last getvalue, or the
77 start of the redirection.
78 """
79 output = self.stop()
80 self.start()
81 return output
@@ -0,0 +1,66 b''
1 # encoding: utf-8
2
3 """ File like object that redirects its write calls to a given callback."""
4
5 __docformat__ = "restructuredtext en"
6
7 #-------------------------------------------------------------------------------
8 # Copyright (C) 2008 The IPython Development Team
9 #
10 # Distributed under the terms of the BSD License. The full license is in
11 # the file COPYING, distributed as part of this software.
12 #-------------------------------------------------------------------------------
13
14 import sys
15
16 class FileLike(object):
17 """ FileLike object that redirects all write to a callback.
18
19 Only the write-related methods are implemented, as well as those
20 required to read a StringIO.
21 """
22 closed = False
23
24 def __init__(self, write_callback):
25 self.write = write_callback
26
27 def flush(self):
28 """ This method is there for compatibility with other file-like
29 objects.
30 """
31 pass
32
33 def close(self):
34 """ This method is there for compatibility with other file-like
35 objects.
36 """
37 pass
38
39 def writelines(self, lines):
40 map(self.write, lines)
41
42 def isatty(self):
43 """ This method is there for compatibility with other file-like
44 objects.
45 """
46 return False
47
48 def getvalue(self):
49 """ This method is there for compatibility with other file-like
50 objects.
51 """
52 return ''
53
54 def reset(self):
55 """ This method is there for compatibility with other file-like
56 objects.
57 """
58 pass
59
60 def truncate(self):
61 """ This method is there for compatibility with other file-like
62 objects.
63 """
64 pass
65
66
@@ -0,0 +1,97 b''
1 # encoding: utf-8
2
3 """
4 Trap stdout/stderr, including at the OS level. Calls a callback with
5 the output each time Python tries to write to the stdout or stderr.
6 """
7
8 __docformat__ = "restructuredtext en"
9
10 #-------------------------------------------------------------------------------
11 # Copyright (C) 2008 The IPython Development Team
12 #
13 # Distributed under the terms of the BSD License. The full license is in
14 # the file COPYING, distributed as part of this software.
15 #-------------------------------------------------------------------------------
16
17 #-------------------------------------------------------------------------------
18 # Imports
19 #-------------------------------------------------------------------------------
20
21 from fd_redirector import FDRedirector, STDOUT, STDERR
22
23 from IPython.kernel.core.file_like import FileLike
24 from IPython.kernel.core.output_trap import OutputTrap
25
26 class RedirectorOutputTrap(OutputTrap):
27 """ Object which can trap text sent to stdout and stderr.
28 """
29
30 #------------------------------------------------------------------------
31 # OutputTrap interface.
32 #------------------------------------------------------------------------
33 def __init__(self, out_callback, err_callback):
34 """
35 out_callback : callable called when there is output in the stdout
36 err_callback : callable called when there is output in the stderr
37 """
38 # Callback invoked on write to stdout and stderr
39 self.out_callback = out_callback
40 self.err_callback = err_callback
41
42 # File descriptor redirectors, to capture non-Python
43 # output.
44 self.out_redirector = FDRedirector(STDOUT)
45 self.err_redirector = FDRedirector(STDERR)
46
47 # Call the base class with file like objects that will trigger
48 # our callbacks
49 OutputTrap.__init__(self, out=FileLike(self.on_out_write),
50 err=FileLike(self.on_err_write), )
51
52
53 def set(self):
54 """ Set the hooks: set the redirectors and call the base class.
55 """
56 self.out_redirector.start()
57 self.err_redirector.start()
58 OutputTrap.set(self)
59
60
61 def unset(self):
62 """ Remove the hooks: call the base class and stop the
63 redirectors.
64 """
65 OutputTrap.unset(self)
66 # Flush the redirectors before stopping them
67 self.on_err_write('')
68 self.err_redirector.stop()
69 self.on_out_write('')
70 self.out_redirector.stop()
71
72
73 #------------------------------------------------------------------------
74 # Callbacks for synchronous output
75 #------------------------------------------------------------------------
76 def on_out_write(self, string):
77 """ Callback called when there is some Python output on stdout.
78 """
79 try:
80 self.out_callback(self.out_redirector.getvalue() + string)
81 except:
82 # If tracebacks are happening and we can't see them, it is
83 # quasy impossible to debug
84 self.unset()
85 raise
86
87 def on_err_write(self, string):
88 """ Callback called when there is some Python output on stderr.
89 """
90 try:
91 self.err_callback(self.err_redirector.getvalue() + string)
92 except:
93 # If tracebacks are happening and we can't see them, it is
94 # quasy impossible to debug
95 self.unset()
96 raise
97
@@ -0,0 +1,53 b''
1 # encoding: utf-8
2
3 """Object to manage sys.excepthook().
4
5 Synchronous version: prints errors when called.
6 """
7
8 __docformat__ = "restructuredtext en"
9
10 #-------------------------------------------------------------------------------
11 # Copyright (C) 2008 The IPython Development Team
12 #
13 # Distributed under the terms of the BSD License. The full license is in
14 # the file COPYING, distributed as part of this software.
15 #-------------------------------------------------------------------------------
16
17 #-------------------------------------------------------------------------------
18 # Imports
19 #-------------------------------------------------------------------------------
20 from traceback_trap import TracebackTrap
21 from IPython.ultraTB import ColorTB
22
23 class SyncTracebackTrap(TracebackTrap):
24 """ TracebackTrap that displays immediatly the traceback in addition
25 to capturing it. Useful in frontends, as without this traceback trap,
26 some tracebacks never get displayed.
27 """
28
29 def __init__(self, sync_formatter=None, formatters=None,
30 raiseException=True):
31 """
32 sync_formatter: Callable to display the traceback.
33 formatters: A list of formatters to apply.
34 """
35 TracebackTrap.__init__(self, formatters=formatters)
36 if sync_formatter is None:
37 sync_formatter = ColorTB(color_scheme='LightBG')
38 self.sync_formatter = sync_formatter
39 self.raiseException = raiseException
40
41
42 def hook(self, *args):
43 """ This method actually implements the hook.
44 """
45 self.args = args
46 if not self.raiseException:
47 print self.sync_formatter(*self.args)
48 else:
49 raise
50
51
52
53
@@ -0,0 +1,61 b''
1 # encoding: utf-8
2 """
3 Test the output capture at the OS level, using file descriptors.
4 """
5
6 __docformat__ = "restructuredtext en"
7
8 #-------------------------------------------------------------------------------
9 # Copyright (C) 2008 The IPython Development Team
10 #
11 # Distributed under the terms of the BSD License. The full license is
12 # in the file COPYING, distributed as part of this software.
13 #-------------------------------------------------------------------------------
14
15
16 import os
17 from cStringIO import StringIO
18
19
20 def test_redirector():
21 """ Checks that the redirector can be used to do synchronous capture.
22 """
23 from IPython.kernel.core.fd_redirector import FDRedirector
24 r = FDRedirector()
25 out = StringIO()
26 try:
27 r.start()
28 for i in range(10):
29 os.system('echo %ic' % i)
30 print >>out, r.getvalue(),
31 print >>out, i
32 except:
33 r.stop()
34 raise
35 r.stop()
36 assert out.getvalue() == "".join("%ic\n%i\n" %(i, i) for i in range(10))
37
38
39 def test_redirector_output_trap():
40 """ This test check not only that the redirector_output_trap does
41 trap the output, but also that it does it in a gready way, that
42 is by calling the callback ASAP.
43 """
44 from IPython.kernel.core.redirector_output_trap import RedirectorOutputTrap
45 out = StringIO()
46 trap = RedirectorOutputTrap(out.write, out.write)
47 try:
48 trap.set()
49 for i in range(10):
50 os.system('echo %ic' % i)
51 print "%ip" % i
52 print >>out, i
53 except:
54 trap.unset()
55 raise
56 trap.unset()
57 assert out.getvalue() == "".join("%ic\n%ip\n%i\n" %(i, i, i)
58 for i in range(10))
59
60
61
@@ -0,0 +1,11 b''
1 #!/usr/bin/env python
2 # -*- coding: utf-8 -*-
3 """IPythonX -- An enhanced Interactive Python
4
5 This script starts the Wx graphical frontend. This is experimental so
6 far.
7 """
8
9 from IPython.frontend.wx import ipythonx
10
11 ipythonx.main()
@@ -49,7 +49,7 b' ip.expose_magic("rehash", magic_rehash)'
49 def magic_Quit(self, parameter_s=''):
49 def magic_Quit(self, parameter_s=''):
50 """Exit IPython without confirmation (like %Exit)."""
50 """Exit IPython without confirmation (like %Exit)."""
51
51
52 self.shell.exit_now = True
52 self.shell.ask_exit()
53
53
54 ip.expose_magic("Quit", magic_Quit)
54 ip.expose_magic("Quit", magic_Quit)
55
55
@@ -2483,7 +2483,7 b' Defaulting color scheme to \'NoColor\'"""'
2483 def magic_Exit(self, parameter_s=''):
2483 def magic_Exit(self, parameter_s=''):
2484 """Exit IPython without confirmation."""
2484 """Exit IPython without confirmation."""
2485
2485
2486 self.shell.exit_now = True
2486 self.shell.ask_exit()
2487
2487
2488 #......................................................................
2488 #......................................................................
2489 # Functions to implement unix shell-type things
2489 # Functions to implement unix shell-type things
@@ -40,7 +40,7 b' from pprint import saferepr'
40
40
41 import IPython
41 import IPython
42 from IPython.kernel.engineservice import ThreadedEngineService
42 from IPython.kernel.engineservice import ThreadedEngineService
43 from IPython.frontend.frontendbase import AsyncFrontEndBase
43 from IPython.frontend.asyncfrontendbase import AsyncFrontEndBase
44
44
45 from twisted.internet.threads import blockingCallFromThread
45 from twisted.internet.threads import blockingCallFromThread
46 from twisted.python.failure import Failure
46 from twisted.python.failure import Failure
@@ -24,20 +24,12 b' import string'
24 import uuid
24 import uuid
25 import _ast
25 import _ast
26
26
27 try:
27 from zopeinterface import Interface, Attribute, implements, classProvides
28 from zope.interface import Interface, Attribute, implements, classProvides
29 except ImportError:
30 #zope.interface is not available
31 Interface = object
32 def Attribute(name, doc): pass
33 def implements(interface): pass
34 def classProvides(interface): pass
35
28
36 from IPython.kernel.core.history import FrontEndHistory
29 from IPython.kernel.core.history import FrontEndHistory
37 from IPython.kernel.core.util import Bunch
30 from IPython.kernel.core.util import Bunch
38 from IPython.kernel.engineservice import IEngineCore
31 from IPython.kernel.engineservice import IEngineCore
39
32
40
41 ##############################################################################
33 ##############################################################################
42 # TEMPORARY!!! fake configuration, while we decide whether to use tconfig or
34 # TEMPORARY!!! fake configuration, while we decide whether to use tconfig or
43 # not
35 # not
@@ -48,6 +40,8 b" rc.prompt_in2 = r'...'"
48 rc.prompt_out = r'Out [$number]: '
40 rc.prompt_out = r'Out [$number]: '
49
41
50 ##############################################################################
42 ##############################################################################
43 # Interface definitions
44 ##############################################################################
51
45
52 class IFrontEndFactory(Interface):
46 class IFrontEndFactory(Interface):
53 """Factory interface for frontends."""
47 """Factory interface for frontends."""
@@ -61,7 +55,6 b' class IFrontEndFactory(Interface):'
61 pass
55 pass
62
56
63
57
64
65 class IFrontEnd(Interface):
58 class IFrontEnd(Interface):
66 """Interface for frontends. All methods return t.i.d.Deferred"""
59 """Interface for frontends. All methods return t.i.d.Deferred"""
67
60
@@ -74,9 +67,10 b' class IFrontEnd(Interface):'
74
67
75 def update_cell_prompt(result, blockID=None):
68 def update_cell_prompt(result, blockID=None):
76 """Subclass may override to update the input prompt for a block.
69 """Subclass may override to update the input prompt for a block.
77 Since this method will be called as a
70
78 twisted.internet.defer.Deferred's callback/errback,
71 In asynchronous frontends, this method will be called as a
79 implementations should return result when finished.
72 twisted.internet.defer.Deferred's callback/errback.
73 Implementations should thus return result when finished.
80
74
81 Result is a result dict in case of success, and a
75 Result is a result dict in case of success, and a
82 twisted.python.util.failure.Failure in case of an error
76 twisted.python.util.failure.Failure in case of an error
@@ -84,7 +78,6 b' class IFrontEnd(Interface):'
84
78
85 pass
79 pass
86
80
87
88 def render_result(result):
81 def render_result(result):
89 """Render the result of an execute call. Implementors may choose the
82 """Render the result of an execute call. Implementors may choose the
90 method of rendering.
83 method of rendering.
@@ -102,16 +95,17 b' class IFrontEnd(Interface):'
102 pass
95 pass
103
96
104 def render_error(failure):
97 def render_error(failure):
105 """Subclasses must override to render the failure. Since this method
98 """Subclasses must override to render the failure.
106 will be called as a twisted.internet.defer.Deferred's callback,
99
107 implementations should return result when finished.
100 In asynchronous frontend, since this method will be called as a
101 twisted.internet.defer.Deferred's callback. Implementations
102 should thus return result when finished.
108
103
109 blockID = failure.blockID
104 blockID = failure.blockID
110 """
105 """
111
106
112 pass
107 pass
113
108
114
115 def input_prompt(number=''):
109 def input_prompt(number=''):
116 """Returns the input prompt by subsituting into
110 """Returns the input prompt by subsituting into
117 self.input_prompt_template
111 self.input_prompt_template
@@ -142,8 +136,7 b' class IFrontEnd(Interface):'
142
136
143 pass
137 pass
144
138
145
139 def get_history_previous(current_block):
146 def get_history_previous(currentBlock):
147 """Returns the block previous in the history. Saves currentBlock if
140 """Returns the block previous in the history. Saves currentBlock if
148 the history_cursor is currently at the end of the input history"""
141 the history_cursor is currently at the end of the input history"""
149 pass
142 pass
@@ -153,6 +146,20 b' class IFrontEnd(Interface):'
153
146
154 pass
147 pass
155
148
149 def complete(self, line):
150 """Returns the list of possible completions, and the completed
151 line.
152
153 The input argument is the full line to be completed. This method
154 returns both the line completed as much as possible, and the list
155 of further possible completions (full words).
156 """
157 pass
158
159
160 ##############################################################################
161 # Base class for all the frontends.
162 ##############################################################################
156
163
157 class FrontEndBase(object):
164 class FrontEndBase(object):
158 """
165 """
@@ -167,9 +174,6 b' class FrontEndBase(object):'
167
174
168 history_cursor = 0
175 history_cursor = 0
169
176
170 current_indent_level = 0
171
172
173 input_prompt_template = string.Template(rc.prompt_in1)
177 input_prompt_template = string.Template(rc.prompt_in1)
174 output_prompt_template = string.Template(rc.prompt_out)
178 output_prompt_template = string.Template(rc.prompt_out)
175 continuation_prompt_template = string.Template(rc.prompt_in2)
179 continuation_prompt_template = string.Template(rc.prompt_in2)
@@ -295,14 +299,14 b' class FrontEndBase(object):'
295 return result
299 return result
296
300
297
301
298 def get_history_previous(self, currentBlock):
302 def get_history_previous(self, current_block):
299 """ Returns previous history string and decrement history cursor.
303 """ Returns previous history string and decrement history cursor.
300 """
304 """
301 command = self.history.get_history_item(self.history_cursor - 1)
305 command = self.history.get_history_item(self.history_cursor - 1)
302
306
303 if command is not None:
307 if command is not None:
304 if(self.history_cursor == len(self.history.input_cache)):
308 if(self.history_cursor+1 == len(self.history.input_cache)):
305 self.history.input_cache[self.history_cursor] = currentBlock
309 self.history.input_cache[self.history_cursor] = current_block
306 self.history_cursor -= 1
310 self.history_cursor -= 1
307 return command
311 return command
308
312
@@ -322,79 +326,34 b' class FrontEndBase(object):'
322
326
323 def update_cell_prompt(self, result, blockID=None):
327 def update_cell_prompt(self, result, blockID=None):
324 """Subclass may override to update the input prompt for a block.
328 """Subclass may override to update the input prompt for a block.
329
330 This method only really makes sens in asyncrhonous frontend.
325 Since this method will be called as a
331 Since this method will be called as a
326 twisted.internet.defer.Deferred's callback, implementations should
332 twisted.internet.defer.Deferred's callback, implementations should
327 return result when finished.
333 return result when finished.
328 """
334 """
329
335
330 return result
336 raise NotImplementedError
331
337
332
338
333 def render_result(self, result):
339 def render_result(self, result):
334 """Subclasses must override to render result. Since this method will
340 """Subclasses must override to render result.
335 be called as a twisted.internet.defer.Deferred's callback,
336 implementations should return result when finished.
337 """
338
341
339 return result
342 In asynchronous frontends, this method will be called as a
340
343 twisted.internet.defer.Deferred's callback. Implementations
341
344 should thus return result when finished.
342 def render_error(self, failure):
343 """Subclasses must override to render the failure. Since this method
344 will be called as a twisted.internet.defer.Deferred's callback,
345 implementations should return result when finished.
346 """
345 """
347
346
348 return failure
347 raise NotImplementedError
349
350
351
352 class AsyncFrontEndBase(FrontEndBase):
353 """
354 Overrides FrontEndBase to wrap execute in a deferred result.
355 All callbacks are made as callbacks on the deferred result.
356 """
357
358 implements(IFrontEnd)
359 classProvides(IFrontEndFactory)
360
361 def __init__(self, engine=None, history=None):
362 assert(engine==None or IEngineCore.providedBy(engine))
363 self.engine = IEngineCore(engine)
364 if history is None:
365 self.history = FrontEndHistory(input_cache=[''])
366 else:
367 self.history = history
368
348
369
349
370 def execute(self, block, blockID=None):
350 def render_error(self, failure):
371 """Execute the block and return the deferred result.
351 """Subclasses must override to render the failure.
372
373 Parameters:
374 block : {str, AST}
375 blockID : any
376 Caller may provide an ID to identify this block.
377 result['blockID'] := blockID
378
352
379 Result:
353 In asynchronous frontends, this method will be called as a
380 Deferred result of self.interpreter.execute
354 twisted.internet.defer.Deferred's callback. Implementations
355 should thus return result when finished.
381 """
356 """
382
357
383 if(not self.is_complete(block)):
358 raise NotImplementedError
384 from twisted.python.failure import Failure
385 return Failure(Exception("Block is not compilable"))
386
387 if(blockID == None):
388 blockID = uuid.uuid4() #random UUID
389
390 d = self.engine.execute(block)
391 d.addCallback(self._add_history, block=block)
392 d.addCallback(self._add_block_id_for_result, blockID)
393 d.addErrback(self._add_block_id_for_failure, blockID)
394 d.addBoth(self.update_cell_prompt, blockID=blockID)
395 d.addCallbacks(self.render_result,
396 errback=self.render_error)
397
398 return d
399
400
359
@@ -16,10 +16,11 b' __docformat__ = "restructuredtext en"'
16 #---------------------------------------------------------------------------
16 #---------------------------------------------------------------------------
17
17
18 import unittest
18 import unittest
19 from IPython.frontend import frontendbase
19 from IPython.frontend.asyncfrontendbase import AsyncFrontEndBase
20 from IPython.frontend import frontendbase
20 from IPython.kernel.engineservice import EngineService
21 from IPython.kernel.engineservice import EngineService
21
22
22 class FrontEndCallbackChecker(frontendbase.AsyncFrontEndBase):
23 class FrontEndCallbackChecker(AsyncFrontEndBase):
23 """FrontEndBase subclass for checking callbacks"""
24 """FrontEndBase subclass for checking callbacks"""
24 def __init__(self, engine=None, history=None):
25 def __init__(self, engine=None, history=None):
25 super(FrontEndCallbackChecker, self).__init__(engine=engine,
26 super(FrontEndCallbackChecker, self).__init__(engine=engine,
@@ -53,7 +54,7 b' class TestAsyncFrontendBase(unittest.TestCase):'
53
54
54 def test_implements_IFrontEnd(self):
55 def test_implements_IFrontEnd(self):
55 assert(frontendbase.IFrontEnd.implementedBy(
56 assert(frontendbase.IFrontEnd.implementedBy(
56 frontendbase.AsyncFrontEndBase))
57 AsyncFrontEndBase))
57
58
58
59
59 def test_is_complete_returns_False_for_incomplete_block(self):
60 def test_is_complete_returns_False_for_incomplete_block(self):
@@ -727,7 +727,7 b' class InteractiveShell(object,Magic):'
727 batchrun = True
727 batchrun = True
728 # without -i option, exit after running the batch file
728 # without -i option, exit after running the batch file
729 if batchrun and not self.rc.interact:
729 if batchrun and not self.rc.interact:
730 self.exit_now = True
730 self.ask_exit()
731
731
732 def add_builtins(self):
732 def add_builtins(self):
733 """Store ipython references into the builtin namespace.
733 """Store ipython references into the builtin namespace.
@@ -1592,7 +1592,7 b' want to merge them back into the new files.""" % locals()'
1592 #sys.argv = ['-c']
1592 #sys.argv = ['-c']
1593 self.push(self.prefilter(self.rc.c, False))
1593 self.push(self.prefilter(self.rc.c, False))
1594 if not self.rc.interact:
1594 if not self.rc.interact:
1595 self.exit_now = True
1595 self.ask_exit()
1596
1596
1597 def embed_mainloop(self,header='',local_ns=None,global_ns=None,stack_depth=0):
1597 def embed_mainloop(self,header='',local_ns=None,global_ns=None,stack_depth=0):
1598 """Embeds IPython into a running python program.
1598 """Embeds IPython into a running python program.
@@ -1755,7 +1755,8 b' want to merge them back into the new files.""" % locals()'
1755
1755
1756 if self.has_readline:
1756 if self.has_readline:
1757 self.readline_startup_hook(self.pre_readline)
1757 self.readline_startup_hook(self.pre_readline)
1758 # exit_now is set by a call to %Exit or %Quit
1758 # exit_now is set by a call to %Exit or %Quit, through the
1759 # ask_exit callback.
1759
1760
1760 while not self.exit_now:
1761 while not self.exit_now:
1761 self.hooks.pre_prompt_hook()
1762 self.hooks.pre_prompt_hook()
@@ -2151,7 +2152,7 b' want to merge them back into the new files.""" % locals()'
2151 except ValueError:
2152 except ValueError:
2152 warn("\n********\nYou or a %run:ed script called sys.stdin.close()"
2153 warn("\n********\nYou or a %run:ed script called sys.stdin.close()"
2153 " or sys.stdout.close()!\nExiting IPython!")
2154 " or sys.stdout.close()!\nExiting IPython!")
2154 self.exit_now = True
2155 self.ask_exit()
2155 return ""
2156 return ""
2156
2157
2157 # Try to be reasonably smart about not re-indenting pasted input more
2158 # Try to be reasonably smart about not re-indenting pasted input more
@@ -2502,16 +2503,20 b' want to merge them back into the new files.""" % locals()'
2502 """Write a string to the default error output"""
2503 """Write a string to the default error output"""
2503 Term.cerr.write(data)
2504 Term.cerr.write(data)
2504
2505
2506 def ask_exit(self):
2507 """ Call for exiting. Can be overiden and used as a callback. """
2508 self.exit_now = True
2509
2505 def exit(self):
2510 def exit(self):
2506 """Handle interactive exit.
2511 """Handle interactive exit.
2507
2512
2508 This method sets the exit_now attribute."""
2513 This method calls the ask_exit callback."""
2509
2514
2510 if self.rc.confirm_exit:
2515 if self.rc.confirm_exit:
2511 if self.ask_yes_no('Do you really want to exit ([y]/n)?','y'):
2516 if self.ask_yes_no('Do you really want to exit ([y]/n)?','y'):
2512 self.exit_now = True
2517 self.ask_exit()
2513 else:
2518 else:
2514 self.exit_now = True
2519 self.ask_exit()
2515
2520
2516 def safe_execfile(self,fname,*where,**kw):
2521 def safe_execfile(self,fname,*where,**kw):
2517 """A safe version of the builtin execfile().
2522 """A safe version of the builtin execfile().
@@ -56,7 +56,7 b' class History(object):'
56 """ Returns the history string at index, where index is the
56 """ Returns the history string at index, where index is the
57 distance from the end (positive).
57 distance from the end (positive).
58 """
58 """
59 if index>0 and index<len(self.input_cache):
59 if index>=0 and index<len(self.input_cache):
60 return self.input_cache[index]
60 return self.input_cache[index]
61
61
62
62
@@ -20,13 +20,11 b' __docformat__ = "restructuredtext en"'
20 #-------------------------------------------------------------------------------
20 #-------------------------------------------------------------------------------
21
21
22 # Standard library imports.
22 # Standard library imports.
23 from compiler.ast import Discard
24 from types import FunctionType
23 from types import FunctionType
25
24
26 import __builtin__
25 import __builtin__
27 import codeop
26 import codeop
28 import compiler
27 import compiler
29 import pprint
30 import sys
28 import sys
31 import traceback
29 import traceback
32
30
@@ -23,10 +23,16 b' class OutputTrap(object):'
23 """ Object which can trap text sent to stdout and stderr.
23 """ Object which can trap text sent to stdout and stderr.
24 """
24 """
25
25
26 def __init__(self):
26 def __init__(self, out=None, err=None):
27 # Filelike objects to store stdout/stderr text.
27 # Filelike objects to store stdout/stderr text.
28 self.out = StringIO()
28 if out is None:
29 self.err = StringIO()
29 self.out = StringIO()
30 else:
31 self.out = out
32 if err is None:
33 self.err = StringIO()
34 else:
35 self.err = err
30
36
31 # Boolean to check if the stdout/stderr hook is set.
37 # Boolean to check if the stdout/stderr hook is set.
32 self.out_set = False
38 self.out_set = False
@@ -62,21 +68,23 b' class OutputTrap(object):'
62 """ Remove the hooks.
68 """ Remove the hooks.
63 """
69 """
64
70
65 sys.stdout = self._out_save
71 if self.out_set:
72 sys.stdout = self._out_save
66 self.out_set = False
73 self.out_set = False
67
74
68 sys.stderr = self._err_save
75 if self.err_set:
76 sys.stderr = self._err_save
69 self.err_set = False
77 self.err_set = False
70
78
71 def clear(self):
79 def clear(self):
72 """ Clear out the buffers.
80 """ Clear out the buffers.
73 """
81 """
74
82
75 self.out.close()
83 self.out.reset()
76 self.out = StringIO()
84 self.out.truncate()
77
85
78 self.err.close()
86 self.err.reset()
79 self.err = StringIO()
87 self.err.truncate()
80
88
81 def add_to_message(self, message):
89 def add_to_message(self, message):
82 """ Add the text from stdout and stderr to the message from the
90 """ Add the text from stdout and stderr to the message from the
@@ -14,9 +14,8 b' __docformat__ = "restructuredtext en"'
14 #-------------------------------------------------------------------------------
14 #-------------------------------------------------------------------------------
15 # Imports
15 # Imports
16 #-------------------------------------------------------------------------------
16 #-------------------------------------------------------------------------------
17
18 import sys
17 import sys
19
18 from traceback import format_list
20
19
21 class TracebackTrap(object):
20 class TracebackTrap(object):
22 """ Object to trap and format tracebacks.
21 """ Object to trap and format tracebacks.
@@ -38,7 +37,6 b' class TracebackTrap(object):'
38 def hook(self, *args):
37 def hook(self, *args):
39 """ This method actually implements the hook.
38 """ This method actually implements the hook.
40 """
39 """
41
42 self.args = args
40 self.args = args
43
41
44 def set(self):
42 def set(self):
@@ -76,8 +74,12 b' class TracebackTrap(object):'
76
74
77 # Go through the list of formatters and let them add their formatting.
75 # Go through the list of formatters and let them add their formatting.
78 traceback = {}
76 traceback = {}
79 for formatter in self.formatters:
77 try:
80 traceback[formatter.identifier] = formatter(*self.args)
78 for formatter in self.formatters:
81
79 traceback[formatter.identifier] = formatter(*self.args)
80 except:
81 # This works always, including with string exceptions.
82 traceback['fallback'] = repr(self.args)
83
82 message['traceback'] = traceback
84 message['traceback'] = traceback
83
85
@@ -131,3 +131,5 b' def skip(func):'
131 func.__name__)
131 func.__name__)
132
132
133 return apply_wrapper(wrapper,func)
133 return apply_wrapper(wrapper,func)
134
135
@@ -2,8 +2,8 b''
2 PREFIX=~/usr/local
2 PREFIX=~/usr/local
3 PREFIX=~/tmp/local
3 PREFIX=~/tmp/local
4
4
5 NOSE0=nosetests -vs --with-doctest --doctest-tests
5 NOSE0=nosetests -vs --with-doctest --doctest-tests --detailed-errors
6 NOSE=nosetests -vvs --with-ipdoctest --doctest-tests --doctest-extension=txt
6 NOSE=nosetests -vvs --with-ipdoctest --doctest-tests --doctest-extension=txt --detailed-errors
7
7
8 SRC=ipdoctest.py setup.py ../decorators.py
8 SRC=ipdoctest.py setup.py ../decorators.py
9
9
@@ -132,7 +132,8 b" if 'setuptools' in sys.modules:"
132 'pycolor = IPython.PyColorize:main',
132 'pycolor = IPython.PyColorize:main',
133 'ipcontroller = IPython.kernel.scripts.ipcontroller:main',
133 'ipcontroller = IPython.kernel.scripts.ipcontroller:main',
134 'ipengine = IPython.kernel.scripts.ipengine:main',
134 'ipengine = IPython.kernel.scripts.ipengine:main',
135 'ipcluster = IPython.kernel.scripts.ipcluster:main'
135 'ipcluster = IPython.kernel.scripts.ipcluster:main',
136 'ipythonx = IPython.frontend.wx.ipythonx:main'
136 ]
137 ]
137 }
138 }
138 setup_args["extras_require"] = dict(
139 setup_args["extras_require"] = dict(
@@ -107,6 +107,10 b' def find_packages():'
107 add_package(packages, 'external')
107 add_package(packages, 'external')
108 add_package(packages, 'gui')
108 add_package(packages, 'gui')
109 add_package(packages, 'gui.wx')
109 add_package(packages, 'gui.wx')
110 add_package(packages, 'frontend', tests=True)
111 add_package(packages, 'frontend._process')
112 add_package(packages, 'frontend.wx')
113 add_package(packages, 'frontend.cocoa', tests=True)
110 add_package(packages, 'kernel', config=True, tests=True, scripts=True)
114 add_package(packages, 'kernel', config=True, tests=True, scripts=True)
111 add_package(packages, 'kernel.core', config=True, tests=True)
115 add_package(packages, 'kernel.core', config=True, tests=True)
112 add_package(packages, 'testing', tests=True)
116 add_package(packages, 'testing', tests=True)
@@ -181,6 +185,7 b' def find_scripts():'
181 scripts.append('IPython/kernel/scripts/ipcontroller')
185 scripts.append('IPython/kernel/scripts/ipcontroller')
182 scripts.append('IPython/kernel/scripts/ipcluster')
186 scripts.append('IPython/kernel/scripts/ipcluster')
183 scripts.append('scripts/ipython')
187 scripts.append('scripts/ipython')
188 scripts.append('scripts/ipythonx')
184 scripts.append('scripts/pycolor')
189 scripts.append('scripts/pycolor')
185 scripts.append('scripts/irunner')
190 scripts.append('scripts/irunner')
186
191
@@ -229,4 +234,4 b' def check_for_dependencies():'
229 check_for_sphinx()
234 check_for_sphinx()
230 check_for_pygments()
235 check_for_pygments()
231 check_for_nose()
236 check_for_nose()
232 check_for_pexpect() No newline at end of file
237 check_for_pexpect()
General Comments 0
You need to be logged in to leave comments. Login now