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