##// END OF EJS Templates
merged
Barry Wark -
r1409:a9b79816 merge
parent child Browse files
Show More
@@ -1,505 +1,560 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 AsyncFrontEndBase
43 from IPython.frontend.frontendbase import AsyncFrontEndBase
44
44
45 from twisted.internet.threads import blockingCallFromThread
45 from twisted.internet.threads import blockingCallFromThread
46 from twisted.python.failure import Failure
46 from twisted.python.failure import Failure
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 = super(AutoreleasePoolWrappedThreadedEngineService,
65 except Exception,e:
65 self).wrapped_execute(msg, lines)
66 # This gives the following:
67 # et=exception class
68 # ev=exception class instance
69 # tb=traceback object
70 et,ev,tb = sys.exc_info()
71 # This call adds attributes to the exception value
72 et,ev,tb = self.shell.formatTraceback(et,ev,tb,msg)
73 # Add another attribute
74
75 # Create a new exception with the new attributes
76 e = et(ev._ipython_traceback_text)
77 e._ipython_engine_info = msg
78
79 # Re-raise
80 raise e
81 finally:
66 finally:
82 p.drain()
67 p.drain()
83
68
84 return result
69 return result
85
70
86 def execute(self, lines):
71
87 # Only import this if we are going to use this class
72
88 from twisted.internet import threads
73 class Cell(NSObject):
74 """
75 Representation of the prompts, input and output of a cell in the
76 frontend
77 """
78
79 blockNumber = objc.ivar().unsigned_long()
80 blockID = objc.ivar()
81 inputBlock = objc.ivar()
82 output = objc.ivar()
83
84
85
86 class CellBlock(object):
87 """
88 Storage for information about text ranges relating to a single cell
89 """
90
89
91
90 msg = {'engineid':self.id,
92 def __init__(self, inputPromptRange, inputRange=None, outputPromptRange=None,
91 'method':'execute',
93 outputRange=None):
92 'args':[lines]}
94 super(CellBlock, self).__init__()
95 self.inputPromptRange = inputPromptRange
96 self.inputRange = inputRange
97 self.outputPromptRange = outputPromptRange
98 self.outputRange = outputRange
99
100 def update_ranges_for_insertion(self, text, textRange):
101 """Update ranges for text insertion at textRange"""
102
103 for r in [self.inputPromptRange,self.inputRange,
104 self.outputPromptRange, self.outputRange]:
105 if(r == None):
106 continue
107 intersection = NSIntersectionRange(r,textRange)
108 if(intersection.length == 0): #ranges don't intersect
109 if r.location >= textRange.location:
110 r.location += len(text)
111 else: #ranges intersect
112 if(r.location > textRange.location):
113 offset = len(text) - intersection.length
114 r.length -= offset
115 r.location += offset
116 elif(r.location == textRange.location):
117 r.length += len(text) - intersection.length
118 else:
119 r.length -= intersection.length
120
121
122 def update_ranges_for_deletion(self, textRange):
123 """Update ranges for text deletion at textRange"""
93
124
94 d = threads.deferToThread(self.wrapped_execute, msg, lines)
125 for r in [self.inputPromptRange,self.inputRange,
95 d.addCallback(self.addIDToResult)
126 self.outputPromptRange, self.outputRange]:
96 return d
127 if(r==None):
128 continue
129 intersection = NSIntersectionRange(r, textRange)
130 if(intersection.length == 0): #ranges don't intersect
131 if r.location >= textRange.location:
132 r.location -= textRange.length
133 else: #ranges intersect
134 if(r.location > textRange.location):
135 offset = intersection.length
136 r.length -= offset
137 r.location += offset
138 elif(r.location == textRange.location):
139 r.length += intersection.length
140 else:
141 r.length -= intersection.length
142
143 def __repr__(self):
144 return 'CellBlock('+ str((self.inputPromptRange,
145 self.inputRange,
146 self.outputPromptRange,
147 self.outputRange)) + ')'
148
97
149
98
150
151
99 class IPythonCocoaController(NSObject, AsyncFrontEndBase):
152 class IPythonCocoaController(NSObject, AsyncFrontEndBase):
100 userNS = objc.ivar() #mirror of engine.user_ns (key=>str(value))
153 userNS = objc.ivar() #mirror of engine.user_ns (key=>str(value))
101 waitingForEngine = objc.ivar().bool()
154 waitingForEngine = objc.ivar().bool()
102 textView = objc.IBOutlet()
155 textView = objc.IBOutlet()
103
156
104 def init(self):
157 def init(self):
105 self = super(IPythonCocoaController, self).init()
158 self = super(IPythonCocoaController, self).init()
106 AsyncFrontEndBase.__init__(self,
159 AsyncFrontEndBase.__init__(self,
107 engine=AutoreleasePoolWrappedThreadedEngineService())
160 engine=AutoreleasePoolWrappedThreadedEngineService())
108 if(self != None):
161 if(self != None):
109 self._common_init()
162 self._common_init()
110
163
111 return self
164 return self
112
165
113 def _common_init(self):
166 def _common_init(self):
114 """_common_init"""
167 """_common_init"""
115
168
116 self.userNS = NSMutableDictionary.dictionary()
169 self.userNS = NSMutableDictionary.dictionary()
117 self.waitingForEngine = False
170 self.waitingForEngine = False
118
171
119 self.lines = {}
172 self.lines = {}
120 self.tabSpaces = 4
173 self.tabSpaces = 4
121 self.tabUsesSpaces = True
174 self.tabUsesSpaces = True
122 self.currentBlockID = self.next_block_ID()
175 self.currentBlockID = self.next_block_ID()
123 self.blockRanges = {} # blockID=>NSRange
176 self.blockRanges = {} # blockID=>CellBlock
124
177
125
178
126 def awakeFromNib(self):
179 def awakeFromNib(self):
127 """awakeFromNib"""
180 """awakeFromNib"""
128
181
129 self._common_init()
182 self._common_init()
130
183
131 # Start the IPython engine
184 # Start the IPython engine
132 self.engine.startService()
185 self.engine.startService()
133 NSLog('IPython engine started')
186 NSLog('IPython engine started')
134
187
135 # Register for app termination
188 # Register for app termination
136 nc = NSNotificationCenter.defaultCenter()
189 nc = NSNotificationCenter.defaultCenter()
137 nc.addObserver_selector_name_object_(
190 nc.addObserver_selector_name_object_(
138 self,
191 self,
139 'appWillTerminate:',
192 'appWillTerminate:',
140 NSApplicationWillTerminateNotification,
193 NSApplicationWillTerminateNotification,
141 None)
194 None)
142
195
143 self.textView.setDelegate_(self)
196 self.textView.setDelegate_(self)
144 self.textView.enclosingScrollView().setHasVerticalRuler_(True)
197 self.textView.enclosingScrollView().setHasVerticalRuler_(True)
145 r = NSRulerView.alloc().initWithScrollView_orientation_(
198 r = NSRulerView.alloc().initWithScrollView_orientation_(
146 self.textView.enclosingScrollView(),
199 self.textView.enclosingScrollView(),
147 NSVerticalRuler)
200 NSVerticalRuler)
148 self.verticalRulerView = r
201 self.verticalRulerView = r
149 self.verticalRulerView.setClientView_(self.textView)
202 self.verticalRulerView.setClientView_(self.textView)
150 self._start_cli_banner()
203 self._start_cli_banner()
204 self.start_new_block()
151
205
152
206
153 def appWillTerminate_(self, notification):
207 def appWillTerminate_(self, notification):
154 """appWillTerminate"""
208 """appWillTerminate"""
155
209
156 self.engine.stopService()
210 self.engine.stopService()
157
211
158
212
159 def complete(self, token):
213 def complete(self, token):
160 """Complete token in engine's user_ns
214 """Complete token in engine's user_ns
161
215
162 Parameters
216 Parameters
163 ----------
217 ----------
164 token : string
218 token : string
165
219
166 Result
220 Result
167 ------
221 ------
168 Deferred result of
222 Deferred result of
169 IPython.kernel.engineservice.IEngineBase.complete
223 IPython.kernel.engineservice.IEngineBase.complete
170 """
224 """
171
225
172 return self.engine.complete(token)
226 return self.engine.complete(token)
173
227
174
228
175 def execute(self, block, blockID=None):
229 def execute(self, block, blockID=None):
176 self.waitingForEngine = True
230 self.waitingForEngine = True
177 self.willChangeValueForKey_('commandHistory')
231 self.willChangeValueForKey_('commandHistory')
178 d = super(IPythonCocoaController, self).execute(block,
232 d = super(IPythonCocoaController, self).execute(block,
179 blockID)
233 blockID)
180 d.addBoth(self._engine_done)
234 d.addBoth(self._engine_done)
181 d.addCallback(self._update_user_ns)
235 d.addCallback(self._update_user_ns)
182
236
183 return d
237 return d
184
238
185
239
186 def push_(self, namespace):
240 def push_(self, namespace):
187 """Push dictionary of key=>values to python namespace"""
241 """Push dictionary of key=>values to python namespace"""
188
242
189 self.waitingForEngine = True
243 self.waitingForEngine = True
190 self.willChangeValueForKey_('commandHistory')
244 self.willChangeValueForKey_('commandHistory')
191 d = self.engine.push(namespace)
245 d = self.engine.push(namespace)
192 d.addBoth(self._engine_done)
246 d.addBoth(self._engine_done)
193 d.addCallback(self._update_user_ns)
247 d.addCallback(self._update_user_ns)
194
248
195
249
196 def pull_(self, keys):
250 def pull_(self, keys):
197 """Pull keys from python namespace"""
251 """Pull keys from python namespace"""
198
252
199 self.waitingForEngine = True
253 self.waitingForEngine = True
200 result = blockingCallFromThread(self.engine.pull, keys)
254 result = blockingCallFromThread(self.engine.pull, keys)
201 self.waitingForEngine = False
255 self.waitingForEngine = False
202
256
203 @objc.signature('v@:@I')
257 @objc.signature('v@:@I')
204 def executeFileAtPath_encoding_(self, path, encoding):
258 def executeFileAtPath_encoding_(self, path, encoding):
205 """Execute file at path in an empty namespace. Update the engine
259 """Execute file at path in an empty namespace. Update the engine
206 user_ns with the resulting locals."""
260 user_ns with the resulting locals."""
207
261
208 lines,err = NSString.stringWithContentsOfFile_encoding_error_(
262 lines,err = NSString.stringWithContentsOfFile_encoding_error_(
209 path,
263 path,
210 encoding,
264 encoding,
211 None)
265 None)
212 self.engine.execute(lines)
266 self.engine.execute(lines)
213
267
214
268
215 def _engine_done(self, x):
269 def _engine_done(self, x):
216 self.waitingForEngine = False
270 self.waitingForEngine = False
217 self.didChangeValueForKey_('commandHistory')
271 self.didChangeValueForKey_('commandHistory')
218 return x
272 return x
219
273
220 def _update_user_ns(self, result):
274 def _update_user_ns(self, result):
221 """Update self.userNS from self.engine's namespace"""
275 """Update self.userNS from self.engine's namespace"""
222 d = self.engine.keys()
276 d = self.engine.keys()
223 d.addCallback(self._get_engine_namespace_values_for_keys)
277 d.addCallback(self._get_engine_namespace_values_for_keys)
224
278
225 return result
279 return result
226
280
227
281
228 def _get_engine_namespace_values_for_keys(self, keys):
282 def _get_engine_namespace_values_for_keys(self, keys):
229 d = self.engine.pull(keys)
283 d = self.engine.pull(keys)
230 d.addCallback(self._store_engine_namespace_values, keys=keys)
284 d.addCallback(self._store_engine_namespace_values, keys=keys)
231
285
232
286
233 def _store_engine_namespace_values(self, values, keys=[]):
287 def _store_engine_namespace_values(self, values, keys=[]):
234 assert(len(values) == len(keys))
288 assert(len(values) == len(keys))
235 self.willChangeValueForKey_('userNS')
289 self.willChangeValueForKey_('userNS')
236 for (k,v) in zip(keys,values):
290 for (k,v) in zip(keys,values):
237 self.userNS[k] = saferepr(v)
291 self.userNS[k] = saferepr(v)
238 self.didChangeValueForKey_('userNS')
292 self.didChangeValueForKey_('userNS')
239
293
240
294
241 def update_cell_prompt(self, result, blockID=None):
295 def update_cell_prompt(self, result, blockID=None):
296 print self.blockRanges
242 if(isinstance(result, Failure)):
297 if(isinstance(result, Failure)):
243 self.insert_text(self.input_prompt(),
298 prompt = self.input_prompt()
244 textRange=NSMakeRange(self.blockRanges[blockID].location,0),
299
245 scrollToVisible=False
246 )
247 else:
300 else:
248 self.insert_text(self.input_prompt(number=result['number']),
301 prompt = self.input_prompt(number=result['number'])
249 textRange=NSMakeRange(self.blockRanges[blockID].location,0),
302
303 r = self.blockRanges[blockID].inputPromptRange
304 self.insert_text(prompt,
305 textRange=r,
250 scrollToVisible=False
306 scrollToVisible=False
251 )
307 )
252
308
253 return result
309 return result
254
310
255
311
256 def render_result(self, result):
312 def render_result(self, result):
257 blockID = result['blockID']
313 blockID = result['blockID']
258 inputRange = self.blockRanges[blockID]
314 inputRange = self.blockRanges[blockID].inputRange
259 del self.blockRanges[blockID]
315 del self.blockRanges[blockID]
260
316
261 #print inputRange,self.current_block_range()
317 #print inputRange,self.current_block_range()
262 self.insert_text('\n' +
318 self.insert_text('\n' +
263 self.output_prompt(number=result['number']) +
319 self.output_prompt(number=result['number']) +
264 result.get('display',{}).get('pprint','') +
320 result.get('display',{}).get('pprint','') +
265 '\n\n',
321 '\n\n',
266 textRange=NSMakeRange(inputRange.location+inputRange.length,
322 textRange=NSMakeRange(inputRange.location+inputRange.length,
267 0))
323 0))
268 return result
324 return result
269
325
270
326
271 def render_error(self, failure):
327 def render_error(self, failure):
328 print failure
329 blockID = failure.blockID
330 inputRange = self.blockRanges[blockID].inputRange
272 self.insert_text('\n' +
331 self.insert_text('\n' +
273 self.output_prompt() +
332 self.output_prompt() +
274 '\n' +
333 '\n' +
275 failure.getErrorMessage() +
334 failure.getErrorMessage() +
276 '\n\n')
335 '\n\n',
336 textRange=NSMakeRange(inputRange.location +
337 inputRange.length,
338 0))
277 self.start_new_block()
339 self.start_new_block()
278 return failure
340 return failure
279
341
280
342
281 def _start_cli_banner(self):
343 def _start_cli_banner(self):
282 """Print banner"""
344 """Print banner"""
283
345
284 banner = """IPython1 %s -- An enhanced Interactive Python.""" % \
346 banner = """IPython1 %s -- An enhanced Interactive Python.""" % \
285 IPython.__version__
347 IPython.__version__
286
348
287 self.insert_text(banner + '\n\n')
349 self.insert_text(banner + '\n\n')
288
350
289
351
290 def start_new_block(self):
352 def start_new_block(self):
291 """"""
353 """"""
292
354
293 self.currentBlockID = self.next_block_ID()
355 self.currentBlockID = self.next_block_ID()
356 self.blockRanges[self.currentBlockID] = self.new_cell_block()
357 self.insert_text(self.input_prompt(),
358 textRange=self.current_block_range().inputPromptRange)
294
359
295
360
296
361
297 def next_block_ID(self):
362 def next_block_ID(self):
298
363
299 return uuid.uuid4()
364 return uuid.uuid4()
300
365
366 def new_cell_block(self):
367 """A new CellBlock at the end of self.textView.textStorage()"""
368
369 return CellBlock(NSMakeRange(self.textView.textStorage().length(),
370 0), #len(self.input_prompt())),
371 NSMakeRange(self.textView.textStorage().length(),# + len(self.input_prompt()),
372 0))
373
374
301 def current_block_range(self):
375 def current_block_range(self):
302 return self.blockRanges.get(self.currentBlockID,
376 return self.blockRanges.get(self.currentBlockID,
303 NSMakeRange(self.textView.textStorage().length(),
377 self.new_cell_block())
304 0))
305
378
306 def current_block(self):
379 def current_block(self):
307 """The current block's text"""
380 """The current block's text"""
308
381
309 return self.text_for_range(self.current_block_range())
382 return self.text_for_range(self.current_block_range().inputRange)
310
383
311 def text_for_range(self, textRange):
384 def text_for_range(self, textRange):
312 """text_for_range"""
385 """text_for_range"""
313
386
314 ts = self.textView.textStorage()
387 ts = self.textView.textStorage()
315 return ts.string().substringWithRange_(textRange)
388 return ts.string().substringWithRange_(textRange)
316
389
317 def current_line(self):
390 def current_line(self):
318 block = self.text_for_range(self.current_block_range())
391 block = self.text_for_range(self.current_block_range().inputRange)
319 block = block.split('\n')
392 block = block.split('\n')
320 return block[-1]
393 return block[-1]
321
394
322
395
323 def insert_text(self, string=None, textRange=None, scrollToVisible=True):
396 def insert_text(self, string=None, textRange=None, scrollToVisible=True):
324 """Insert text into textView at textRange, updating blockRanges
397 """Insert text into textView at textRange, updating blockRanges
325 as necessary
398 as necessary
326 """
399 """
327
328 if(textRange == None):
400 if(textRange == None):
329 #range for end of text
401 #range for end of text
330 textRange = NSMakeRange(self.textView.textStorage().length(), 0)
402 textRange = NSMakeRange(self.textView.textStorage().length(), 0)
331
403
332 for r in self.blockRanges.itervalues():
333 intersection = NSIntersectionRange(r,textRange)
334 if(intersection.length == 0): #ranges don't intersect
335 if r.location >= textRange.location:
336 r.location += len(string)
337 else: #ranges intersect
338 if(r.location <= textRange.location):
339 assert(intersection.length == textRange.length)
340 r.length += textRange.length
341 else:
342 r.location += intersection.length
343
404
344 self.textView.replaceCharactersInRange_withString_(
405 self.textView.replaceCharactersInRange_withString_(
345 textRange, string)
406 textRange, string)
346 self.textView.setSelectedRange_(
407
347 NSMakeRange(textRange.location+len(string), 0))
408 for r in self.blockRanges.itervalues():
409 r.update_ranges_for_insertion(string, textRange)
410
411 self.textView.setSelectedRange_(textRange)
348 if(scrollToVisible):
412 if(scrollToVisible):
349 self.textView.scrollRangeToVisible_(textRange)
413 self.textView.scrollRangeToVisible_(textRange)
350
351
414
352
415
353
416
354 def replace_current_block_with_string(self, textView, string):
417 def replace_current_block_with_string(self, textView, string):
355 textView.replaceCharactersInRange_withString_(
418 textView.replaceCharactersInRange_withString_(
356 self.current_block_range(),
419 self.current_block_range().inputRange,
357 string)
420 string)
358 self.current_block_range().length = len(string)
421 self.current_block_range().inputRange.length = len(string)
359 r = NSMakeRange(textView.textStorage().length(), 0)
422 r = NSMakeRange(textView.textStorage().length(), 0)
360 textView.scrollRangeToVisible_(r)
423 textView.scrollRangeToVisible_(r)
361 textView.setSelectedRange_(r)
424 textView.setSelectedRange_(r)
362
425
363
426
364 def current_indent_string(self):
427 def current_indent_string(self):
365 """returns string for indent or None if no indent"""
428 """returns string for indent or None if no indent"""
366
429
367 return self._indent_for_block(self.current_block())
430 return self._indent_for_block(self.current_block())
368
431
369
432
370 def _indent_for_block(self, block):
433 def _indent_for_block(self, block):
371 lines = block.split('\n')
434 lines = block.split('\n')
372 if(len(lines) > 1):
435 if(len(lines) > 1):
373 currentIndent = len(lines[-1]) - len(lines[-1].lstrip())
436 currentIndent = len(lines[-1]) - len(lines[-1].lstrip())
374 if(currentIndent == 0):
437 if(currentIndent == 0):
375 currentIndent = self.tabSpaces
438 currentIndent = self.tabSpaces
376
439
377 if(self.tabUsesSpaces):
440 if(self.tabUsesSpaces):
378 result = ' ' * currentIndent
441 result = ' ' * currentIndent
379 else:
442 else:
380 result = '\t' * (currentIndent/self.tabSpaces)
443 result = '\t' * (currentIndent/self.tabSpaces)
381 else:
444 else:
382 result = None
445 result = None
383
446
384 return result
447 return result
385
448
386
449
387 # NSTextView delegate methods...
450 # NSTextView delegate methods...
388 def textView_doCommandBySelector_(self, textView, selector):
451 def textView_doCommandBySelector_(self, textView, selector):
389 assert(textView == self.textView)
452 assert(textView == self.textView)
390 NSLog("textView_doCommandBySelector_: "+selector)
453 NSLog("textView_doCommandBySelector_: "+selector)
391
454
392
455
393 if(selector == 'insertNewline:'):
456 if(selector == 'insertNewline:'):
394 indent = self.current_indent_string()
457 indent = self.current_indent_string()
395 if(indent):
458 if(indent):
396 line = indent + self.current_line()
459 line = indent + self.current_line()
397 else:
460 else:
398 line = self.current_line()
461 line = self.current_line()
399
462
400 if(self.is_complete(self.current_block())):
463 if(self.is_complete(self.current_block())):
401 self.execute(self.current_block(),
464 self.execute(self.current_block(),
402 blockID=self.currentBlockID)
465 blockID=self.currentBlockID)
403 self.start_new_block()
466 self.start_new_block()
404
467
405 return True
468 return True
406
469
407 return False
470 return False
408
471
409 elif(selector == 'moveUp:'):
472 elif(selector == 'moveUp:'):
410 prevBlock = self.get_history_previous(self.current_block())
473 prevBlock = self.get_history_previous(self.current_block())
411 if(prevBlock != None):
474 if(prevBlock != None):
412 self.replace_current_block_with_string(textView, prevBlock)
475 self.replace_current_block_with_string(textView, prevBlock)
413 else:
476 else:
414 NSBeep()
477 NSBeep()
415 return True
478 return True
416
479
417 elif(selector == 'moveDown:'):
480 elif(selector == 'moveDown:'):
418 nextBlock = self.get_history_next()
481 nextBlock = self.get_history_next()
419 if(nextBlock != None):
482 if(nextBlock != None):
420 self.replace_current_block_with_string(textView, nextBlock)
483 self.replace_current_block_with_string(textView, nextBlock)
421 else:
484 else:
422 NSBeep()
485 NSBeep()
423 return True
486 return True
424
487
425 elif(selector == 'moveToBeginningOfParagraph:'):
488 elif(selector == 'moveToBeginningOfParagraph:'):
426 textView.setSelectedRange_(NSMakeRange(
489 textView.setSelectedRange_(NSMakeRange(
427 self.current_block_range().location,
490 self.current_block_range().inputRange.location,
428 0))
491 0))
429 return True
492 return True
430 elif(selector == 'moveToEndOfParagraph:'):
493 elif(selector == 'moveToEndOfParagraph:'):
431 textView.setSelectedRange_(NSMakeRange(
494 textView.setSelectedRange_(NSMakeRange(
432 self.current_block_range().location + \
495 self.current_block_range().inputRange.location + \
433 self.current_block_range().length, 0))
496 self.current_block_range().inputRange.length, 0))
434 return True
497 return True
435 elif(selector == 'deleteToEndOfParagraph:'):
498 elif(selector == 'deleteToEndOfParagraph:'):
436 if(textView.selectedRange().location <= \
499 if(textView.selectedRange().location <= \
437 self.current_block_range().location):
500 self.current_block_range().location):
438 # Intersect the selected range with the current line range
501 raise NotImplemented()
439 if(self.current_block_range().length < 0):
440 self.blockRanges[self.currentBlockID].length = 0
441
442 r = NSIntersectionRange(textView.rangesForUserTextChange()[0],
443 self.current_block_range())
444
445 if(r.length > 0): #no intersection
446 textView.setSelectedRange_(r)
447
502
448 return False # don't actually handle the delete
503 return False # don't actually handle the delete
449
504
450 elif(selector == 'insertTab:'):
505 elif(selector == 'insertTab:'):
451 if(len(self.current_line().strip()) == 0): #only white space
506 if(len(self.current_line().strip()) == 0): #only white space
452 return False
507 return False
453 else:
508 else:
454 self.textView.complete_(self)
509 self.textView.complete_(self)
455 return True
510 return True
456
511
457 elif(selector == 'deleteBackward:'):
512 elif(selector == 'deleteBackward:'):
458 #if we're at the beginning of the current block, ignore
513 #if we're at the beginning of the current block, ignore
459 if(textView.selectedRange().location == \
514 if(textView.selectedRange().location == \
460 self.current_block_range().location):
515 self.current_block_range().inputRange.location):
461 return True
516 return True
462 else:
517 else:
463 self.current_block_range().length-=1
518 for r in self.blockRanges.itervalues():
519 deleteRange = textView.selectedRange
520 if(deleteRange.length == 0):
521 deleteRange.location -= 1
522 deleteRange.length = 1
523 r.update_ranges_for_deletion(deleteRange)
464 return False
524 return False
465 return False
525 return False
466
526
467
527
468 def textView_shouldChangeTextInRanges_replacementStrings_(self,
528 def textView_shouldChangeTextInRanges_replacementStrings_(self,
469 textView, ranges, replacementStrings):
529 textView, ranges, replacementStrings):
470 """
530 """
471 Delegate method for NSTextView.
531 Delegate method for NSTextView.
472
532
473 Refuse change text in ranges not at end, but make those changes at
533 Refuse change text in ranges not at end, but make those changes at
474 end.
534 end.
475 """
535 """
476
536
477 assert(len(ranges) == len(replacementStrings))
537 assert(len(ranges) == len(replacementStrings))
478 allow = True
538 allow = True
479 for r,s in zip(ranges, replacementStrings):
539 for r,s in zip(ranges, replacementStrings):
480 r = r.rangeValue()
540 r = r.rangeValue()
481 if(textView.textStorage().length() > 0 and
541 if(textView.textStorage().length() > 0 and
482 r.location < self.current_block_range().location):
542 r.location < self.current_block_range().inputRange.location):
483 self.insert_text(s)
543 self.insert_text(s)
484 allow = False
544 allow = False
485
486
487 self.blockRanges.setdefault(self.currentBlockID,
488 self.current_block_range()).length +=\
489 len(s)
490
545
491 return allow
546 return allow
492
547
493 def textView_completions_forPartialWordRange_indexOfSelectedItem_(self,
548 def textView_completions_forPartialWordRange_indexOfSelectedItem_(self,
494 textView, words, charRange, index):
549 textView, words, charRange, index):
495 try:
550 try:
496 ts = textView.textStorage()
551 ts = textView.textStorage()
497 token = ts.string().substringWithRange_(charRange)
552 token = ts.string().substringWithRange_(charRange)
498 completions = blockingCallFromThread(self.complete, token)
553 completions = blockingCallFromThread(self.complete, token)
499 except:
554 except:
500 completions = objc.nil
555 completions = objc.nil
501 NSBeep()
556 NSBeep()
502
557
503 return (completions,0)
558 return (completions,0)
504
559
505
560
@@ -1,407 +1,400 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 try:
27 try:
28 from zope.interface import Interface, Attribute, implements, classProvides
28 from zope.interface import Interface, Attribute, implements, classProvides
29 except ImportError:
29 except ImportError:
30 #zope.interface is not available
30 #zope.interface is not available
31 Interface = object
31 Interface = object
32 def Attribute(name, doc): pass
32 def Attribute(name, doc): pass
33 def implements(interface): pass
33 def implements(interface): pass
34 def classProvides(interface): pass
34 def classProvides(interface): pass
35
35
36 from IPython.kernel.core.history import FrontEndHistory
36 from IPython.kernel.core.history import FrontEndHistory
37 from IPython.kernel.core.util import Bunch
37 from IPython.kernel.core.util import Bunch
38 from IPython.kernel.engineservice import IEngineCore
38 from IPython.kernel.engineservice import IEngineCore
39
39
40 try:
41 from twisted.python.failure import Failure
42 except ImportError:
43 #Twisted not available
44 Failure = Exception
45
40
46 ##############################################################################
41 ##############################################################################
47 # TEMPORARY!!! fake configuration, while we decide whether to use tconfig or
42 # TEMPORARY!!! fake configuration, while we decide whether to use tconfig or
48 # not
43 # not
49
44
50 rc = Bunch()
45 rc = Bunch()
51 rc.prompt_in1 = r'In [$number]: '
46 rc.prompt_in1 = r'In [$number]: '
52 rc.prompt_in2 = r'...'
47 rc.prompt_in2 = r'...'
53 rc.prompt_out = r'Out [$number]: '
48 rc.prompt_out = r'Out [$number]: '
54
49
55 ##############################################################################
50 ##############################################################################
56
51
57 class IFrontEndFactory(Interface):
52 class IFrontEndFactory(Interface):
58 """Factory interface for frontends."""
53 """Factory interface for frontends."""
59
54
60 def __call__(engine=None, history=None):
55 def __call__(engine=None, history=None):
61 """
56 """
62 Parameters:
57 Parameters:
63 interpreter : IPython.kernel.engineservice.IEngineCore
58 interpreter : IPython.kernel.engineservice.IEngineCore
64 """
59 """
65
60
66 pass
61 pass
67
62
68
63
69
64
70 class IFrontEnd(Interface):
65 class IFrontEnd(Interface):
71 """Interface for frontends. All methods return t.i.d.Deferred"""
66 """Interface for frontends. All methods return t.i.d.Deferred"""
72
67
73 Attribute("input_prompt_template", "string.Template instance\
68 Attribute("input_prompt_template", "string.Template instance\
74 substituteable with execute result.")
69 substituteable with execute result.")
75 Attribute("output_prompt_template", "string.Template instance\
70 Attribute("output_prompt_template", "string.Template instance\
76 substituteable with execute result.")
71 substituteable with execute result.")
77 Attribute("continuation_prompt_template", "string.Template instance\
72 Attribute("continuation_prompt_template", "string.Template instance\
78 substituteable with execute result.")
73 substituteable with execute result.")
79
74
80 def update_cell_prompt(result, blockID=None):
75 def update_cell_prompt(result, blockID=None):
81 """Subclass may override to update the input prompt for a block.
76 """Subclass may override to update the input prompt for a block.
82 Since this method will be called as a
77 Since this method will be called as a
83 twisted.internet.defer.Deferred's callback/errback,
78 twisted.internet.defer.Deferred's callback/errback,
84 implementations should return result when finished.
79 implementations should return result when finished.
85
80
86 Result is a result dict in case of success, and a
81 Result is a result dict in case of success, and a
87 twisted.python.util.failure.Failure in case of an error
82 twisted.python.util.failure.Failure in case of an error
88 """
83 """
89
84
90 pass
85 pass
91
86
92
87
93 def render_result(result):
88 def render_result(result):
94 """Render the result of an execute call. Implementors may choose the
89 """Render the result of an execute call. Implementors may choose the
95 method of rendering.
90 method of rendering.
96 For example, a notebook-style frontend might render a Chaco plot
91 For example, a notebook-style frontend might render a Chaco plot
97 inline.
92 inline.
98
93
99 Parameters:
94 Parameters:
100 result : dict (result of IEngineBase.execute )
95 result : dict (result of IEngineBase.execute )
101 blockID = result['blockID']
96 blockID = result['blockID']
102
97
103 Result:
98 Result:
104 Output of frontend rendering
99 Output of frontend rendering
105 """
100 """
106
101
107 pass
102 pass
108
103
109 def render_error(failure):
104 def render_error(failure):
110 """Subclasses must override to render the failure. Since this method
105 """Subclasses must override to render the failure. Since this method
111 will be called as a twisted.internet.defer.Deferred's callback,
106 will be called as a twisted.internet.defer.Deferred's callback,
112 implementations should return result when finished.
107 implementations should return result when finished.
113
108
114 blockID = failure.blockID
109 blockID = failure.blockID
115 """
110 """
116
111
117 pass
112 pass
118
113
119
114
120 def input_prompt(number=''):
115 def input_prompt(number=''):
121 """Returns the input prompt by subsituting into
116 """Returns the input prompt by subsituting into
122 self.input_prompt_template
117 self.input_prompt_template
123 """
118 """
124 pass
119 pass
125
120
126 def output_prompt(number=''):
121 def output_prompt(number=''):
127 """Returns the output prompt by subsituting into
122 """Returns the output prompt by subsituting into
128 self.output_prompt_template
123 self.output_prompt_template
129 """
124 """
130
125
131 pass
126 pass
132
127
133 def continuation_prompt():
128 def continuation_prompt():
134 """Returns the continuation prompt by subsituting into
129 """Returns the continuation prompt by subsituting into
135 self.continuation_prompt_template
130 self.continuation_prompt_template
136 """
131 """
137
132
138 pass
133 pass
139
134
140 def is_complete(block):
135 def is_complete(block):
141 """Returns True if block is complete, False otherwise."""
136 """Returns True if block is complete, False otherwise."""
142
137
143 pass
138 pass
144
139
145 def compile_ast(block):
140 def compile_ast(block):
146 """Compiles block to an _ast.AST"""
141 """Compiles block to an _ast.AST"""
147
142
148 pass
143 pass
149
144
150
145
151 def get_history_previous(currentBlock):
146 def get_history_previous(currentBlock):
152 """Returns the block previous in the history. Saves currentBlock if
147 """Returns the block previous in the history. Saves currentBlock if
153 the history_cursor is currently at the end of the input history"""
148 the history_cursor is currently at the end of the input history"""
154 pass
149 pass
155
150
156 def get_history_next():
151 def get_history_next():
157 """Returns the next block in the history."""
152 """Returns the next block in the history."""
158
153
159 pass
154 pass
160
155
161
156
162 class FrontEndBase(object):
157 class FrontEndBase(object):
163 """
158 """
164 FrontEndBase manages the state tasks for a CLI frontend:
159 FrontEndBase manages the state tasks for a CLI frontend:
165 - Input and output history management
160 - Input and output history management
166 - Input/continuation and output prompt generation
161 - Input/continuation and output prompt generation
167
162
168 Some issues (due to possibly unavailable engine):
163 Some issues (due to possibly unavailable engine):
169 - How do we get the current cell number for the engine?
164 - How do we get the current cell number for the engine?
170 - How do we handle completions?
165 - How do we handle completions?
171 """
166 """
172
167
173 history_cursor = 0
168 history_cursor = 0
174
169
175 current_indent_level = 0
170 current_indent_level = 0
176
171
177
172
178 input_prompt_template = string.Template(rc.prompt_in1)
173 input_prompt_template = string.Template(rc.prompt_in1)
179 output_prompt_template = string.Template(rc.prompt_out)
174 output_prompt_template = string.Template(rc.prompt_out)
180 continuation_prompt_template = string.Template(rc.prompt_in2)
175 continuation_prompt_template = string.Template(rc.prompt_in2)
181
176
182 def __init__(self, shell=None, history=None):
177 def __init__(self, shell=None, history=None):
183 self.shell = shell
178 self.shell = shell
184 if history is None:
179 if history is None:
185 self.history = FrontEndHistory(input_cache=[''])
180 self.history = FrontEndHistory(input_cache=[''])
186 else:
181 else:
187 self.history = history
182 self.history = history
188
183
189
184
190 def input_prompt(self, number=''):
185 def input_prompt(self, number=''):
191 """Returns the current input prompt
186 """Returns the current input prompt
192
187
193 It would be great to use ipython1.core.prompts.Prompt1 here
188 It would be great to use ipython1.core.prompts.Prompt1 here
194 """
189 """
195 return self.input_prompt_template.safe_substitute({'number':number})
190 return self.input_prompt_template.safe_substitute({'number':number})
196
191
197
192
198 def continuation_prompt(self):
193 def continuation_prompt(self):
199 """Returns the current continuation prompt"""
194 """Returns the current continuation prompt"""
200
195
201 return self.continuation_prompt_template.safe_substitute()
196 return self.continuation_prompt_template.safe_substitute()
202
197
203 def output_prompt(self, number=''):
198 def output_prompt(self, number=''):
204 """Returns the output prompt for result"""
199 """Returns the output prompt for result"""
205
200
206 return self.output_prompt_template.safe_substitute({'number':number})
201 return self.output_prompt_template.safe_substitute({'number':number})
207
202
208
203
209 def is_complete(self, block):
204 def is_complete(self, block):
210 """Determine if block is complete.
205 """Determine if block is complete.
211
206
212 Parameters
207 Parameters
213 block : string
208 block : string
214
209
215 Result
210 Result
216 True if block can be sent to the engine without compile errors.
211 True if block can be sent to the engine without compile errors.
217 False otherwise.
212 False otherwise.
218 """
213 """
219
214
220 try:
215 try:
221 ast = self.compile_ast(block)
216 ast = self.compile_ast(block)
222 except:
217 except:
223 return False
218 return False
224
219
225 lines = block.split('\n')
220 lines = block.split('\n')
226 return (len(lines)==1 or str(lines[-1])=='')
221 return (len(lines)==1 or str(lines[-1])=='')
227
222
228
223
229 def compile_ast(self, block):
224 def compile_ast(self, block):
230 """Compile block to an AST
225 """Compile block to an AST
231
226
232 Parameters:
227 Parameters:
233 block : str
228 block : str
234
229
235 Result:
230 Result:
236 AST
231 AST
237
232
238 Throws:
233 Throws:
239 Exception if block cannot be compiled
234 Exception if block cannot be compiled
240 """
235 """
241
236
242 return compile(block, "<string>", "exec", _ast.PyCF_ONLY_AST)
237 return compile(block, "<string>", "exec", _ast.PyCF_ONLY_AST)
243
238
244
239
245 def execute(self, block, blockID=None):
240 def execute(self, block, blockID=None):
246 """Execute the block and return the result.
241 """Execute the block and return the result.
247
242
248 Parameters:
243 Parameters:
249 block : {str, AST}
244 block : {str, AST}
250 blockID : any
245 blockID : any
251 Caller may provide an ID to identify this block.
246 Caller may provide an ID to identify this block.
252 result['blockID'] := blockID
247 result['blockID'] := blockID
253
248
254 Result:
249 Result:
255 Deferred result of self.interpreter.execute
250 Deferred result of self.interpreter.execute
256 """
251 """
257
252
258 if(not self.is_complete(block)):
253 if(not self.is_complete(block)):
259 raise Exception("Block is not compilable")
254 raise Exception("Block is not compilable")
260
255
261 if(blockID == None):
256 if(blockID == None):
262 blockID = uuid.uuid4() #random UUID
257 blockID = uuid.uuid4() #random UUID
263
258
264 try:
259 try:
265 result = self.shell.execute(block)
260 result = self.shell.execute(block)
266 except Exception,e:
261 except Exception,e:
267 e = self._add_block_id_for_failure(e, blockID=blockID)
262 e = self._add_block_id_for_failure(e, blockID=blockID)
268 e = self.update_cell_prompt(e, blockID=blockID)
263 e = self.update_cell_prompt(e, blockID=blockID)
269 e = self.render_error(e)
264 e = self.render_error(e)
270 else:
265 else:
271 result = self._add_block_id_for_result(result, blockID=blockID)
266 result = self._add_block_id_for_result(result, blockID=blockID)
272 result = self.update_cell_prompt(result, blockID=blockID)
267 result = self.update_cell_prompt(result, blockID=blockID)
273 result = self.render_result(result)
268 result = self.render_result(result)
274
269
275 return result
270 return result
276
271
277
272
278 def _add_block_id_for_result(self, result, blockID):
273 def _add_block_id_for_result(self, result, blockID):
279 """Add the blockID to result or failure. Unfortunatley, we have to
274 """Add the blockID to result or failure. Unfortunatley, we have to
280 treat failures differently than result dicts.
275 treat failures differently than result dicts.
281 """
276 """
282
277
283 result['blockID'] = blockID
278 result['blockID'] = blockID
284
279
285 return result
280 return result
286
281
287 def _add_block_id_for_failure(self, failure, blockID):
282 def _add_block_id_for_failure(self, failure, blockID):
288 """_add_block_id_for_failure"""
283 """_add_block_id_for_failure"""
289
290 failure.blockID = blockID
284 failure.blockID = blockID
291 return failure
285 return failure
292
286
293
287
294 def _add_history(self, result, block=None):
288 def _add_history(self, result, block=None):
295 """Add block to the history"""
289 """Add block to the history"""
296
290
297 assert(block != None)
291 assert(block != None)
298 self.history.add_items([block])
292 self.history.add_items([block])
299 self.history_cursor += 1
293 self.history_cursor += 1
300
294
301 return result
295 return result
302
296
303
297
304 def get_history_previous(self, currentBlock):
298 def get_history_previous(self, currentBlock):
305 """ Returns previous history string and decrement history cursor.
299 """ Returns previous history string and decrement history cursor.
306 """
300 """
307 command = self.history.get_history_item(self.history_cursor - 1)
301 command = self.history.get_history_item(self.history_cursor - 1)
308
302
309 if command is not None:
303 if command is not None:
310 if(self.history_cursor == len(self.history.input_cache)):
304 if(self.history_cursor == len(self.history.input_cache)):
311 self.history.input_cache[self.history_cursor] = currentBlock
305 self.history.input_cache[self.history_cursor] = currentBlock
312 self.history_cursor -= 1
306 self.history_cursor -= 1
313 return command
307 return command
314
308
315
309
316 def get_history_next(self):
310 def get_history_next(self):
317 """ Returns next history string and increment history cursor.
311 """ Returns next history string and increment history cursor.
318 """
312 """
319 command = self.history.get_history_item(self.history_cursor+1)
313 command = self.history.get_history_item(self.history_cursor+1)
320
314
321 if command is not None:
315 if command is not None:
322 self.history_cursor += 1
316 self.history_cursor += 1
323 return command
317 return command
324
318
325 ###
319 ###
326 # Subclasses probably want to override these methods...
320 # Subclasses probably want to override these methods...
327 ###
321 ###
328
322
329 def update_cell_prompt(self, result, blockID=None):
323 def update_cell_prompt(self, result, blockID=None):
330 """Subclass may override to update the input prompt for a block.
324 """Subclass may override to update the input prompt for a block.
331 Since this method will be called as a
325 Since this method will be called as a
332 twisted.internet.defer.Deferred's callback, implementations should
326 twisted.internet.defer.Deferred's callback, implementations should
333 return result when finished.
327 return result when finished.
334 """
328 """
335
329
336 return result
330 return result
337
331
338
332
339 def render_result(self, result):
333 def render_result(self, result):
340 """Subclasses must override to render result. Since this method will
334 """Subclasses must override to render result. Since this method will
341 be called as a twisted.internet.defer.Deferred's callback,
335 be called as a twisted.internet.defer.Deferred's callback,
342 implementations should return result when finished.
336 implementations should return result when finished.
343 """
337 """
344
338
345 return result
339 return result
346
340
347
341
348 def render_error(self, failure):
342 def render_error(self, failure):
349 """Subclasses must override to render the failure. Since this method
343 """Subclasses must override to render the failure. Since this method
350 will be called as a twisted.internet.defer.Deferred's callback,
344 will be called as a twisted.internet.defer.Deferred's callback,
351 implementations should return result when finished.
345 implementations should return result when finished.
352 """
346 """
353
347
354 return failure
348 return failure
355
349
356
350
357
351
358 class AsyncFrontEndBase(FrontEndBase):
352 class AsyncFrontEndBase(FrontEndBase):
359 """
353 """
360 Overrides FrontEndBase to wrap execute in a deferred result.
354 Overrides FrontEndBase to wrap execute in a deferred result.
361 All callbacks are made as callbacks on the deferred result.
355 All callbacks are made as callbacks on the deferred result.
362 """
356 """
363
357
364 implements(IFrontEnd)
358 implements(IFrontEnd)
365 classProvides(IFrontEndFactory)
359 classProvides(IFrontEndFactory)
366
360
367 def __init__(self, engine=None, history=None):
361 def __init__(self, engine=None, history=None):
368 assert(engine==None or IEngineCore.providedBy(engine))
362 assert(engine==None or IEngineCore.providedBy(engine))
369 self.engine = IEngineCore(engine)
363 self.engine = IEngineCore(engine)
370 if history is None:
364 if history is None:
371 self.history = FrontEndHistory(input_cache=[''])
365 self.history = FrontEndHistory(input_cache=[''])
372 else:
366 else:
373 self.history = history
367 self.history = history
374
368
375
369
376 def execute(self, block, blockID=None):
370 def execute(self, block, blockID=None):
377 """Execute the block and return the deferred result.
371 """Execute the block and return the deferred result.
378
372
379 Parameters:
373 Parameters:
380 block : {str, AST}
374 block : {str, AST}
381 blockID : any
375 blockID : any
382 Caller may provide an ID to identify this block.
376 Caller may provide an ID to identify this block.
383 result['blockID'] := blockID
377 result['blockID'] := blockID
384
378
385 Result:
379 Result:
386 Deferred result of self.interpreter.execute
380 Deferred result of self.interpreter.execute
387 """
381 """
388
382
389 if(not self.is_complete(block)):
383 if(not self.is_complete(block)):
384 from twisted.python.failure import Failure
390 return Failure(Exception("Block is not compilable"))
385 return Failure(Exception("Block is not compilable"))
391
386
392 if(blockID == None):
387 if(blockID == None):
393 blockID = uuid.uuid4() #random UUID
388 blockID = uuid.uuid4() #random UUID
394
389
395 d = self.engine.execute(block)
390 d = self.engine.execute(block)
396 d.addCallback(self._add_history, block=block)
391 d.addCallback(self._add_history, block=block)
397 d.addCallbacks(self._add_block_id_for_result,
392 d.addCallback(self._add_block_id_for_result, blockID)
398 errback=self._add_block_id_for_failure,
393 d.addErrback(self._add_block_id_for_failure, blockID)
399 callbackArgs=(blockID,),
400 errbackArgs=(blockID,))
401 d.addBoth(self.update_cell_prompt, blockID=blockID)
394 d.addBoth(self.update_cell_prompt, blockID=blockID)
402 d.addCallbacks(self.render_result,
395 d.addCallbacks(self.render_result,
403 errback=self.render_error)
396 errback=self.render_error)
404
397
405 return d
398 return d
406
399
407
400
General Comments 0
You need to be logged in to leave comments. Login now