##// END OF EJS Templates
Merged changes from ipython-frontend1. Better error rendering in Cocoa frontend. Refactored frontendbase.FrontEndBase so that FrontEndBase is synchronous and AsynchronousFronEndBase(FrontEndBase) wraps FrontEndBase in Twisted goodness.
Barry Wark -
r1312:06dc07c6 merge
parent child Browse files
Show More
@@ -0,0 +1,256 b''
1 // !$*UTF8*$!
2 {
3 archiveVersion = 1;
4 classes = {
5 };
6 objectVersion = 42;
7 objects = {
8
9 /* Begin PBXContainerItemProxy section */
10 4C5B7ADB0E1A0BCD006CB905 /* PBXContainerItemProxy */ = {
11 isa = PBXContainerItemProxy;
12 containerPortal = 4C96F4FE0E199AB500B03430 /* Project object */;
13 proxyType = 1;
14 remoteGlobalIDString = 4C96F50C0E199AF100B03430 /* Cocoa Frontend Plugin */;
15 remoteInfo = "Cocoa Frontend Plugin";
16 };
17 /* End PBXContainerItemProxy section */
18
19 /* Begin PBXFileReference section */
20 4C5B7A8D0E1A0B4C006CB905 /* Plugin-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "Plugin-Info.plist"; sourceTree = "<group>"; };
21 4C5B7AD30E1A0BC8006CB905 /* Placeholder (Do Not Use).bundle */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "Placeholder (Do Not Use).bundle"; sourceTree = BUILT_PRODUCTS_DIR; };
22 4C5B7AD40E1A0BC8006CB905 /* Placeholder (Do Not Use)-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "Placeholder (Do Not Use)-Info.plist"; sourceTree = "<group>"; };
23 /* End PBXFileReference section */
24
25 /* Begin PBXFrameworksBuildPhase section */
26 4C5B7AD10E1A0BC8006CB905 /* Frameworks */ = {
27 isa = PBXFrameworksBuildPhase;
28 buildActionMask = 2147483647;
29 files = (
30 );
31 runOnlyForDeploymentPostprocessing = 0;
32 };
33 /* End PBXFrameworksBuildPhase section */
34
35 /* Begin PBXGroup section */
36 4C5B7A8C0E1A0B4C006CB905 /* Products */ = {
37 isa = PBXGroup;
38 children = (
39 4C5B7AD30E1A0BC8006CB905 /* Placeholder (Do Not Use).bundle */,
40 );
41 name = Products;
42 sourceTree = "<group>";
43 };
44 4C96F4FC0E199AB500B03430 = {
45 isa = PBXGroup;
46 children = (
47 4C5B7A8C0E1A0B4C006CB905 /* Products */,
48 4C5B7A8D0E1A0B4C006CB905 /* Plugin-Info.plist */,
49 4C5B7AD40E1A0BC8006CB905 /* Placeholder (Do Not Use)-Info.plist */,
50 );
51 sourceTree = "<group>";
52 };
53 /* End PBXGroup section */
54
55 /* Begin PBXLegacyTarget section */
56 4C96F50C0E199AF100B03430 /* Cocoa Frontend Plugin */ = {
57 isa = PBXLegacyTarget;
58 buildArgumentsString = "$(ACTION)";
59 buildConfigurationList = 4C96F5110E199B3300B03430 /* Build configuration list for PBXLegacyTarget "Cocoa Frontend Plugin" */;
60 buildPhases = (
61 );
62 buildToolPath = /usr/bin/make;
63 buildWorkingDirectory = "";
64 dependencies = (
65 );
66 name = "Cocoa Frontend Plugin";
67 passBuildSettingsInEnvironment = 1;
68 productName = "Cocoa Frontend Plugin";
69 };
70 /* End PBXLegacyTarget section */
71
72 /* Begin PBXNativeTarget section */
73 4C5B7AD20E1A0BC8006CB905 /* Placeholder (Do Not Use) */ = {
74 isa = PBXNativeTarget;
75 buildConfigurationList = 4C5B7ADA0E1A0BC9006CB905 /* Build configuration list for PBXNativeTarget "Placeholder (Do Not Use)" */;
76 buildPhases = (
77 4C5B7ACF0E1A0BC8006CB905 /* Resources */,
78 4C5B7AD00E1A0BC8006CB905 /* Sources */,
79 4C5B7AD10E1A0BC8006CB905 /* Frameworks */,
80 );
81 buildRules = (
82 );
83 dependencies = (
84 4C5B7ADC0E1A0BCD006CB905 /* PBXTargetDependency */,
85 );
86 name = "Placeholder (Do Not Use)";
87 productName = "Placeholder (Do Not Use)";
88 productReference = 4C5B7AD30E1A0BC8006CB905 /* Placeholder (Do Not Use).bundle */;
89 productType = "com.apple.product-type.bundle";
90 };
91 /* End PBXNativeTarget section */
92
93 /* Begin PBXProject section */
94 4C96F4FE0E199AB500B03430 /* Project object */ = {
95 isa = PBXProject;
96 buildConfigurationList = 4C96F5010E199AB500B03430 /* Build configuration list for PBXProject "CocoaFrontendPlugin" */;
97 compatibilityVersion = "Xcode 2.4";
98 hasScannedForEncodings = 0;
99 mainGroup = 4C96F4FC0E199AB500B03430;
100 productRefGroup = 4C5B7A8C0E1A0B4C006CB905 /* Products */;
101 projectDirPath = "";
102 projectRoot = "";
103 targets = (
104 4C96F50C0E199AF100B03430 /* Cocoa Frontend Plugin */,
105 4C5B7AD20E1A0BC8006CB905 /* Placeholder (Do Not Use) */,
106 );
107 };
108 /* End PBXProject section */
109
110 /* Begin PBXResourcesBuildPhase section */
111 4C5B7ACF0E1A0BC8006CB905 /* Resources */ = {
112 isa = PBXResourcesBuildPhase;
113 buildActionMask = 2147483647;
114 files = (
115 );
116 runOnlyForDeploymentPostprocessing = 0;
117 };
118 /* End PBXResourcesBuildPhase section */
119
120 /* Begin PBXSourcesBuildPhase section */
121 4C5B7AD00E1A0BC8006CB905 /* Sources */ = {
122 isa = PBXSourcesBuildPhase;
123 buildActionMask = 2147483647;
124 files = (
125 );
126 runOnlyForDeploymentPostprocessing = 0;
127 };
128 /* End PBXSourcesBuildPhase section */
129
130 /* Begin PBXTargetDependency section */
131 4C5B7ADC0E1A0BCD006CB905 /* PBXTargetDependency */ = {
132 isa = PBXTargetDependency;
133 target = 4C96F50C0E199AF100B03430 /* Cocoa Frontend Plugin */;
134 targetProxy = 4C5B7ADB0E1A0BCD006CB905 /* PBXContainerItemProxy */;
135 };
136 /* End PBXTargetDependency section */
137
138 /* Begin XCBuildConfiguration section */
139 4C5B7AD50E1A0BC9006CB905 /* Debug */ = {
140 isa = XCBuildConfiguration;
141 buildSettings = {
142 COPY_PHASE_STRIP = NO;
143 GCC_DYNAMIC_NO_PIC = NO;
144 GCC_ENABLE_FIX_AND_CONTINUE = YES;
145 GCC_MODEL_TUNING = G5;
146 GCC_OPTIMIZATION_LEVEL = 0;
147 GCC_PRECOMPILE_PREFIX_HEADER = YES;
148 GCC_PREFIX_HEADER = "$(SYSTEM_LIBRARY_DIR)/Frameworks/AppKit.framework/Headers/AppKit.h";
149 INFOPLIST_FILE = "Placeholder (Do Not Use)-Info.plist";
150 INSTALL_PATH = "$(HOME)/Library/Bundles";
151 OTHER_LDFLAGS = (
152 "-framework",
153 Foundation,
154 "-framework",
155 AppKit,
156 );
157 PREBINDING = NO;
158 PRODUCT_NAME = "Placeholder (Do Not Use)";
159 WRAPPER_EXTENSION = bundle;
160 ZERO_LINK = YES;
161 };
162 name = Debug;
163 };
164 4C5B7AD60E1A0BC9006CB905 /* Release */ = {
165 isa = XCBuildConfiguration;
166 buildSettings = {
167 COPY_PHASE_STRIP = YES;
168 DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
169 GCC_ENABLE_FIX_AND_CONTINUE = NO;
170 GCC_MODEL_TUNING = G5;
171 GCC_PRECOMPILE_PREFIX_HEADER = YES;
172 GCC_PREFIX_HEADER = "$(SYSTEM_LIBRARY_DIR)/Frameworks/AppKit.framework/Headers/AppKit.h";
173 INFOPLIST_FILE = "Placeholder (Do Not Use)-Info.plist";
174 INSTALL_PATH = "$(HOME)/Library/Bundles";
175 OTHER_LDFLAGS = (
176 "-framework",
177 Foundation,
178 "-framework",
179 AppKit,
180 );
181 PREBINDING = NO;
182 PRODUCT_NAME = "Placeholder (Do Not Use)";
183 WRAPPER_EXTENSION = bundle;
184 ZERO_LINK = NO;
185 };
186 name = Release;
187 };
188 4C96F4FF0E199AB500B03430 /* Debug */ = {
189 isa = XCBuildConfiguration;
190 buildSettings = {
191 COPY_PHASE_STRIP = NO;
192 };
193 name = Debug;
194 };
195 4C96F5000E199AB500B03430 /* Release */ = {
196 isa = XCBuildConfiguration;
197 buildSettings = {
198 COPY_PHASE_STRIP = YES;
199 };
200 name = Release;
201 };
202 4C96F50D0E199AF100B03430 /* Debug */ = {
203 isa = XCBuildConfiguration;
204 buildSettings = {
205 COPY_PHASE_STRIP = NO;
206 GCC_DYNAMIC_NO_PIC = NO;
207 GCC_OPTIMIZATION_LEVEL = 0;
208 PRODUCT_NAME = "Cocoa Frontend Plugin";
209 };
210 name = Debug;
211 };
212 4C96F50E0E199AF100B03430 /* Release */ = {
213 isa = XCBuildConfiguration;
214 buildSettings = {
215 COPY_PHASE_STRIP = YES;
216 DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
217 GCC_ENABLE_FIX_AND_CONTINUE = NO;
218 PRODUCT_NAME = "Cocoa Frontend Plugin";
219 ZERO_LINK = NO;
220 };
221 name = Release;
222 };
223 /* End XCBuildConfiguration section */
224
225 /* Begin XCConfigurationList section */
226 4C5B7ADA0E1A0BC9006CB905 /* Build configuration list for PBXNativeTarget "Placeholder (Do Not Use)" */ = {
227 isa = XCConfigurationList;
228 buildConfigurations = (
229 4C5B7AD50E1A0BC9006CB905 /* Debug */,
230 4C5B7AD60E1A0BC9006CB905 /* Release */,
231 );
232 defaultConfigurationIsVisible = 0;
233 defaultConfigurationName = Release;
234 };
235 4C96F5010E199AB500B03430 /* Build configuration list for PBXProject "CocoaFrontendPlugin" */ = {
236 isa = XCConfigurationList;
237 buildConfigurations = (
238 4C96F4FF0E199AB500B03430 /* Debug */,
239 4C96F5000E199AB500B03430 /* Release */,
240 );
241 defaultConfigurationIsVisible = 0;
242 defaultConfigurationName = Release;
243 };
244 4C96F5110E199B3300B03430 /* Build configuration list for PBXLegacyTarget "Cocoa Frontend Plugin" */ = {
245 isa = XCConfigurationList;
246 buildConfigurations = (
247 4C96F50D0E199AF100B03430 /* Debug */,
248 4C96F50E0E199AF100B03430 /* Release */,
249 );
250 defaultConfigurationIsVisible = 0;
251 defaultConfigurationName = Release;
252 };
253 /* End XCConfigurationList section */
254 };
255 rootObject = 4C96F4FE0E199AB500B03430 /* Project object */;
256 }
@@ -0,0 +1,25 b''
1 # encoding: utf-8
2 """
3 Provides a namespace for loading the Cocoa frontend via a Cocoa plugin.
4
5 Author: Barry Wark
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 from PyObjCTools import AppHelper
17 from twisted.internet import _threadedselect
18
19 #make sure _threadedselect is installed first
20 reactor = _threadedselect.install()
21
22 # load the Cocoa frontend controller
23 from IPython.frontend.cocoa.cocoa_frontend import IPythonCocoaController
24 reactor.interleave(AppHelper.callAfter)
25 assert(reactor.running)
@@ -0,0 +1,20 b''
1 <?xml version="1.0" encoding="UTF-8"?>
2 <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3 <plist version="1.0">
4 <dict>
5 <key>CFBundleDevelopmentRegion</key>
6 <string>English</string>
7 <key>CFBundleExecutable</key>
8 <string>${EXECUTABLE_NAME}</string>
9 <key>CFBundleIdentifier</key>
10 <string>com.yourcompany.Placeholder (Do Not Use)</string>
11 <key>CFBundleInfoDictionaryVersion</key>
12 <string>6.0</string>
13 <key>CFBundlePackageType</key>
14 <string>BNDL</string>
15 <key>CFBundleSignature</key>
16 <string>????</string>
17 <key>CFBundleVersion</key>
18 <string>1.0</string>
19 </dict>
20 </plist>
@@ -1,504 +1,504 b''
1 # encoding: utf-8
1 # encoding: utf-8
2 # -*- test-case-name: IPython.frontend.cocoa.tests.test_cocoa_frontend -*-
2 # -*- test-case-name: IPython.frontend.cocoa.tests.test_cocoa_frontend -*-
3
3
4 """PyObjC classes to provide a Cocoa frontend to the
4 """PyObjC classes to provide a Cocoa frontend to the
5 IPython.kernel.engineservice.IEngineBase.
5 IPython.kernel.engineservice.IEngineBase.
6
6
7 To add an IPython interpreter to a cocoa app, instantiate an
7 To add an IPython interpreter to a cocoa app, instantiate an
8 IPythonCocoaController in a XIB and connect its textView outlet to an
8 IPythonCocoaController in a XIB and connect its textView outlet to an
9 NSTextView instance in your UI. That's it.
9 NSTextView instance in your UI. That's it.
10
10
11 Author: Barry Wark
11 Author: Barry Wark
12 """
12 """
13
13
14 __docformat__ = "restructuredtext en"
14 __docformat__ = "restructuredtext en"
15
15
16 #-----------------------------------------------------------------------------
16 #-----------------------------------------------------------------------------
17 # Copyright (C) 2008 The IPython Development Team
17 # Copyright (C) 2008 The IPython Development Team
18 #
18 #
19 # Distributed under the terms of the BSD License. The full license is in
19 # Distributed under the terms of the BSD License. The full license is in
20 # the file COPYING, distributed as part of this software.
20 # the file COPYING, distributed as part of this software.
21 #-----------------------------------------------------------------------------
21 #-----------------------------------------------------------------------------
22
22
23 #-----------------------------------------------------------------------------
23 #-----------------------------------------------------------------------------
24 # Imports
24 # Imports
25 #-----------------------------------------------------------------------------
25 #-----------------------------------------------------------------------------
26
26
27 import sys
27 import sys
28 import objc
28 import objc
29 import uuid
29 import uuid
30
30
31 from Foundation import NSObject, NSMutableArray, NSMutableDictionary,\
31 from Foundation import NSObject, NSMutableArray, NSMutableDictionary,\
32 NSLog, NSNotificationCenter, NSMakeRange,\
32 NSLog, NSNotificationCenter, NSMakeRange,\
33 NSLocalizedString, NSIntersectionRange,\
33 NSLocalizedString, NSIntersectionRange,\
34 NSString, NSAutoreleasePool
34 NSString, NSAutoreleasePool
35
35
36 from AppKit import NSApplicationWillTerminateNotification, NSBeep,\
36 from AppKit import NSApplicationWillTerminateNotification, NSBeep,\
37 NSTextView, NSRulerView, NSVerticalRuler
37 NSTextView, NSRulerView, NSVerticalRuler
38
38
39 from pprint import saferepr
39 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 FrontEndBase
43 from IPython.frontend.frontendbase import AsynchronousFrontEndBase
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
47
47
48 #------------------------------------------------------------------------------
48 #------------------------------------------------------------------------------
49 # Classes to implement the Cocoa frontend
49 # Classes to implement the Cocoa frontend
50 #------------------------------------------------------------------------------
50 #------------------------------------------------------------------------------
51
51
52 # TODO:
52 # TODO:
53 # 1. use MultiEngineClient and out-of-process engine rather than
53 # 1. use MultiEngineClient and out-of-process engine rather than
54 # ThreadedEngineService?
54 # ThreadedEngineService?
55 # 2. integrate Xgrid launching of engines
55 # 2. integrate Xgrid launching of engines
56
56
57 class AutoreleasePoolWrappedThreadedEngineService(ThreadedEngineService):
57 class AutoreleasePoolWrappedThreadedEngineService(ThreadedEngineService):
58 """Wrap all blocks in an NSAutoreleasePool"""
58 """Wrap all blocks in an NSAutoreleasePool"""
59
59
60 def wrapped_execute(self, msg, lines):
60 def wrapped_execute(self, msg, lines):
61 """wrapped_execute"""
61 """wrapped_execute"""
62 try:
62 try:
63 p = NSAutoreleasePool.alloc().init()
63 p = NSAutoreleasePool.alloc().init()
64 result = self.shell.execute(lines)
64 result = self.shell.execute(lines)
65 except Exception,e:
65 except Exception,e:
66 # This gives the following:
66 # This gives the following:
67 # et=exception class
67 # et=exception class
68 # ev=exception class instance
68 # ev=exception class instance
69 # tb=traceback object
69 # tb=traceback object
70 et,ev,tb = sys.exc_info()
70 et,ev,tb = sys.exc_info()
71 # This call adds attributes to the exception value
71 # This call adds attributes to the exception value
72 et,ev,tb = self.shell.formatTraceback(et,ev,tb,msg)
72 et,ev,tb = self.shell.formatTraceback(et,ev,tb,msg)
73 # Add another attribute
73 # Add another attribute
74
74
75 # Create a new exception with the new attributes
75 # Create a new exception with the new attributes
76 e = et(ev._ipython_traceback_text)
76 e = et(ev._ipython_traceback_text)
77 e._ipython_engine_info = msg
77 e._ipython_engine_info = msg
78
78
79 # Re-raise
79 # Re-raise
80 raise e
80 raise e
81 finally:
81 finally:
82 p.drain()
82 p.drain()
83
83
84 return result
84 return result
85
85
86 def execute(self, lines):
86 def execute(self, lines):
87 # Only import this if we are going to use this class
87 # Only import this if we are going to use this class
88 from twisted.internet import threads
88 from twisted.internet import threads
89
89
90 msg = {'engineid':self.id,
90 msg = {'engineid':self.id,
91 'method':'execute',
91 'method':'execute',
92 'args':[lines]}
92 'args':[lines]}
93
93
94 d = threads.deferToThread(self.wrapped_execute, msg, lines)
94 d = threads.deferToThread(self.wrapped_execute, msg, lines)
95 d.addCallback(self.addIDToResult)
95 d.addCallback(self.addIDToResult)
96 return d
96 return d
97
97
98
98
99 class IPythonCocoaController(NSObject, FrontEndBase):
99 class IPythonCocoaController(NSObject, AsynchronousFrontEndBase):
100 userNS = objc.ivar() #mirror of engine.user_ns (key=>str(value))
100 userNS = objc.ivar() #mirror of engine.user_ns (key=>str(value))
101 waitingForEngine = objc.ivar().bool()
101 waitingForEngine = objc.ivar().bool()
102 textView = objc.IBOutlet()
102 textView = objc.IBOutlet()
103
103
104 def init(self):
104 def init(self):
105 self = super(IPythonCocoaController, self).init()
105 self = super(IPythonCocoaController, self).init()
106 FrontEndBase.__init__(self,
106 AsynchronousFrontEndBase.__init__(self,
107 engine=AutoreleasePoolWrappedThreadedEngineService())
107 engine=AutoreleasePoolWrappedThreadedEngineService())
108 if(self != None):
108 if(self != None):
109 self._common_init()
109 self._common_init()
110
110
111 return self
111 return self
112
112
113 def _common_init(self):
113 def _common_init(self):
114 """_common_init"""
114 """_common_init"""
115
115
116 self.userNS = NSMutableDictionary.dictionary()
116 self.userNS = NSMutableDictionary.dictionary()
117 self.waitingForEngine = False
117 self.waitingForEngine = False
118
118
119 self.lines = {}
119 self.lines = {}
120 self.tabSpaces = 4
120 self.tabSpaces = 4
121 self.tabUsesSpaces = True
121 self.tabUsesSpaces = True
122 self.currentBlockID = self.next_block_ID()
122 self.currentBlockID = self.next_block_ID()
123 self.blockRanges = {} # blockID=>NSRange
123 self.blockRanges = {} # blockID=>NSRange
124
124
125
125
126 def awakeFromNib(self):
126 def awakeFromNib(self):
127 """awakeFromNib"""
127 """awakeFromNib"""
128
128
129 self._common_init()
129 self._common_init()
130
130
131 # Start the IPython engine
131 # Start the IPython engine
132 self.engine.startService()
132 self.engine.startService()
133 NSLog('IPython engine started')
133 NSLog('IPython engine started')
134
134
135 # Register for app termination
135 # Register for app termination
136 nc = NSNotificationCenter.defaultCenter()
136 nc = NSNotificationCenter.defaultCenter()
137 nc.addObserver_selector_name_object_(
137 nc.addObserver_selector_name_object_(
138 self,
138 self,
139 'appWillTerminate:',
139 'appWillTerminate:',
140 NSApplicationWillTerminateNotification,
140 NSApplicationWillTerminateNotification,
141 None)
141 None)
142
142
143 self.textView.setDelegate_(self)
143 self.textView.setDelegate_(self)
144 self.textView.enclosingScrollView().setHasVerticalRuler_(True)
144 self.textView.enclosingScrollView().setHasVerticalRuler_(True)
145 r = NSRulerView.alloc().initWithScrollView_orientation_(
145 r = NSRulerView.alloc().initWithScrollView_orientation_(
146 self.textView.enclosingScrollView(),
146 self.textView.enclosingScrollView(),
147 NSVerticalRuler)
147 NSVerticalRuler)
148 self.verticalRulerView = r
148 self.verticalRulerView = r
149 self.verticalRulerView.setClientView_(self.textView)
149 self.verticalRulerView.setClientView_(self.textView)
150 self._start_cli_banner()
150 self._start_cli_banner()
151
151
152
152
153 def appWillTerminate_(self, notification):
153 def appWillTerminate_(self, notification):
154 """appWillTerminate"""
154 """appWillTerminate"""
155
155
156 self.engine.stopService()
156 self.engine.stopService()
157
157
158
158
159 def complete(self, token):
159 def complete(self, token):
160 """Complete token in engine's user_ns
160 """Complete token in engine's user_ns
161
161
162 Parameters
162 Parameters
163 ----------
163 ----------
164 token : string
164 token : string
165
165
166 Result
166 Result
167 ------
167 ------
168 Deferred result of
168 Deferred result of
169 IPython.kernel.engineservice.IEngineBase.complete
169 IPython.kernel.engineservice.IEngineBase.complete
170 """
170 """
171
171
172 return self.engine.complete(token)
172 return self.engine.complete(token)
173
173
174
174
175 def execute(self, block, blockID=None):
175 def execute(self, block, blockID=None):
176 self.waitingForEngine = True
176 self.waitingForEngine = True
177 self.willChangeValueForKey_('commandHistory')
177 self.willChangeValueForKey_('commandHistory')
178 d = super(IPythonCocoaController, self).execute(block,
178 d = super(IPythonCocoaController, self).execute(block,
179 blockID)
179 blockID)
180 d.addBoth(self._engine_done)
180 d.addBoth(self._engine_done)
181 d.addCallback(self._update_user_ns)
181 d.addCallback(self._update_user_ns)
182
182
183 return d
183 return d
184
184
185
185
186 def push_(self, namespace):
186 def push_(self, namespace):
187 """Push dictionary of key=>values to python namespace"""
187 """Push dictionary of key=>values to python namespace"""
188
188
189 self.waitingForEngine = True
189 self.waitingForEngine = True
190 self.willChangeValueForKey_('commandHistory')
190 self.willChangeValueForKey_('commandHistory')
191 d = self.engine.push(namespace)
191 d = self.engine.push(namespace)
192 d.addBoth(self._engine_done)
192 d.addBoth(self._engine_done)
193 d.addCallback(self._update_user_ns)
193 d.addCallback(self._update_user_ns)
194
194
195
195
196 def pull_(self, keys):
196 def pull_(self, keys):
197 """Pull keys from python namespace"""
197 """Pull keys from python namespace"""
198
198
199 self.waitingForEngine = True
199 self.waitingForEngine = True
200 result = blockingCallFromThread(self.engine.pull, keys)
200 result = blockingCallFromThread(self.engine.pull, keys)
201 self.waitingForEngine = False
201 self.waitingForEngine = False
202
202
203 def executeFileAtPath_(self, path):
203 def executeFileAtPath_(self, path):
204 """Execute file at path in an empty namespace. Update the engine
204 """Execute file at path in an empty namespace. Update the engine
205 user_ns with the resulting locals."""
205 user_ns with the resulting locals."""
206
206
207 lines,err = NSString.stringWithContentsOfFile_encoding_error_(
207 lines,err = NSString.stringWithContentsOfFile_encoding_error_(
208 path,
208 path,
209 NSString.defaultCStringEncoding(),
209 NSString.defaultCStringEncoding(),
210 None)
210 None)
211 self.engine.execute(lines)
211 self.engine.execute(lines)
212
212
213
213
214 def _engine_done(self, x):
214 def _engine_done(self, x):
215 self.waitingForEngine = False
215 self.waitingForEngine = False
216 self.didChangeValueForKey_('commandHistory')
216 self.didChangeValueForKey_('commandHistory')
217 return x
217 return x
218
218
219 def _update_user_ns(self, result):
219 def _update_user_ns(self, result):
220 """Update self.userNS from self.engine's namespace"""
220 """Update self.userNS from self.engine's namespace"""
221 d = self.engine.keys()
221 d = self.engine.keys()
222 d.addCallback(self._get_engine_namespace_values_for_keys)
222 d.addCallback(self._get_engine_namespace_values_for_keys)
223
223
224 return result
224 return result
225
225
226
226
227 def _get_engine_namespace_values_for_keys(self, keys):
227 def _get_engine_namespace_values_for_keys(self, keys):
228 d = self.engine.pull(keys)
228 d = self.engine.pull(keys)
229 d.addCallback(self._store_engine_namespace_values, keys=keys)
229 d.addCallback(self._store_engine_namespace_values, keys=keys)
230
230
231
231
232 def _store_engine_namespace_values(self, values, keys=[]):
232 def _store_engine_namespace_values(self, values, keys=[]):
233 assert(len(values) == len(keys))
233 assert(len(values) == len(keys))
234 self.willChangeValueForKey_('userNS')
234 self.willChangeValueForKey_('userNS')
235 for (k,v) in zip(keys,values):
235 for (k,v) in zip(keys,values):
236 self.userNS[k] = saferepr(v)
236 self.userNS[k] = saferepr(v)
237 self.didChangeValueForKey_('userNS')
237 self.didChangeValueForKey_('userNS')
238
238
239
239
240 def update_cell_prompt(self, result, blockID=None):
240 def update_cell_prompt(self, result, blockID=None):
241 if(isinstance(result, Failure)):
241 if(isinstance(result, Failure)):
242 self.insert_text(self.input_prompt(),
242 self.insert_text(self.input_prompt(),
243 textRange=NSMakeRange(self.blockRanges[blockID].location,0),
243 textRange=NSMakeRange(self.blockRanges[blockID].location,0),
244 scrollToVisible=False
244 scrollToVisible=False
245 )
245 )
246 else:
246 else:
247 self.insert_text(self.input_prompt(number=result['number']),
247 self.insert_text(self.input_prompt(number=result['number']),
248 textRange=NSMakeRange(self.blockRanges[blockID].location,0),
248 textRange=NSMakeRange(self.blockRanges[blockID].location,0),
249 scrollToVisible=False
249 scrollToVisible=False
250 )
250 )
251
251
252 return result
252 return result
253
253
254
254
255 def render_result(self, result):
255 def render_result(self, result):
256 blockID = result['blockID']
256 blockID = result['blockID']
257 inputRange = self.blockRanges[blockID]
257 inputRange = self.blockRanges[blockID]
258 del self.blockRanges[blockID]
258 del self.blockRanges[blockID]
259
259
260 #print inputRange,self.current_block_range()
260 #print inputRange,self.current_block_range()
261 self.insert_text('\n' +
261 self.insert_text('\n' +
262 self.output_prompt(number=result['number']) +
262 self.output_prompt(number=result['number']) +
263 result.get('display',{}).get('pprint','') +
263 result.get('display',{}).get('pprint','') +
264 '\n\n',
264 '\n\n',
265 textRange=NSMakeRange(inputRange.location+inputRange.length,
265 textRange=NSMakeRange(inputRange.location+inputRange.length,
266 0))
266 0))
267 return result
267 return result
268
268
269
269
270 def render_error(self, failure):
270 def render_error(self, failure):
271 self.insert_text('\n' +
271 self.insert_text('\n' +
272 self.output_prompt() +
272 self.output_prompt() +
273 '\n' +
273 '\n' +
274 failure.getErrorMessage() +
274 failure.getErrorMessage() +
275 '\n\n')
275 '\n\n')
276 self.start_new_block()
276 self.start_new_block()
277 return failure
277 return failure
278
278
279
279
280 def _start_cli_banner(self):
280 def _start_cli_banner(self):
281 """Print banner"""
281 """Print banner"""
282
282
283 banner = """IPython1 %s -- An enhanced Interactive Python.""" % \
283 banner = """IPython1 %s -- An enhanced Interactive Python.""" % \
284 IPython.__version__
284 IPython.__version__
285
285
286 self.insert_text(banner + '\n\n')
286 self.insert_text(banner + '\n\n')
287
287
288
288
289 def start_new_block(self):
289 def start_new_block(self):
290 """"""
290 """"""
291
291
292 self.currentBlockID = self.next_block_ID()
292 self.currentBlockID = self.next_block_ID()
293
293
294
294
295
295
296 def next_block_ID(self):
296 def next_block_ID(self):
297
297
298 return uuid.uuid4()
298 return uuid.uuid4()
299
299
300 def current_block_range(self):
300 def current_block_range(self):
301 return self.blockRanges.get(self.currentBlockID,
301 return self.blockRanges.get(self.currentBlockID,
302 NSMakeRange(self.textView.textStorage().length(),
302 NSMakeRange(self.textView.textStorage().length(),
303 0))
303 0))
304
304
305 def current_block(self):
305 def current_block(self):
306 """The current block's text"""
306 """The current block's text"""
307
307
308 return self.text_for_range(self.current_block_range())
308 return self.text_for_range(self.current_block_range())
309
309
310 def text_for_range(self, textRange):
310 def text_for_range(self, textRange):
311 """text_for_range"""
311 """text_for_range"""
312
312
313 ts = self.textView.textStorage()
313 ts = self.textView.textStorage()
314 return ts.string().substringWithRange_(textRange)
314 return ts.string().substringWithRange_(textRange)
315
315
316 def current_line(self):
316 def current_line(self):
317 block = self.text_for_range(self.current_block_range())
317 block = self.text_for_range(self.current_block_range())
318 block = block.split('\n')
318 block = block.split('\n')
319 return block[-1]
319 return block[-1]
320
320
321
321
322 def insert_text(self, string=None, textRange=None, scrollToVisible=True):
322 def insert_text(self, string=None, textRange=None, scrollToVisible=True):
323 """Insert text into textView at textRange, updating blockRanges
323 """Insert text into textView at textRange, updating blockRanges
324 as necessary
324 as necessary
325 """
325 """
326
326
327 if(textRange == None):
327 if(textRange == None):
328 #range for end of text
328 #range for end of text
329 textRange = NSMakeRange(self.textView.textStorage().length(), 0)
329 textRange = NSMakeRange(self.textView.textStorage().length(), 0)
330
330
331 for r in self.blockRanges.itervalues():
331 for r in self.blockRanges.itervalues():
332 intersection = NSIntersectionRange(r,textRange)
332 intersection = NSIntersectionRange(r,textRange)
333 if(intersection.length == 0): #ranges don't intersect
333 if(intersection.length == 0): #ranges don't intersect
334 if r.location >= textRange.location:
334 if r.location >= textRange.location:
335 r.location += len(string)
335 r.location += len(string)
336 else: #ranges intersect
336 else: #ranges intersect
337 if(r.location <= textRange.location):
337 if(r.location <= textRange.location):
338 assert(intersection.length == textRange.length)
338 assert(intersection.length == textRange.length)
339 r.length += textRange.length
339 r.length += textRange.length
340 else:
340 else:
341 r.location += intersection.length
341 r.location += intersection.length
342
342
343 self.textView.replaceCharactersInRange_withString_(
343 self.textView.replaceCharactersInRange_withString_(
344 textRange, string)
344 textRange, string)
345 self.textView.setSelectedRange_(
345 self.textView.setSelectedRange_(
346 NSMakeRange(textRange.location+len(string), 0))
346 NSMakeRange(textRange.location+len(string), 0))
347 if(scrollToVisible):
347 if(scrollToVisible):
348 self.textView.scrollRangeToVisible_(textRange)
348 self.textView.scrollRangeToVisible_(textRange)
349
349
350
350
351
351
352
352
353 def replace_current_block_with_string(self, textView, string):
353 def replace_current_block_with_string(self, textView, string):
354 textView.replaceCharactersInRange_withString_(
354 textView.replaceCharactersInRange_withString_(
355 self.current_block_range(),
355 self.current_block_range(),
356 string)
356 string)
357 self.current_block_range().length = len(string)
357 self.current_block_range().length = len(string)
358 r = NSMakeRange(textView.textStorage().length(), 0)
358 r = NSMakeRange(textView.textStorage().length(), 0)
359 textView.scrollRangeToVisible_(r)
359 textView.scrollRangeToVisible_(r)
360 textView.setSelectedRange_(r)
360 textView.setSelectedRange_(r)
361
361
362
362
363 def current_indent_string(self):
363 def current_indent_string(self):
364 """returns string for indent or None if no indent"""
364 """returns string for indent or None if no indent"""
365
365
366 return self._indent_for_block(self.current_block())
366 return self._indent_for_block(self.current_block())
367
367
368
368
369 def _indent_for_block(self, block):
369 def _indent_for_block(self, block):
370 lines = block.split('\n')
370 lines = block.split('\n')
371 if(len(lines) > 1):
371 if(len(lines) > 1):
372 currentIndent = len(lines[-1]) - len(lines[-1].lstrip())
372 currentIndent = len(lines[-1]) - len(lines[-1].lstrip())
373 if(currentIndent == 0):
373 if(currentIndent == 0):
374 currentIndent = self.tabSpaces
374 currentIndent = self.tabSpaces
375
375
376 if(self.tabUsesSpaces):
376 if(self.tabUsesSpaces):
377 result = ' ' * currentIndent
377 result = ' ' * currentIndent
378 else:
378 else:
379 result = '\t' * (currentIndent/self.tabSpaces)
379 result = '\t' * (currentIndent/self.tabSpaces)
380 else:
380 else:
381 result = None
381 result = None
382
382
383 return result
383 return result
384
384
385
385
386 # NSTextView delegate methods...
386 # NSTextView delegate methods...
387 def textView_doCommandBySelector_(self, textView, selector):
387 def textView_doCommandBySelector_(self, textView, selector):
388 assert(textView == self.textView)
388 assert(textView == self.textView)
389 NSLog("textView_doCommandBySelector_: "+selector)
389 NSLog("textView_doCommandBySelector_: "+selector)
390
390
391
391
392 if(selector == 'insertNewline:'):
392 if(selector == 'insertNewline:'):
393 indent = self.current_indent_string()
393 indent = self.current_indent_string()
394 if(indent):
394 if(indent):
395 line = indent + self.current_line()
395 line = indent + self.current_line()
396 else:
396 else:
397 line = self.current_line()
397 line = self.current_line()
398
398
399 if(self.is_complete(self.current_block())):
399 if(self.is_complete(self.current_block())):
400 self.execute(self.current_block(),
400 self.execute(self.current_block(),
401 blockID=self.currentBlockID)
401 blockID=self.currentBlockID)
402 self.start_new_block()
402 self.start_new_block()
403
403
404 return True
404 return True
405
405
406 return False
406 return False
407
407
408 elif(selector == 'moveUp:'):
408 elif(selector == 'moveUp:'):
409 prevBlock = self.get_history_previous(self.current_block())
409 prevBlock = self.get_history_previous(self.current_block())
410 if(prevBlock != None):
410 if(prevBlock != None):
411 self.replace_current_block_with_string(textView, prevBlock)
411 self.replace_current_block_with_string(textView, prevBlock)
412 else:
412 else:
413 NSBeep()
413 NSBeep()
414 return True
414 return True
415
415
416 elif(selector == 'moveDown:'):
416 elif(selector == 'moveDown:'):
417 nextBlock = self.get_history_next()
417 nextBlock = self.get_history_next()
418 if(nextBlock != None):
418 if(nextBlock != None):
419 self.replace_current_block_with_string(textView, nextBlock)
419 self.replace_current_block_with_string(textView, nextBlock)
420 else:
420 else:
421 NSBeep()
421 NSBeep()
422 return True
422 return True
423
423
424 elif(selector == 'moveToBeginningOfParagraph:'):
424 elif(selector == 'moveToBeginningOfParagraph:'):
425 textView.setSelectedRange_(NSMakeRange(
425 textView.setSelectedRange_(NSMakeRange(
426 self.current_block_range().location,
426 self.current_block_range().location,
427 0))
427 0))
428 return True
428 return True
429 elif(selector == 'moveToEndOfParagraph:'):
429 elif(selector == 'moveToEndOfParagraph:'):
430 textView.setSelectedRange_(NSMakeRange(
430 textView.setSelectedRange_(NSMakeRange(
431 self.current_block_range().location + \
431 self.current_block_range().location + \
432 self.current_block_range().length, 0))
432 self.current_block_range().length, 0))
433 return True
433 return True
434 elif(selector == 'deleteToEndOfParagraph:'):
434 elif(selector == 'deleteToEndOfParagraph:'):
435 if(textView.selectedRange().location <= \
435 if(textView.selectedRange().location <= \
436 self.current_block_range().location):
436 self.current_block_range().location):
437 # Intersect the selected range with the current line range
437 # Intersect the selected range with the current line range
438 if(self.current_block_range().length < 0):
438 if(self.current_block_range().length < 0):
439 self.blockRanges[self.currentBlockID].length = 0
439 self.blockRanges[self.currentBlockID].length = 0
440
440
441 r = NSIntersectionRange(textView.rangesForUserTextChange()[0],
441 r = NSIntersectionRange(textView.rangesForUserTextChange()[0],
442 self.current_block_range())
442 self.current_block_range())
443
443
444 if(r.length > 0): #no intersection
444 if(r.length > 0): #no intersection
445 textView.setSelectedRange_(r)
445 textView.setSelectedRange_(r)
446
446
447 return False # don't actually handle the delete
447 return False # don't actually handle the delete
448
448
449 elif(selector == 'insertTab:'):
449 elif(selector == 'insertTab:'):
450 if(len(self.current_line().strip()) == 0): #only white space
450 if(len(self.current_line().strip()) == 0): #only white space
451 return False
451 return False
452 else:
452 else:
453 self.textView.complete_(self)
453 self.textView.complete_(self)
454 return True
454 return True
455
455
456 elif(selector == 'deleteBackward:'):
456 elif(selector == 'deleteBackward:'):
457 #if we're at the beginning of the current block, ignore
457 #if we're at the beginning of the current block, ignore
458 if(textView.selectedRange().location == \
458 if(textView.selectedRange().location == \
459 self.current_block_range().location):
459 self.current_block_range().location):
460 return True
460 return True
461 else:
461 else:
462 self.current_block_range().length-=1
462 self.current_block_range().length-=1
463 return False
463 return False
464 return False
464 return False
465
465
466
466
467 def textView_shouldChangeTextInRanges_replacementStrings_(self,
467 def textView_shouldChangeTextInRanges_replacementStrings_(self,
468 textView, ranges, replacementStrings):
468 textView, ranges, replacementStrings):
469 """
469 """
470 Delegate method for NSTextView.
470 Delegate method for NSTextView.
471
471
472 Refuse change text in ranges not at end, but make those changes at
472 Refuse change text in ranges not at end, but make those changes at
473 end.
473 end.
474 """
474 """
475
475
476 assert(len(ranges) == len(replacementStrings))
476 assert(len(ranges) == len(replacementStrings))
477 allow = True
477 allow = True
478 for r,s in zip(ranges, replacementStrings):
478 for r,s in zip(ranges, replacementStrings):
479 r = r.rangeValue()
479 r = r.rangeValue()
480 if(textView.textStorage().length() > 0 and
480 if(textView.textStorage().length() > 0 and
481 r.location < self.current_block_range().location):
481 r.location < self.current_block_range().location):
482 self.insert_text(s)
482 self.insert_text(s)
483 allow = False
483 allow = False
484
484
485
485
486 self.blockRanges.setdefault(self.currentBlockID,
486 self.blockRanges.setdefault(self.currentBlockID,
487 self.current_block_range()).length +=\
487 self.current_block_range()).length +=\
488 len(s)
488 len(s)
489
489
490 return allow
490 return allow
491
491
492 def textView_completions_forPartialWordRange_indexOfSelectedItem_(self,
492 def textView_completions_forPartialWordRange_indexOfSelectedItem_(self,
493 textView, words, charRange, index):
493 textView, words, charRange, index):
494 try:
494 try:
495 ts = textView.textStorage()
495 ts = textView.textStorage()
496 token = ts.string().substringWithRange_(charRange)
496 token = ts.string().substringWithRange_(charRange)
497 completions = blockingCallFromThread(self.complete, token)
497 completions = blockingCallFromThread(self.complete, token)
498 except:
498 except:
499 completions = objc.nil
499 completions = objc.nil
500 NSBeep()
500 NSBeep()
501
501
502 return (completions,0)
502 return (completions,0)
503
503
504
504
@@ -1,34 +1,35 b''
1 # encoding: utf-8
1 # encoding: utf-8
2 """
2 """
3 setup.py
3 setup.py
4
4
5 Setuptools installer script for generating a Cocoa plugin for the
5 Setuptools installer script for generating a Cocoa plugin for the
6 IPython cocoa frontend
6 IPython cocoa frontend
7
7
8 Author: Barry Wark
8 Author: Barry Wark
9 """
9 """
10 __docformat__ = "restructuredtext en"
10 __docformat__ = "restructuredtext en"
11
11
12 #-----------------------------------------------------------------------------
12 #-----------------------------------------------------------------------------
13 # Copyright (C) 2008 The IPython Development Team
13 # Copyright (C) 2008 The IPython Development Team
14 #
14 #
15 # Distributed under the terms of the BSD License. The full license is in
15 # Distributed under the terms of the BSD License. The full license is in
16 # the file COPYING, distributed as part of this software.
16 # the file COPYING, distributed as part of this software.
17 #-----------------------------------------------------------------------------
17 #-----------------------------------------------------------------------------
18
18
19 from setuptools import setup
19 from setuptools import setup
20
20
21 infoPlist = dict(
21 infoPlist = dict(
22 CFBundleDevelopmentRegion='English',
22 CFBundleDevelopmentRegion='English',
23 CFBundleIdentifier='org.scipy.ipython.cocoa_frontend',
23 CFBundleIdentifier='org.scipy.ipython.cocoa_frontend',
24 NSPrincipalClass='IPythonCocoaController',
24 NSPrincipalClass='IPythonCocoaController',
25 )
25 )
26
26
27 setup(
27 setup(
28 plugin=['IPythonCocoaFrontendLoader.py'],
28 plugin=['IPythonCocoaFrontendLoader.py'],
29 setup_requires=['py2app'],
29 setup_requires=['py2app'],
30 options=dict(py2app=dict(
30 options=dict(py2app=dict(
31 plist=infoPlist,
31 plist=infoPlist,
32 excludes=['IPython','twisted']
32 site_packages=True,
33 excludes=['IPython','twisted','PyObjCTools']
33 )),
34 )),
34 ) No newline at end of file
35 )
@@ -1,349 +1,396 b''
1 # encoding: utf-8
1 # encoding: utf-8
2 # -*- test-case-name: IPython.frontend.tests.test_frontendbase -*-
2 # -*- test-case-name: IPython.frontend.tests.test_frontendbase -*-
3 """
3 """
4 frontendbase provides an interface and base class for GUI frontends for
4 frontendbase provides an interface and base class for GUI frontends for
5 IPython.kernel/IPython.kernel.core.
5 IPython.kernel/IPython.kernel.core.
6
6
7 Frontend implementations will likely want to subclass FrontEndBase.
7 Frontend implementations will likely want to subclass FrontEndBase.
8
8
9 Author: Barry Wark
9 Author: Barry Wark
10 """
10 """
11 __docformat__ = "restructuredtext en"
11 __docformat__ = "restructuredtext en"
12
12
13 #-------------------------------------------------------------------------------
13 #-------------------------------------------------------------------------------
14 # Copyright (C) 2008 The IPython Development Team
14 # Copyright (C) 2008 The IPython Development Team
15 #
15 #
16 # Distributed under the terms of the BSD License. The full license is in
16 # Distributed under the terms of the BSD License. The full license is in
17 # the file COPYING, distributed as part of this software.
17 # the file COPYING, distributed as part of this software.
18 #-------------------------------------------------------------------------------
18 #-------------------------------------------------------------------------------
19
19
20 #-------------------------------------------------------------------------------
20 #-------------------------------------------------------------------------------
21 # Imports
21 # Imports
22 #-------------------------------------------------------------------------------
22 #-------------------------------------------------------------------------------
23 import string
23 import string
24 import uuid
24 import uuid
25 import _ast
25 import _ast
26
26
27 import zope.interface as zi
27 import zope.interface as zi
28
28
29 from IPython.kernel.core.history import FrontEndHistory
29 from IPython.kernel.core.history import FrontEndHistory
30 from IPython.kernel.core.util import Bunch
30 from IPython.kernel.core.util import Bunch
31 from IPython.kernel.engineservice import IEngineCore
31 from IPython.kernel.engineservice import IEngineCore
32
32
33 from twisted.python.failure import Failure
33 from twisted.python.failure import Failure
34
34
35 ##############################################################################
35 ##############################################################################
36 # TEMPORARY!!! fake configuration, while we decide whether to use tconfig or
36 # TEMPORARY!!! fake configuration, while we decide whether to use tconfig or
37 # not
37 # not
38
38
39 rc = Bunch()
39 rc = Bunch()
40 rc.prompt_in1 = r'In [$number]: '
40 rc.prompt_in1 = r'In [$number]: '
41 rc.prompt_in2 = r'...'
41 rc.prompt_in2 = r'...'
42 rc.prompt_out = r'Out [$number]: '
42 rc.prompt_out = r'Out [$number]: '
43
43
44 ##############################################################################
44 ##############################################################################
45
45
46 class IFrontEndFactory(zi.Interface):
46 class IFrontEndFactory(zi.Interface):
47 """Factory interface for frontends."""
47 """Factory interface for frontends."""
48
48
49 def __call__(engine=None, history=None):
49 def __call__(engine=None, history=None):
50 """
50 """
51 Parameters:
51 Parameters:
52 interpreter : IPython.kernel.engineservice.IEngineCore
52 interpreter : IPython.kernel.engineservice.IEngineCore
53 """
53 """
54
54
55 pass
55 pass
56
56
57
57
58
58
59 class IFrontEnd(zi.Interface):
59 class IFrontEnd(zi.Interface):
60 """Interface for frontends. All methods return t.i.d.Deferred"""
60 """Interface for frontends. All methods return t.i.d.Deferred"""
61
61
62 zi.Attribute("input_prompt_template", "string.Template instance\
62 zi.Attribute("input_prompt_template", "string.Template instance\
63 substituteable with execute result.")
63 substituteable with execute result.")
64 zi.Attribute("output_prompt_template", "string.Template instance\
64 zi.Attribute("output_prompt_template", "string.Template instance\
65 substituteable with execute result.")
65 substituteable with execute result.")
66 zi.Attribute("continuation_prompt_template", "string.Template instance\
66 zi.Attribute("continuation_prompt_template", "string.Template instance\
67 substituteable with execute result.")
67 substituteable with execute result.")
68
68
69 def update_cell_prompt(result, blockID=None):
69 def update_cell_prompt(result, blockID=None):
70 """Subclass may override to update the input prompt for a block.
70 """Subclass may override to update the input prompt for a block.
71 Since this method will be called as a
71 Since this method will be called as a
72 twisted.internet.defer.Deferred's callback/errback,
72 twisted.internet.defer.Deferred's callback/errback,
73 implementations should return result when finished.
73 implementations should return result when finished.
74
74
75 Result is a result dict in case of success, and a
75 Result is a result dict in case of success, and a
76 twisted.python.util.failure.Failure in case of an error
76 twisted.python.util.failure.Failure in case of an error
77 """
77 """
78
78
79 pass
79 pass
80
80
81
81
82 def render_result(result):
82 def render_result(result):
83 """Render the result of an execute call. Implementors may choose the
83 """Render the result of an execute call. Implementors may choose the
84 method of rendering.
84 method of rendering.
85 For example, a notebook-style frontend might render a Chaco plot
85 For example, a notebook-style frontend might render a Chaco plot
86 inline.
86 inline.
87
87
88 Parameters:
88 Parameters:
89 result : dict (result of IEngineBase.execute )
89 result : dict (result of IEngineBase.execute )
90 blockID = result['blockID']
90 blockID = result['blockID']
91
91
92 Result:
92 Result:
93 Output of frontend rendering
93 Output of frontend rendering
94 """
94 """
95
95
96 pass
96 pass
97
97
98 def render_error(failure):
98 def render_error(failure):
99 """Subclasses must override to render the failure. Since this method
99 """Subclasses must override to render the failure. Since this method
100 will be called as a twisted.internet.defer.Deferred's callback,
100 will be called as a twisted.internet.defer.Deferred's callback,
101 implementations should return result when finished.
101 implementations should return result when finished.
102
102
103 blockID = failure.blockID
103 blockID = failure.blockID
104 """
104 """
105
105
106 pass
106 pass
107
107
108
108
109 def input_prompt(number=''):
109 def input_prompt(number=''):
110 """Returns the input prompt by subsituting into
110 """Returns the input prompt by subsituting into
111 self.input_prompt_template
111 self.input_prompt_template
112 """
112 """
113 pass
113 pass
114
114
115 def output_prompt(number=''):
115 def output_prompt(number=''):
116 """Returns the output prompt by subsituting into
116 """Returns the output prompt by subsituting into
117 self.output_prompt_template
117 self.output_prompt_template
118 """
118 """
119
119
120 pass
120 pass
121
121
122 def continuation_prompt():
122 def continuation_prompt():
123 """Returns the continuation prompt by subsituting into
123 """Returns the continuation prompt by subsituting into
124 self.continuation_prompt_template
124 self.continuation_prompt_template
125 """
125 """
126
126
127 pass
127 pass
128
128
129 def is_complete(block):
129 def is_complete(block):
130 """Returns True if block is complete, False otherwise."""
130 """Returns True if block is complete, False otherwise."""
131
131
132 pass
132 pass
133
133
134 def compile_ast(block):
134 def compile_ast(block):
135 """Compiles block to an _ast.AST"""
135 """Compiles block to an _ast.AST"""
136
136
137 pass
137 pass
138
138
139
139
140 def get_history_previous(currentBlock):
140 def get_history_previous(currentBlock):
141 """Returns the block previous in the history. Saves currentBlock if
141 """Returns the block previous in the history. Saves currentBlock if
142 the history_cursor is currently at the end of the input history"""
142 the history_cursor is currently at the end of the input history"""
143 pass
143 pass
144
144
145 def get_history_next():
145 def get_history_next():
146 """Returns the next block in the history."""
146 """Returns the next block in the history."""
147
147
148 pass
148 pass
149
149
150
150
151 class FrontEndBase(object):
151 class FrontEndBase(object):
152 """
152 """
153 FrontEndBase manages the state tasks for a CLI frontend:
153 FrontEndBase manages the state tasks for a CLI frontend:
154 - Input and output history management
154 - Input and output history management
155 - Input/continuation and output prompt generation
155 - Input/continuation and output prompt generation
156
156
157 Some issues (due to possibly unavailable engine):
157 Some issues (due to possibly unavailable engine):
158 - How do we get the current cell number for the engine?
158 - How do we get the current cell number for the engine?
159 - How do we handle completions?
159 - How do we handle completions?
160 """
160 """
161
161
162 zi.implements(IFrontEnd)
163 zi.classProvides(IFrontEndFactory)
164
165 history_cursor = 0
162 history_cursor = 0
166
163
167 current_indent_level = 0
164 current_indent_level = 0
168
165
169
166
170 input_prompt_template = string.Template(rc.prompt_in1)
167 input_prompt_template = string.Template(rc.prompt_in1)
171 output_prompt_template = string.Template(rc.prompt_out)
168 output_prompt_template = string.Template(rc.prompt_out)
172 continuation_prompt_template = string.Template(rc.prompt_in2)
169 continuation_prompt_template = string.Template(rc.prompt_in2)
173
170
174 def __init__(self, engine=None, history=None):
171 def __init__(self, shell=None, history=None):
175 assert(engine==None or IEngineCore.providedBy(engine))
172 self.shell = shell
176 self.engine = IEngineCore(engine)
177 if history is None:
173 if history is None:
178 self.history = FrontEndHistory(input_cache=[''])
174 self.history = FrontEndHistory(input_cache=[''])
179 else:
175 else:
180 self.history = history
176 self.history = history
181
177
182
178
183 def input_prompt(self, number=''):
179 def input_prompt(self, number=''):
184 """Returns the current input prompt
180 """Returns the current input prompt
185
181
186 It would be great to use ipython1.core.prompts.Prompt1 here
182 It would be great to use ipython1.core.prompts.Prompt1 here
187 """
183 """
188 return self.input_prompt_template.safe_substitute({'number':number})
184 return self.input_prompt_template.safe_substitute({'number':number})
189
185
190
186
191 def continuation_prompt(self):
187 def continuation_prompt(self):
192 """Returns the current continuation prompt"""
188 """Returns the current continuation prompt"""
193
189
194 return self.continuation_prompt_template.safe_substitute()
190 return self.continuation_prompt_template.safe_substitute()
195
191
196 def output_prompt(self, number=''):
192 def output_prompt(self, number=''):
197 """Returns the output prompt for result"""
193 """Returns the output prompt for result"""
198
194
199 return self.output_prompt_template.safe_substitute({'number':number})
195 return self.output_prompt_template.safe_substitute({'number':number})
200
196
201
197
202 def is_complete(self, block):
198 def is_complete(self, block):
203 """Determine if block is complete.
199 """Determine if block is complete.
204
200
205 Parameters
201 Parameters
206 block : string
202 block : string
207
203
208 Result
204 Result
209 True if block can be sent to the engine without compile errors.
205 True if block can be sent to the engine without compile errors.
210 False otherwise.
206 False otherwise.
211 """
207 """
212
208
213 try:
209 try:
214 ast = self.compile_ast(block)
210 ast = self.compile_ast(block)
215 except:
211 except:
216 return False
212 return False
217
213
218 lines = block.split('\n')
214 lines = block.split('\n')
219 return (len(lines)==1 or str(lines[-1])=='')
215 return (len(lines)==1 or str(lines[-1])=='')
220
216
221
217
222 def compile_ast(self, block):
218 def compile_ast(self, block):
223 """Compile block to an AST
219 """Compile block to an AST
224
220
225 Parameters:
221 Parameters:
226 block : str
222 block : str
227
223
228 Result:
224 Result:
229 AST
225 AST
230
226
231 Throws:
227 Throws:
232 Exception if block cannot be compiled
228 Exception if block cannot be compiled
233 """
229 """
234
230
235 return compile(block, "<string>", "exec", _ast.PyCF_ONLY_AST)
231 return compile(block, "<string>", "exec", _ast.PyCF_ONLY_AST)
236
232
237
233
238 def execute(self, block, blockID=None):
234 def execute(self, block, blockID=None):
239 """Execute the block and return result.
235 """Execute the block and return the result.
240
236
241 Parameters:
237 Parameters:
242 block : {str, AST}
238 block : {str, AST}
243 blockID : any
239 blockID : any
244 Caller may provide an ID to identify this block.
240 Caller may provide an ID to identify this block.
245 result['blockID'] := blockID
241 result['blockID'] := blockID
246
242
247 Result:
243 Result:
248 Deferred result of self.interpreter.execute
244 Deferred result of self.interpreter.execute
249 """
245 """
250
246
251 if(not self.is_complete(block)):
247 if(not self.is_complete(block)):
252 return Failure(Exception("Block is not compilable"))
248 raise Exception("Block is not compilable")
253
249
254 if(blockID == None):
250 if(blockID == None):
255 blockID = uuid.uuid4() #random UUID
251 blockID = uuid.uuid4() #random UUID
256
252
257 d = self.engine.execute(block)
253 try:
258 d.addCallback(self._add_history, block=block)
254 result = self.shell.execute(block)
259 d.addCallbacks(self._add_block_id_for_result,
255 except Exception,e:
260 errback=self._add_block_id_for_failure,
256 e = self._add_block_id_for_failure(e, blockID=blockID)
261 callbackArgs=(blockID,),
257 e = self.update_cell_prompt(e, blockID=blockID)
262 errbackArgs=(blockID,))
258 e = self.render_error(e)
263 d.addBoth(self.update_cell_prompt, blockID=blockID)
259 else:
264 d.addCallbacks(self.render_result,
260 result = self._add_block_id_for_result(result, blockID=blockID)
265 errback=self.render_error)
261 result = self.update_cell_prompt(result, blockID=blockID)
262 result = self.render_result(result)
266
263
267 return d
264 return result
268
265
269
266
270 def _add_block_id_for_result(self, result, blockID):
267 def _add_block_id_for_result(self, result, blockID):
271 """Add the blockID to result or failure. Unfortunatley, we have to
268 """Add the blockID to result or failure. Unfortunatley, we have to
272 treat failures differently than result dicts.
269 treat failures differently than result dicts.
273 """
270 """
274
271
275 result['blockID'] = blockID
272 result['blockID'] = blockID
276
273
277 return result
274 return result
278
275
279 def _add_block_id_for_failure(self, failure, blockID):
276 def _add_block_id_for_failure(self, failure, blockID):
280 """_add_block_id_for_failure"""
277 """_add_block_id_for_failure"""
281
278
282 failure.blockID = blockID
279 failure.blockID = blockID
283 return failure
280 return failure
284
281
285
282
286 def _add_history(self, result, block=None):
283 def _add_history(self, result, block=None):
287 """Add block to the history"""
284 """Add block to the history"""
288
285
289 assert(block != None)
286 assert(block != None)
290 self.history.add_items([block])
287 self.history.add_items([block])
291 self.history_cursor += 1
288 self.history_cursor += 1
292
289
293 return result
290 return result
294
291
295
292
296 def get_history_previous(self, currentBlock):
293 def get_history_previous(self, currentBlock):
297 """ Returns previous history string and decrement history cursor.
294 """ Returns previous history string and decrement history cursor.
298 """
295 """
299 command = self.history.get_history_item(self.history_cursor - 1)
296 command = self.history.get_history_item(self.history_cursor - 1)
300
297
301 if command is not None:
298 if command is not None:
302 if(self.history_cursor == len(self.history.input_cache)):
299 if(self.history_cursor == len(self.history.input_cache)):
303 self.history.input_cache[self.history_cursor] = currentBlock
300 self.history.input_cache[self.history_cursor] = currentBlock
304 self.history_cursor -= 1
301 self.history_cursor -= 1
305 return command
302 return command
306
303
307
304
308 def get_history_next(self):
305 def get_history_next(self):
309 """ Returns next history string and increment history cursor.
306 """ Returns next history string and increment history cursor.
310 """
307 """
311 command = self.history.get_history_item(self.history_cursor+1)
308 command = self.history.get_history_item(self.history_cursor+1)
312
309
313 if command is not None:
310 if command is not None:
314 self.history_cursor += 1
311 self.history_cursor += 1
315 return command
312 return command
316
313
317 ###
314 ###
318 # Subclasses probably want to override these methods...
315 # Subclasses probably want to override these methods...
319 ###
316 ###
320
317
321 def update_cell_prompt(self, result, blockID=None):
318 def update_cell_prompt(self, result, blockID=None):
322 """Subclass may override to update the input prompt for a block.
319 """Subclass may override to update the input prompt for a block.
323 Since this method will be called as a
320 Since this method will be called as a
324 twisted.internet.defer.Deferred's callback, implementations should
321 twisted.internet.defer.Deferred's callback, implementations should
325 return result when finished.
322 return result when finished.
326 """
323 """
327
324
328 return result
325 return result
329
326
330
327
331 def render_result(self, result):
328 def render_result(self, result):
332 """Subclasses must override to render result. Since this method will
329 """Subclasses must override to render result. Since this method will
333 be called as a twisted.internet.defer.Deferred's callback,
330 be called as a twisted.internet.defer.Deferred's callback,
334 implementations should return result when finished.
331 implementations should return result when finished.
335 """
332 """
336
333
337 return result
334 return result
338
335
339
336
340 def render_error(self, failure):
337 def render_error(self, failure):
341 """Subclasses must override to render the failure. Since this method
338 """Subclasses must override to render the failure. Since this method
342 will be called as a twisted.internet.defer.Deferred's callback,
339 will be called as a twisted.internet.defer.Deferred's callback,
343 implementations should return result when finished.
340 implementations should return result when finished.
344 """
341 """
345
342
346 return failure
343 return failure
347
344
348
345
349
346
347 class AsynchronousFrontEndBase(FrontEndBase):
348 """
349 Overrides FrontEndBase to wrap execute in a deferred result.
350 All callbacks are made as callbacks on the deferred result.
351 """
352
353 zi.implements(IFrontEnd)
354 zi.classProvides(IFrontEndFactory)
355
356 def __init__(self, engine=None, history=None):
357 assert(engine==None or IEngineCore.providedBy(engine))
358 self.engine = IEngineCore(engine)
359 if history is None:
360 self.history = FrontEndHistory(input_cache=[''])
361 else:
362 self.history = history
363
364
365 def execute(self, block, blockID=None):
366 """Execute the block and return the deferred result.
367
368 Parameters:
369 block : {str, AST}
370 blockID : any
371 Caller may provide an ID to identify this block.
372 result['blockID'] := blockID
373
374 Result:
375 Deferred result of self.interpreter.execute
376 """
377
378 if(not self.is_complete(block)):
379 return Failure(Exception("Block is not compilable"))
380
381 if(blockID == None):
382 blockID = uuid.uuid4() #random UUID
383
384 d = self.engine.execute(block)
385 d.addCallback(self._add_history, block=block)
386 d.addCallbacks(self._add_block_id_for_result,
387 errback=self._add_block_id_for_failure,
388 callbackArgs=(blockID,),
389 errbackArgs=(blockID,))
390 d.addBoth(self.update_cell_prompt, blockID=blockID)
391 d.addCallbacks(self.render_result,
392 errback=self.render_error)
393
394 return d
395
396
@@ -1,151 +1,151 b''
1 # encoding: utf-8
1 # encoding: utf-8
2
2
3 """This file contains unittests for the frontendbase module."""
3 """This file contains unittests for the frontendbase module."""
4
4
5 __docformat__ = "restructuredtext en"
5 __docformat__ = "restructuredtext en"
6
6
7 #---------------------------------------------------------------------------
7 #---------------------------------------------------------------------------
8 # Copyright (C) 2008 The IPython Development Team
8 # Copyright (C) 2008 The IPython Development Team
9 #
9 #
10 # Distributed under the terms of the BSD License. The full license is in
10 # Distributed under the terms of the BSD License. The full license is in
11 # the file COPYING, distributed as part of this software.
11 # the file COPYING, distributed as part of this software.
12 #---------------------------------------------------------------------------
12 #---------------------------------------------------------------------------
13
13
14 #---------------------------------------------------------------------------
14 #---------------------------------------------------------------------------
15 # Imports
15 # Imports
16 #---------------------------------------------------------------------------
16 #---------------------------------------------------------------------------
17
17
18 import unittest
18 import unittest
19 from IPython.frontend import frontendbase
19 from IPython.frontend import frontendbase
20 from IPython.kernel.engineservice import EngineService
20 from IPython.kernel.engineservice import EngineService
21
21
22 class FrontEndCallbackChecker(frontendbase.FrontEndBase):
22 class FrontEndCallbackChecker(frontendbase.AsynchronousFrontEndBase):
23 """FrontEndBase subclass for checking callbacks"""
23 """FrontEndBase subclass for checking callbacks"""
24 def __init__(self, engine=None, history=None):
24 def __init__(self, engine=None, history=None):
25 super(FrontEndCallbackChecker, self).__init__(engine=engine,
25 super(FrontEndCallbackChecker, self).__init__(engine=engine,
26 history=history)
26 history=history)
27 self.updateCalled = False
27 self.updateCalled = False
28 self.renderResultCalled = False
28 self.renderResultCalled = False
29 self.renderErrorCalled = False
29 self.renderErrorCalled = False
30
30
31 def update_cell_prompt(self, result, blockID=None):
31 def update_cell_prompt(self, result, blockID=None):
32 self.updateCalled = True
32 self.updateCalled = True
33 return result
33 return result
34
34
35 def render_result(self, result):
35 def render_result(self, result):
36 self.renderResultCalled = True
36 self.renderResultCalled = True
37 return result
37 return result
38
38
39
39
40 def render_error(self, failure):
40 def render_error(self, failure):
41 self.renderErrorCalled = True
41 self.renderErrorCalled = True
42 return failure
42 return failure
43
43
44
44
45
45
46
46
47 class TestFrontendBase(unittest.TestCase):
47 class TestAsynchronousFrontendBase(unittest.TestCase):
48 def setUp(self):
48 def setUp(self):
49 """Setup the EngineService and FrontEndBase"""
49 """Setup the EngineService and FrontEndBase"""
50
50
51 self.fb = FrontEndCallbackChecker(engine=EngineService())
51 self.fb = FrontEndCallbackChecker(engine=EngineService())
52
52
53
53
54 def test_implements_IFrontEnd(self):
54 def test_implements_IFrontEnd(self):
55 assert(frontendbase.IFrontEnd.implementedBy(
55 assert(frontendbase.IFrontEnd.implementedBy(
56 frontendbase.FrontEndBase))
56 frontendbase.AsynchronousFrontEndBase))
57
57
58
58
59 def test_is_complete_returns_False_for_incomplete_block(self):
59 def test_is_complete_returns_False_for_incomplete_block(self):
60 """"""
60 """"""
61
61
62 block = """def test(a):"""
62 block = """def test(a):"""
63
63
64 assert(self.fb.is_complete(block) == False)
64 assert(self.fb.is_complete(block) == False)
65
65
66 def test_is_complete_returns_True_for_complete_block(self):
66 def test_is_complete_returns_True_for_complete_block(self):
67 """"""
67 """"""
68
68
69 block = """def test(a): pass"""
69 block = """def test(a): pass"""
70
70
71 assert(self.fb.is_complete(block))
71 assert(self.fb.is_complete(block))
72
72
73 block = """a=3"""
73 block = """a=3"""
74
74
75 assert(self.fb.is_complete(block))
75 assert(self.fb.is_complete(block))
76
76
77
77
78 def test_blockID_added_to_result(self):
78 def test_blockID_added_to_result(self):
79 block = """3+3"""
79 block = """3+3"""
80
80
81 d = self.fb.execute(block, blockID='TEST_ID')
81 d = self.fb.execute(block, blockID='TEST_ID')
82
82
83 d.addCallback(self.checkBlockID, expected='TEST_ID')
83 d.addCallback(self.checkBlockID, expected='TEST_ID')
84
84
85 def test_blockID_added_to_failure(self):
85 def test_blockID_added_to_failure(self):
86 block = "raise Exception()"
86 block = "raise Exception()"
87
87
88 d = self.fb.execute(block,blockID='TEST_ID')
88 d = self.fb.execute(block,blockID='TEST_ID')
89 d.addErrback(self.checkFailureID, expected='TEST_ID')
89 d.addErrback(self.checkFailureID, expected='TEST_ID')
90
90
91 def checkBlockID(self, result, expected=""):
91 def checkBlockID(self, result, expected=""):
92 assert(result['blockID'] == expected)
92 assert(result['blockID'] == expected)
93
93
94
94
95 def checkFailureID(self, failure, expected=""):
95 def checkFailureID(self, failure, expected=""):
96 assert(failure.blockID == expected)
96 assert(failure.blockID == expected)
97
97
98
98
99 def test_callbacks_added_to_execute(self):
99 def test_callbacks_added_to_execute(self):
100 """test that
100 """test that
101 update_cell_prompt
101 update_cell_prompt
102 render_result
102 render_result
103
103
104 are added to execute request
104 are added to execute request
105 """
105 """
106
106
107 d = self.fb.execute("10+10")
107 d = self.fb.execute("10+10")
108 d.addCallback(self.checkCallbacks)
108 d.addCallback(self.checkCallbacks)
109
109
110
110
111 def checkCallbacks(self, result):
111 def checkCallbacks(self, result):
112 assert(self.fb.updateCalled)
112 assert(self.fb.updateCalled)
113 assert(self.fb.renderResultCalled)
113 assert(self.fb.renderResultCalled)
114
114
115
115
116 def test_error_callback_added_to_execute(self):
116 def test_error_callback_added_to_execute(self):
117 """test that render_error called on execution error"""
117 """test that render_error called on execution error"""
118
118
119 d = self.fb.execute("raise Exception()")
119 d = self.fb.execute("raise Exception()")
120 d.addCallback(self.checkRenderError)
120 d.addCallback(self.checkRenderError)
121
121
122 def checkRenderError(self, result):
122 def checkRenderError(self, result):
123 assert(self.fb.renderErrorCalled)
123 assert(self.fb.renderErrorCalled)
124
124
125 def test_history_returns_expected_block(self):
125 def test_history_returns_expected_block(self):
126 """Make sure history browsing doesn't fail"""
126 """Make sure history browsing doesn't fail"""
127
127
128 blocks = ["a=1","a=2","a=3"]
128 blocks = ["a=1","a=2","a=3"]
129 for b in blocks:
129 for b in blocks:
130 d = self.fb.execute(b)
130 d = self.fb.execute(b)
131
131
132 # d is now the deferred for the last executed block
132 # d is now the deferred for the last executed block
133 d.addCallback(self.historyTests, blocks)
133 d.addCallback(self.historyTests, blocks)
134
134
135
135
136 def historyTests(self, result, blocks):
136 def historyTests(self, result, blocks):
137 """historyTests"""
137 """historyTests"""
138
138
139 assert(len(blocks) >= 3)
139 assert(len(blocks) >= 3)
140 assert(self.fb.get_history_previous("") == blocks[-2])
140 assert(self.fb.get_history_previous("") == blocks[-2])
141 assert(self.fb.get_history_previous("") == blocks[-3])
141 assert(self.fb.get_history_previous("") == blocks[-3])
142 assert(self.fb.get_history_next() == blocks[-2])
142 assert(self.fb.get_history_next() == blocks[-2])
143
143
144
144
145 def test_history_returns_none_at_startup(self):
145 def test_history_returns_none_at_startup(self):
146 """test_history_returns_none_at_startup"""
146 """test_history_returns_none_at_startup"""
147
147
148 assert(self.fb.get_history_previous("")==None)
148 assert(self.fb.get_history_previous("")==None)
149 assert(self.fb.get_history_next()==None)
149 assert(self.fb.get_history_next()==None)
150
150
151
151
General Comments 0
You need to be logged in to leave comments. Login now