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, |
|
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, |
|
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 |
|
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( |
|
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( |
|
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( |
|
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(re |
|
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, |
|
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, re |
|
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( |
|
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.add |
|
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