Show More
@@ -29,7 +29,8 b' import uuid' | |||
|
29 | 29 | |
|
30 | 30 | from Foundation import NSObject, NSMutableArray, NSMutableDictionary,\ |
|
31 | 31 | NSLog, NSNotificationCenter, NSMakeRange,\ |
|
32 | NSLocalizedString, NSIntersectionRange | |
|
32 | NSLocalizedString, NSIntersectionRange,\ | |
|
33 | NSString, NSAutoreleasePool | |
|
33 | 34 | |
|
34 | 35 | from AppKit import NSApplicationWillTerminateNotification, NSBeep,\ |
|
35 | 36 | NSTextView, NSRulerView, NSVerticalRuler |
@@ -52,7 +53,29 b' from twisted.python.failure import Failure' | |||
|
52 | 53 | # ThreadedEngineService? |
|
53 | 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 | 81 | class IPythonCocoaController(NSObject, FrontEndBase): |
@@ -62,7 +85,8 b' class IPythonCocoaController(NSObject, FrontEndBase):' | |||
|
62 | 85 | |
|
63 | 86 | def init(self): |
|
64 | 87 | self = super(IPythonCocoaController, self).init() |
|
65 |
FrontEndBase.__init__(self, |
|
|
88 | FrontEndBase.__init__(self, | |
|
89 | engine=AutoreleasePoolWrappedThreadedEngineService()) | |
|
66 | 90 | if(self != None): |
|
67 | 91 | self._common_init() |
|
68 | 92 | |
@@ -133,13 +157,42 b' class IPythonCocoaController(NSObject, FrontEndBase):' | |||
|
133 | 157 | def execute(self, block, blockID=None): |
|
134 | 158 | self.waitingForEngine = True |
|
135 | 159 | self.willChangeValueForKey_('commandHistory') |
|
136 |
d = super(IPythonCocoaController, self).execute(block, |
|
|
160 | d = super(IPythonCocoaController, self).execute(block, | |
|
161 | blockID) | |
|
137 | 162 | d.addBoth(self._engine_done) |
|
138 | 163 | d.addCallback(self._update_user_ns) |
|
139 | 164 | |
|
140 | 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 | 196 | def _engine_done(self, x): |
|
144 | 197 | self.waitingForEngine = False |
|
145 | 198 | self.didChangeValueForKey_('commandHistory') |
@@ -166,14 +219,14 b' class IPythonCocoaController(NSObject, FrontEndBase):' | |||
|
166 | 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 | 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 | 228 | else: |
|
173 | blockID = result['blockID'] | |
|
174 | ||
|
175 | ||
|
176 | self.insert_text(self.input_prompt(result=result), | |
|
229 | self.insert_text(self.input_prompt(number=result['number']), | |
|
177 | 230 | textRange=NSMakeRange(self.blockRanges[blockID].location,0), |
|
178 | 231 | scrollToVisible=False |
|
179 | 232 | ) |
@@ -2,4 +2,5 b' include ./plugins.mk' | |||
|
2 | 2 | |
|
3 | 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 | 1 | %.plugin:: |
|
2 | mkdir -p plugin | |
|
3 | 2 | rm -rf dist/$(notdir $@) |
|
4 | 3 | rm -rf build dist && \ |
|
5 | 4 | python setup.py py2app -s |
@@ -25,10 +25,10 b' infoPlist = dict(' | |||
|
25 | 25 | ) |
|
26 | 26 | |
|
27 | 27 | setup( |
|
28 | plugin=['FrontendLoader.py'], | |
|
28 | plugin=['IPythonCocoaFrontendLoader.py'], | |
|
29 | 29 | setup_requires=['py2app'], |
|
30 | 30 | options=dict(py2app=dict( |
|
31 | 31 | plist=infoPlist, |
|
32 | excludes=['IPython'] | |
|
32 | excludes=['IPython','twisted'] | |
|
33 | 33 | )), |
|
34 | 34 | ) No newline at end of file |
@@ -66,23 +66,20 b' class IFrontEnd(zi.Interface):' | |||
|
66 | 66 | zi.Attribute("continuation_prompt_template", "string.Template instance\ |
|
67 | 67 | substituteable with execute result.") |
|
68 | 68 | |
|
69 |
def update_cell_prompt( |
|
|
69 | def update_cell_prompt(result, blockID=None): | |
|
70 | 70 | """Subclass may override to update the input prompt for a block. |
|
71 | 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 | 73 | implementations should return result when finished. |
|
74 | 74 | |
|
75 | NB: result is a failure if the execute returned a failre. | |
|
76 | To get the blockID, you should do something like:: | |
|
77 | if(isinstance(result, twisted.python.failure.Failure)): | |
|
78 | blockID = result.blockID | |
|
79 | else: | |
|
80 | blockID = result['blockID'] | |
|
75 | Result is a result dict in case of success, and a | |
|
76 | twisted.python.util.failure.Failure in case of an error | |
|
81 | 77 | """ |
|
82 | 78 | |
|
83 | 79 | pass |
|
84 | 80 | |
|
85 | def render_result(self, result): | |
|
81 | ||
|
82 | def render_result(result): | |
|
86 | 83 | """Render the result of an execute call. Implementors may choose the |
|
87 | 84 | method of rendering. |
|
88 | 85 | For example, a notebook-style frontend might render a Chaco plot |
@@ -90,6 +87,7 b' class IFrontEnd(zi.Interface):' | |||
|
90 | 87 | |
|
91 | 88 | Parameters: |
|
92 | 89 | result : dict (result of IEngineBase.execute ) |
|
90 | blockID = result['blockID'] | |
|
93 | 91 | |
|
94 | 92 | Result: |
|
95 | 93 | Output of frontend rendering |
@@ -97,22 +95,24 b' class IFrontEnd(zi.Interface):' | |||
|
97 | 95 | |
|
98 | 96 | pass |
|
99 | 97 | |
|
100 |
def render_error( |
|
|
98 | def render_error(failure): | |
|
101 | 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 | 101 | implementations should return result when finished. |
|
102 | ||
|
103 | blockID = failure.blockID | |
|
104 | 104 | """ |
|
105 | 105 | |
|
106 | 106 | pass |
|
107 | 107 | |
|
108 | 108 | |
|
109 |
def input_prompt( |
|
|
109 | def input_prompt(number=None): | |
|
110 | 110 | """Returns the input prompt by subsituting into |
|
111 | 111 | self.input_prompt_template |
|
112 | 112 | """ |
|
113 | 113 | pass |
|
114 | 114 | |
|
115 |
def output_prompt(re |
|
|
115 | def output_prompt(number=None): | |
|
116 | 116 | """Returns the output prompt by subsituting into |
|
117 | 117 | self.output_prompt_template |
|
118 | 118 | """ |
@@ -180,15 +180,12 b' class FrontEndBase(object):' | |||
|
180 | 180 | self.history = history |
|
181 | 181 | |
|
182 | 182 | |
|
183 |
def input_prompt(self, |
|
|
183 | def input_prompt(self, number=None): | |
|
184 | 184 | """Returns the current input prompt |
|
185 | 185 | |
|
186 | 186 | It would be great to use ipython1.core.prompts.Prompt1 here |
|
187 | 187 | """ |
|
188 | ||
|
189 | result.setdefault('number','') | |
|
190 | ||
|
191 | return self.input_prompt_template.safe_substitute(result) | |
|
188 | return self.input_prompt_template.safe_substitute({'number':number}) | |
|
192 | 189 | |
|
193 | 190 | |
|
194 | 191 | def continuation_prompt(self): |
@@ -196,10 +193,10 b' class FrontEndBase(object):' | |||
|
196 | 193 | |
|
197 | 194 | return self.continuation_prompt_template.safe_substitute() |
|
198 | 195 | |
|
199 |
def output_prompt(self, re |
|
|
196 | def output_prompt(self, number=None): | |
|
200 | 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 | 202 | def is_complete(self, block): |
@@ -259,25 +256,33 b' class FrontEndBase(object):' | |||
|
259 | 256 | |
|
260 | 257 | d = self.engine.execute(block) |
|
261 | 258 | d.addCallback(self._add_history, block=block) |
|
262 |
d.add |
|
|
263 | d.addBoth(self.update_cell_prompt) | |
|
264 | d.addCallbacks(self.render_result, errback=self.render_error) | |
|
259 | d.addCallbacks(self._add_block_id_for_result, | |
|
260 | errback=self._add_block_id_for_failure, | |
|
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 | 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 | 271 | """Add the blockID to result or failure. Unfortunatley, we have to |
|
271 | 272 | treat failures differently than result dicts. |
|
272 | 273 | """ |
|
273 | 274 | |
|
274 | if(isinstance(result, Failure)): | |
|
275 | result.blockID = blockID | |
|
276 | else: | |
|
277 | result['blockID'] = blockID | |
|
275 | result['blockID'] = blockID | |
|
278 | 276 | |
|
279 | 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 | 286 | def _add_history(self, result, block=None): |
|
282 | 287 | """Add block to the history""" |
|
283 | 288 | |
@@ -313,20 +318,11 b' class FrontEndBase(object):' | |||
|
313 | 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 | 322 | """Subclass may override to update the input prompt for a block. |
|
318 | 323 | Since this method will be called as a |
|
319 | 324 | twisted.internet.defer.Deferred's callback, implementations should |
|
320 | 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 | 328 | return result |
@@ -344,7 +340,8 b' class FrontEndBase(object):' | |||
|
344 | 340 | def render_error(self, failure): |
|
345 | 341 | """Subclasses must override to render the failure. Since this method |
|
346 | 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 | 346 | return failure |
|
350 | 347 |
@@ -28,7 +28,7 b' class FrontEndCallbackChecker(frontendbase.FrontEndBase):' | |||
|
28 | 28 | self.renderResultCalled = False |
|
29 | 29 | self.renderErrorCalled = False |
|
30 | 30 | |
|
31 | def update_cell_prompt(self, result): | |
|
31 | def update_cell_prompt(self, result, blockID=None): | |
|
32 | 32 | self.updateCalled = True |
|
33 | 33 | return result |
|
34 | 34 |
|
1 | NO CONTENT: file was removed |
General Comments 0
You need to be logged in to leave comments.
Login now