##// END OF EJS Templates
updates for frontendbase API. Cocoa plugin functional in Objective-C app
Barry Wark -
Show More
@@ -29,7 +29,8 b' import uuid'
29
29
30 from Foundation import NSObject, NSMutableArray, NSMutableDictionary,\
30 from Foundation import NSObject, NSMutableArray, NSMutableDictionary,\
31 NSLog, NSNotificationCenter, NSMakeRange,\
31 NSLog, NSNotificationCenter, NSMakeRange,\
32 NSLocalizedString, NSIntersectionRange
32 NSLocalizedString, NSIntersectionRange,\
33 NSString, NSAutoreleasePool
33
34
34 from AppKit import NSApplicationWillTerminateNotification, NSBeep,\
35 from AppKit import NSApplicationWillTerminateNotification, NSBeep,\
35 NSTextView, NSRulerView, NSVerticalRuler
36 NSTextView, NSRulerView, NSVerticalRuler
@@ -52,7 +53,29 b' from twisted.python.failure import Failure'
52 # ThreadedEngineService?
53 # ThreadedEngineService?
53 # 2. integrate Xgrid launching of engines
54 # 2. integrate Xgrid launching of engines
54
55
56 class AutoreleasePoolWrappedThreadedEngineService(ThreadedEngineService):
57 """Wrap all blocks in an NSAutoreleasePool"""
55
58
59 def wrapped_execute(self, lines):
60 """wrapped_execute"""
61
62 p = NSAutoreleasePool.alloc().init()
63 result = self.shell.execute(lines)
64 p.drain()
65
66 return result
67
68 def execute(self, lines):
69 # Only import this if we are going to use this class
70 from twisted.internet import threads
71
72 msg = {'engineid':self.id,
73 'method':'execute',
74 'args':[lines]}
75
76 d = threads.deferToThread(self.wrapped_execute, lines)
77 d.addCallback(self.addIDToResult)
78 return d
56
79
57
80
58 class IPythonCocoaController(NSObject, FrontEndBase):
81 class IPythonCocoaController(NSObject, FrontEndBase):
@@ -62,7 +85,8 b' class IPythonCocoaController(NSObject, FrontEndBase):'
62
85
63 def init(self):
86 def init(self):
64 self = super(IPythonCocoaController, self).init()
87 self = super(IPythonCocoaController, self).init()
65 FrontEndBase.__init__(self, engine=ThreadedEngineService())
88 FrontEndBase.__init__(self,
89 engine=AutoreleasePoolWrappedThreadedEngineService())
66 if(self != None):
90 if(self != None):
67 self._common_init()
91 self._common_init()
68
92
@@ -133,13 +157,42 b' class IPythonCocoaController(NSObject, FrontEndBase):'
133 def execute(self, block, blockID=None):
157 def execute(self, block, blockID=None):
134 self.waitingForEngine = True
158 self.waitingForEngine = True
135 self.willChangeValueForKey_('commandHistory')
159 self.willChangeValueForKey_('commandHistory')
136 d = super(IPythonCocoaController, self).execute(block, blockID)
160 d = super(IPythonCocoaController, self).execute(block,
161 blockID)
137 d.addBoth(self._engine_done)
162 d.addBoth(self._engine_done)
138 d.addCallback(self._update_user_ns)
163 d.addCallback(self._update_user_ns)
139
164
140 return d
165 return d
141
166
167
168 def push_(self, namespace):
169 """Push dictionary of key=>values to python namespace"""
170
171 self.waitingForEngine = True
172 self.willChangeValueForKey_('commandHistory')
173 d = self.engine.push(namespace)
174 d.addBoth(self._engine_done)
175 d.addCallback(self._update_user_ns)
176
177
178 def pull_(self, keys):
179 """Pull keys from python namespace"""
142
180
181 self.waitingForEngine = True
182 result = blockingCallFromThread(self.engine.pull, keys)
183 self.waitingForEngine = False
184
185 def executeFileAtPath_(self, path):
186 """Execute file at path in an empty namespace. Update the engine
187 user_ns with the resulting locals."""
188
189 lines,err = NSString.stringWithContentsOfFile_encoding_error_(
190 path,
191 NSString.defaultCStringEncoding(),
192 None)
193 self.engine.execute(lines)
194
195
143 def _engine_done(self, x):
196 def _engine_done(self, x):
144 self.waitingForEngine = False
197 self.waitingForEngine = False
145 self.didChangeValueForKey_('commandHistory')
198 self.didChangeValueForKey_('commandHistory')
@@ -166,14 +219,14 b' class IPythonCocoaController(NSObject, FrontEndBase):'
166 self.didChangeValueForKey_('userNS')
219 self.didChangeValueForKey_('userNS')
167
220
168
221
169 def update_cell_prompt(self, result):
222 def update_cell_prompt(self, result, blockID=None):
170 if(isinstance(result, Failure)):
223 if(isinstance(result, Failure)):
171 blockID = result.blockID
224 self.insert_text(self.input_prompt(),
225 textRange=NSMakeRange(self.blockRanges[blockID].location,0),
226 scrollToVisible=False
227 )
172 else:
228 else:
173 blockID = result['blockID']
229 self.insert_text(self.input_prompt(number=result['number']),
174
175
176 self.insert_text(self.input_prompt(result=result),
177 textRange=NSMakeRange(self.blockRanges[blockID].location,0),
230 textRange=NSMakeRange(self.blockRanges[blockID].location,0),
178 scrollToVisible=False
231 scrollToVisible=False
179 )
232 )
@@ -2,4 +2,5 b' include ./plugins.mk'
2
2
3 all : dist/IPythonCocoaController.plugin
3 all : dist/IPythonCocoaController.plugin
4
4
5 dist/IPythonCocoaController.plugin : ./FrontendLoader.py ./setup.py No newline at end of file
5 dist/IPythonCocoaController.plugin : ./IPythonCocoaFrontendLoader.py\
6 ./setup.py No newline at end of file
@@ -1,5 +1,4 b''
1 %.plugin::
1 %.plugin::
2 mkdir -p plugin
3 rm -rf dist/$(notdir $@)
2 rm -rf dist/$(notdir $@)
4 rm -rf build dist && \
3 rm -rf build dist && \
5 python setup.py py2app -s
4 python setup.py py2app -s
@@ -25,10 +25,10 b' infoPlist = dict('
25 )
25 )
26
26
27 setup(
27 setup(
28 plugin=['FrontendLoader.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']
32 excludes=['IPython','twisted']
33 )),
33 )),
34 ) No newline at end of file
34 )
@@ -66,23 +66,20 b' class IFrontEnd(zi.Interface):'
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(self, result):
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,
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 NB: result is a failure if the execute returned a failre.
75 Result is a result dict in case of success, and a
76 To get the blockID, you should do something like::
76 twisted.python.util.failure.Failure in case of an error
77 if(isinstance(result, twisted.python.failure.Failure)):
78 blockID = result.blockID
79 else:
80 blockID = result['blockID']
81 """
77 """
82
78
83 pass
79 pass
84
80
85 def render_result(self, result):
81
82 def render_result(result):
86 """Render the result of an execute call. Implementors may choose the
83 """Render the result of an execute call. Implementors may choose the
87 method of rendering.
84 method of rendering.
88 For example, a notebook-style frontend might render a Chaco plot
85 For example, a notebook-style frontend might render a Chaco plot
@@ -90,6 +87,7 b' class IFrontEnd(zi.Interface):'
90
87
91 Parameters:
88 Parameters:
92 result : dict (result of IEngineBase.execute )
89 result : dict (result of IEngineBase.execute )
90 blockID = result['blockID']
93
91
94 Result:
92 Result:
95 Output of frontend rendering
93 Output of frontend rendering
@@ -97,22 +95,24 b' class IFrontEnd(zi.Interface):'
97
95
98 pass
96 pass
99
97
100 def render_error(self, failure):
98 def render_error(failure):
101 """Subclasses must override to render the failure. Since this method
99 """Subclasses must override to render the failure. Since this method
102 ill be called as a twisted.internet.defer.Deferred's callback,
100 will be called as a twisted.internet.defer.Deferred's callback,
103 implementations should return result when finished.
101 implementations should return result when finished.
102
103 blockID = failure.blockID
104 """
104 """
105
105
106 pass
106 pass
107
107
108
108
109 def input_prompt(result={}):
109 def input_prompt(number=None):
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(result):
115 def output_prompt(number=None):
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 """
@@ -180,15 +180,12 b' class FrontEndBase(object):'
180 self.history = history
180 self.history = history
181
181
182
182
183 def input_prompt(self, result={}):
183 def input_prompt(self, number=None):
184 """Returns the current input prompt
184 """Returns the current input prompt
185
185
186 It would be great to use ipython1.core.prompts.Prompt1 here
186 It would be great to use ipython1.core.prompts.Prompt1 here
187 """
187 """
188
188 return self.input_prompt_template.safe_substitute({'number':number})
189 result.setdefault('number','')
190
191 return self.input_prompt_template.safe_substitute(result)
192
189
193
190
194 def continuation_prompt(self):
191 def continuation_prompt(self):
@@ -196,10 +193,10 b' class FrontEndBase(object):'
196
193
197 return self.continuation_prompt_template.safe_substitute()
194 return self.continuation_prompt_template.safe_substitute()
198
195
199 def output_prompt(self, result):
196 def output_prompt(self, number=None):
200 """Returns the output prompt for result"""
197 """Returns the output prompt for result"""
201
198
202 return self.output_prompt_template.safe_substitute(result)
199 return self.output_prompt_template.safe_substitute({'number':number})
203
200
204
201
205 def is_complete(self, block):
202 def is_complete(self, block):
@@ -259,25 +256,33 b' class FrontEndBase(object):'
259
256
260 d = self.engine.execute(block)
257 d = self.engine.execute(block)
261 d.addCallback(self._add_history, block=block)
258 d.addCallback(self._add_history, block=block)
262 d.addBoth(self._add_block_id, blockID)
259 d.addCallbacks(self._add_block_id_for_result,
263 d.addBoth(self.update_cell_prompt)
260 errback=self._add_block_id_for_failure,
264 d.addCallbacks(self.render_result, errback=self.render_error)
261 callbackArgs=(blockID,),
262 errbackArgs=(blockID,))
263 d.addBoth(self.update_cell_prompt, blockID=blockID)
264 d.addCallbacks(self.render_result,
265 errback=self.render_error)
265
266
266 return d
267 return d
267
268
268
269
269 def _add_block_id(self, result, blockID):
270 def _add_block_id_for_result(self, result, blockID):
270 """Add the blockID to result or failure. Unfortunatley, we have to
271 """Add the blockID to result or failure. Unfortunatley, we have to
271 treat failures differently than result dicts.
272 treat failures differently than result dicts.
272 """
273 """
273
274
274 if(isinstance(result, Failure)):
275 result['blockID'] = blockID
275 result.blockID = blockID
276 else:
277 result['blockID'] = blockID
278
276
279 return result
277 return result
280
278
279 def _add_block_id_for_failure(self, failure, blockID):
280 """_add_block_id_for_failure"""
281
282 failure.blockID = blockID
283 return failure
284
285
281 def _add_history(self, result, block=None):
286 def _add_history(self, result, block=None):
282 """Add block to the history"""
287 """Add block to the history"""
283
288
@@ -313,20 +318,11 b' class FrontEndBase(object):'
313 # Subclasses probably want to override these methods...
318 # Subclasses probably want to override these methods...
314 ###
319 ###
315
320
316 def update_cell_prompt(self, result):
321 def update_cell_prompt(self, result, blockID=None):
317 """Subclass may override to update the input prompt for a block.
322 """Subclass may override to update the input prompt for a block.
318 Since this method will be called as a
323 Since this method will be called as a
319 twisted.internet.defer.Deferred's callback, implementations should
324 twisted.internet.defer.Deferred's callback, implementations should
320 return result when finished.
325 return result when finished.
321
322 NB: result is a failure if the execute returned a failre.
323 To get the blockID, you should do something like::
324 if(isinstance(result, twisted.python.failure.Failure)):
325 blockID = result.blockID
326 else:
327 blockID = result['blockID']
328
329
330 """
326 """
331
327
332 return result
328 return result
@@ -344,7 +340,8 b' class FrontEndBase(object):'
344 def render_error(self, failure):
340 def render_error(self, failure):
345 """Subclasses must override to render the failure. Since this method
341 """Subclasses must override to render the failure. Since this method
346 will be called as a twisted.internet.defer.Deferred's callback,
342 will be called as a twisted.internet.defer.Deferred's callback,
347 implementations should return result when finished."""
343 implementations should return result when finished.
344 """
348
345
349 return failure
346 return failure
350
347
@@ -28,7 +28,7 b' class FrontEndCallbackChecker(frontendbase.FrontEndBase):'
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):
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
1 NO CONTENT: file was removed
NO CONTENT: file was removed
General Comments 0
You need to be logged in to leave comments. Login now