Show More
@@ -70,6 +70,72 b' class AutoreleasePoolWrappedThreadedEngineService(ThreadedEngineService):' | |||
|
70 | 70 | |
|
71 | 71 | |
|
72 | 72 | |
|
73 | class CellBlock(object): | |
|
74 | """ | |
|
75 | Storage for information about text ranges relating to a single cell | |
|
76 | """ | |
|
77 | ||
|
78 | ||
|
79 | def __init__(self, inputPromptRange, inputRange=None, outputPromptRange=None, | |
|
80 | outputRange=None): | |
|
81 | super(CellBlock, self).__init__() | |
|
82 | self.inputPromptRange = inputPromptRange | |
|
83 | self.inputRange = inputRange | |
|
84 | self.outputPromptRange = outputPromptRange | |
|
85 | self.outputRange = outputRange | |
|
86 | ||
|
87 | def update_ranges_for_insertion(self, text, textRange): | |
|
88 | """Update ranges for text insertion at textRange""" | |
|
89 | ||
|
90 | for r in [self.inputPromptRange,self.inputRange, | |
|
91 | self.outputPromptRange, self.outputRange]: | |
|
92 | if(r == None): | |
|
93 | continue | |
|
94 | intersection = NSIntersectionRange(r,textRange) | |
|
95 | if(intersection.length == 0): #ranges don't intersect | |
|
96 | if r.location >= textRange.location: | |
|
97 | r.location += len(text) | |
|
98 | else: #ranges intersect | |
|
99 | if(r.location > textRange.location): | |
|
100 | offset = len(text) - intersection.length | |
|
101 | r.length -= offset | |
|
102 | r.location += offset | |
|
103 | elif(r.location == textRange.location): | |
|
104 | r.length += len(text) - intersection.length | |
|
105 | else: | |
|
106 | r.length -= intersection.length | |
|
107 | ||
|
108 | ||
|
109 | def update_ranges_for_deletion(self, textRange): | |
|
110 | """Update ranges for text deletion at textRange""" | |
|
111 | ||
|
112 | for r in [self.inputPromptRange,self.inputRange, | |
|
113 | self.outputPromptRange, self.outputRange]: | |
|
114 | if(r==None): | |
|
115 | continue | |
|
116 | intersection = NSIntersectionRange(r, textRange) | |
|
117 | if(intersection.length == 0): #ranges don't intersect | |
|
118 | if r.location >= textRange.location: | |
|
119 | r.location -= textRange.length | |
|
120 | else: #ranges intersect | |
|
121 | if(r.location > textRange.location): | |
|
122 | offset = intersection.length | |
|
123 | r.length -= offset | |
|
124 | r.location += offset | |
|
125 | elif(r.location == textRange.location): | |
|
126 | r.length += intersection.length | |
|
127 | else: | |
|
128 | r.length -= intersection.length | |
|
129 | ||
|
130 | def __repr__(self): | |
|
131 | return 'CellBlock('+ str((self.inputPromptRange, | |
|
132 | self.inputRange, | |
|
133 | self.outputPromptRange, | |
|
134 | self.outputRange)) + ')' | |
|
135 | ||
|
136 | ||
|
137 | ||
|
138 | ||
|
73 | 139 | class IPythonCocoaController(NSObject, AsyncFrontEndBase): |
|
74 | 140 | userNS = objc.ivar() #mirror of engine.user_ns (key=>str(value)) |
|
75 | 141 | waitingForEngine = objc.ivar().bool() |
@@ -94,7 +160,7 b' class IPythonCocoaController(NSObject, AsyncFrontEndBase):' | |||
|
94 | 160 | self.tabSpaces = 4 |
|
95 | 161 | self.tabUsesSpaces = True |
|
96 | 162 | self.currentBlockID = self.next_block_ID() |
|
97 |
self.blockRanges = {} # blockID=> |
|
|
163 | self.blockRanges = {} # blockID=>CellBlock | |
|
98 | 164 | |
|
99 | 165 | |
|
100 | 166 | def awakeFromNib(self): |
@@ -122,6 +188,7 b' class IPythonCocoaController(NSObject, AsyncFrontEndBase):' | |||
|
122 | 188 | self.verticalRulerView = r |
|
123 | 189 | self.verticalRulerView.setClientView_(self.textView) |
|
124 | 190 | self._start_cli_banner() |
|
191 | self.start_new_block() | |
|
125 | 192 | |
|
126 | 193 | |
|
127 | 194 | def appWillTerminate_(self, notification): |
@@ -213,14 +280,16 b' class IPythonCocoaController(NSObject, AsyncFrontEndBase):' | |||
|
213 | 280 | |
|
214 | 281 | |
|
215 | 282 | def update_cell_prompt(self, result, blockID=None): |
|
283 | print self.blockRanges | |
|
216 | 284 | if(isinstance(result, Failure)): |
|
217 |
|
|
|
218 | textRange=NSMakeRange(self.blockRanges[blockID].location,0), | |
|
219 | scrollToVisible=False | |
|
220 | ) | |
|
285 | prompt = self.input_prompt() | |
|
286 | ||
|
221 | 287 | else: |
|
222 |
|
|
|
223 | textRange=NSMakeRange(self.blockRanges[blockID].location,0), | |
|
288 | prompt = self.input_prompt(number=result['number']) | |
|
289 | ||
|
290 | r = self.blockRanges[blockID].inputPromptRange | |
|
291 | self.insert_text(prompt, | |
|
292 | textRange=r, | |
|
224 | 293 | scrollToVisible=False |
|
225 | 294 | ) |
|
226 | 295 | |
@@ -229,7 +298,7 b' class IPythonCocoaController(NSObject, AsyncFrontEndBase):' | |||
|
229 | 298 | |
|
230 | 299 | def render_result(self, result): |
|
231 | 300 | blockID = result['blockID'] |
|
232 | inputRange = self.blockRanges[blockID] | |
|
301 | inputRange = self.blockRanges[blockID].inputRange | |
|
233 | 302 | del self.blockRanges[blockID] |
|
234 | 303 | |
|
235 | 304 | #print inputRange,self.current_block_range() |
@@ -243,11 +312,17 b' class IPythonCocoaController(NSObject, AsyncFrontEndBase):' | |||
|
243 | 312 | |
|
244 | 313 | |
|
245 | 314 | def render_error(self, failure): |
|
315 | print failure | |
|
316 | blockID = failure.blockID | |
|
317 | inputRange = self.blockRanges[blockID].inputRange | |
|
246 | 318 | self.insert_text('\n' + |
|
247 | 319 | self.output_prompt() + |
|
248 | 320 | '\n' + |
|
249 | 321 | failure.getErrorMessage() + |
|
250 |
'\n\n' |
|
|
322 | '\n\n', | |
|
323 | textRange=NSMakeRange(inputRange.location + | |
|
324 | inputRange.length, | |
|
325 | 0)) | |
|
251 | 326 | self.start_new_block() |
|
252 | 327 | return failure |
|
253 | 328 | |
@@ -265,6 +340,9 b' class IPythonCocoaController(NSObject, AsyncFrontEndBase):' | |||
|
265 | 340 | """""" |
|
266 | 341 | |
|
267 | 342 | self.currentBlockID = self.next_block_ID() |
|
343 | self.blockRanges[self.currentBlockID] = self.new_cell_block() | |
|
344 | self.insert_text(self.input_prompt(), | |
|
345 | textRange=self.current_block_range().inputPromptRange) | |
|
268 | 346 | |
|
269 | 347 | |
|
270 | 348 | |
@@ -272,15 +350,23 b' class IPythonCocoaController(NSObject, AsyncFrontEndBase):' | |||
|
272 | 350 | |
|
273 | 351 | return uuid.uuid4() |
|
274 | 352 | |
|
353 | def new_cell_block(self): | |
|
354 | """A new CellBlock at the end of self.textView.textStorage()""" | |
|
355 | ||
|
356 | return CellBlock(NSMakeRange(self.textView.textStorage().length()-1, | |
|
357 | 0), #len(self.input_prompt())), | |
|
358 | NSMakeRange(self.textView.textStorage().length()-1,# + len(self.input_prompt()), | |
|
359 | 0)) | |
|
360 | ||
|
361 | ||
|
275 | 362 | def current_block_range(self): |
|
276 | 363 | return self.blockRanges.get(self.currentBlockID, |
|
277 |
|
|
|
278 | 0)) | |
|
364 | self.new_cell_block()) | |
|
279 | 365 | |
|
280 | 366 | def current_block(self): |
|
281 | 367 | """The current block's text""" |
|
282 | 368 | |
|
283 | return self.text_for_range(self.current_block_range()) | |
|
369 | return self.text_for_range(self.current_block_range().inputRange) | |
|
284 | 370 | |
|
285 | 371 | def text_for_range(self, textRange): |
|
286 | 372 | """text_for_range""" |
@@ -289,7 +375,7 b' class IPythonCocoaController(NSObject, AsyncFrontEndBase):' | |||
|
289 | 375 | return ts.string().substringWithRange_(textRange) |
|
290 | 376 | |
|
291 | 377 | def current_line(self): |
|
292 | block = self.text_for_range(self.current_block_range()) | |
|
378 | block = self.text_for_range(self.current_block_range().inputRange) | |
|
293 | 379 | block = block.split('\n') |
|
294 | 380 | return block[-1] |
|
295 | 381 | |
@@ -298,38 +384,29 b' class IPythonCocoaController(NSObject, AsyncFrontEndBase):' | |||
|
298 | 384 | """Insert text into textView at textRange, updating blockRanges |
|
299 | 385 | as necessary |
|
300 | 386 | """ |
|
301 | ||
|
302 | 387 | if(textRange == None): |
|
303 | 388 | #range for end of text |
|
304 | 389 | textRange = NSMakeRange(self.textView.textStorage().length(), 0) |
|
305 | 390 | |
|
306 | for r in self.blockRanges.itervalues(): | |
|
307 | intersection = NSIntersectionRange(r,textRange) | |
|
308 | if(intersection.length == 0): #ranges don't intersect | |
|
309 | if r.location >= textRange.location: | |
|
310 | r.location += len(string) | |
|
311 | else: #ranges intersect | |
|
312 | if(r.location <= textRange.location): | |
|
313 | assert(intersection.length == textRange.length) | |
|
314 | r.length += textRange.length | |
|
315 | else: | |
|
316 | r.location += intersection.length | |
|
317 | 391 | |
|
392 | print textRange,string,self.textView.textStorage(),self.textView.textStorage().length() | |
|
318 | 393 | self.textView.replaceCharactersInRange_withString_( |
|
319 | 394 | textRange, string) |
|
320 | self.textView.setSelectedRange_( | |
|
321 | NSMakeRange(textRange.location+len(string), 0)) | |
|
395 | ||
|
396 | for r in self.blockRanges.itervalues(): | |
|
397 | r.update_ranges_for_insertion(string, textRange) | |
|
398 | ||
|
399 | self.textView.setSelectedRange_(textRange) | |
|
322 | 400 | if(scrollToVisible): |
|
323 | 401 | self.textView.scrollRangeToVisible_(textRange) |
|
324 | ||
|
325 | 402 | |
|
326 | 403 | |
|
327 | 404 | |
|
328 | 405 | def replace_current_block_with_string(self, textView, string): |
|
329 | 406 | textView.replaceCharactersInRange_withString_( |
|
330 |
|
|
|
331 |
|
|
|
332 | self.current_block_range().length = len(string) | |
|
407 | self.current_block_range().inputRange, | |
|
408 | string) | |
|
409 | self.current_block_range().inputRange.length = len(string) | |
|
333 | 410 | r = NSMakeRange(textView.textStorage().length(), 0) |
|
334 | 411 | textView.scrollRangeToVisible_(r) |
|
335 | 412 | textView.setSelectedRange_(r) |
@@ -398,26 +475,18 b' class IPythonCocoaController(NSObject, AsyncFrontEndBase):' | |||
|
398 | 475 | |
|
399 | 476 | elif(selector == 'moveToBeginningOfParagraph:'): |
|
400 | 477 | textView.setSelectedRange_(NSMakeRange( |
|
401 |
|
|
|
402 |
|
|
|
478 | self.current_block_range().inputRange.location, | |
|
479 | 0)) | |
|
403 | 480 | return True |
|
404 | 481 | elif(selector == 'moveToEndOfParagraph:'): |
|
405 | 482 | textView.setSelectedRange_(NSMakeRange( |
|
406 |
|
|
|
407 |
|
|
|
483 | self.current_block_range().inputRange.location + \ | |
|
484 | self.current_block_range().inputRange.length, 0)) | |
|
408 | 485 | return True |
|
409 | 486 | elif(selector == 'deleteToEndOfParagraph:'): |
|
410 | 487 | if(textView.selectedRange().location <= \ |
|
411 | 488 | self.current_block_range().location): |
|
412 | # Intersect the selected range with the current line range | |
|
413 | if(self.current_block_range().length < 0): | |
|
414 | self.blockRanges[self.currentBlockID].length = 0 | |
|
415 | ||
|
416 | r = NSIntersectionRange(textView.rangesForUserTextChange()[0], | |
|
417 | self.current_block_range()) | |
|
418 | ||
|
419 | if(r.length > 0): #no intersection | |
|
420 | textView.setSelectedRange_(r) | |
|
489 | raise NotImplemented() | |
|
421 | 490 | |
|
422 | 491 | return False # don't actually handle the delete |
|
423 | 492 | |
@@ -431,10 +500,15 b' class IPythonCocoaController(NSObject, AsyncFrontEndBase):' | |||
|
431 | 500 | elif(selector == 'deleteBackward:'): |
|
432 | 501 | #if we're at the beginning of the current block, ignore |
|
433 | 502 | if(textView.selectedRange().location == \ |
|
434 | self.current_block_range().location): | |
|
503 | self.current_block_range().inputRange.location): | |
|
435 | 504 | return True |
|
436 | 505 | else: |
|
437 | self.current_block_range().length-=1 | |
|
506 | for r in self.blockRanges.itervalues(): | |
|
507 | deleteRange = textView.selectedRange | |
|
508 | if(deleteRange.length == 0): | |
|
509 | deleteRange.location -= 1 | |
|
510 | deleteRange.length = 1 | |
|
511 | r.update_ranges_for_deletion(deleteRange) | |
|
438 | 512 | return False |
|
439 | 513 | return False |
|
440 | 514 | |
@@ -453,14 +527,9 b' class IPythonCocoaController(NSObject, AsyncFrontEndBase):' | |||
|
453 | 527 | for r,s in zip(ranges, replacementStrings): |
|
454 | 528 | r = r.rangeValue() |
|
455 | 529 | if(textView.textStorage().length() > 0 and |
|
456 |
|
|
|
457 | self.insert_text(s) | |
|
530 | r.location < self.current_block_range().inputRange.location): | |
|
531 | self.insert_text(s, textRange=r) | |
|
458 | 532 | allow = False |
|
459 | ||
|
460 | ||
|
461 | self.blockRanges.setdefault(self.currentBlockID, | |
|
462 | self.current_block_range()).length +=\ | |
|
463 | len(s) | |
|
464 | 533 | |
|
465 | 534 | return allow |
|
466 | 535 |
@@ -281,7 +281,6 b' class FrontEndBase(object):' | |||
|
281 | 281 | |
|
282 | 282 | def _add_block_id_for_failure(self, failure, blockID): |
|
283 | 283 | """_add_block_id_for_failure""" |
|
284 | ||
|
285 | 284 | failure.blockID = blockID |
|
286 | 285 | return failure |
|
287 | 286 | |
@@ -390,10 +389,8 b' class AsyncFrontEndBase(FrontEndBase):' | |||
|
390 | 389 | |
|
391 | 390 | d = self.engine.execute(block) |
|
392 | 391 | d.addCallback(self._add_history, block=block) |
|
393 |
d.addCallback |
|
|
394 |
|
|
|
395 | callbackArgs=(blockID,), | |
|
396 | errbackArgs=(blockID,)) | |
|
392 | d.addCallback(self._add_block_id_for_result, blockID) | |
|
393 | d.addErrback(self._add_block_id_for_failure, blockID) | |
|
397 | 394 | d.addBoth(self.update_cell_prompt, blockID=blockID) |
|
398 | 395 | d.addCallbacks(self.render_result, |
|
399 | 396 | errback=self.render_error) |
@@ -395,6 +395,7 b' class EngineService(object, service.Service):' | |||
|
395 | 395 | |
|
396 | 396 | return d |
|
397 | 397 | |
|
398 | ||
|
398 | 399 | # The IEngine methods. See the interface for documentation. |
|
399 | 400 | |
|
400 | 401 | def execute(self, lines): |
@@ -862,6 +863,30 b' class ThreadedEngineService(EngineService):' | |||
|
862 | 863 | def __init__(self, shellClass=Interpreter, mpi=None): |
|
863 | 864 | EngineService.__init__(self, shellClass, mpi) |
|
864 | 865 | |
|
866 | def wrapped_execute(self, msg, lines): | |
|
867 | """Wrap self.shell.execute to add extra information to tracebacks""" | |
|
868 | ||
|
869 | try: | |
|
870 | result = self.shell.execute(lines) | |
|
871 | except Exception,e: | |
|
872 | # This gives the following: | |
|
873 | # et=exception class | |
|
874 | # ev=exception class instance | |
|
875 | # tb=traceback object | |
|
876 | et,ev,tb = sys.exc_info() | |
|
877 | # This call adds attributes to the exception value | |
|
878 | et,ev,tb = self.shell.formatTraceback(et,ev,tb,msg) | |
|
879 | # Add another attribute | |
|
880 | ||
|
881 | # Create a new exception with the new attributes | |
|
882 | e = et(ev._ipython_traceback_text) | |
|
883 | e._ipython_engine_info = msg | |
|
884 | ||
|
885 | # Re-raise | |
|
886 | raise e | |
|
887 | ||
|
888 | return result | |
|
889 | ||
|
865 | 890 | |
|
866 | 891 | def execute(self, lines): |
|
867 | 892 | # Only import this if we are going to use this class |
@@ -871,6 +896,6 b' class ThreadedEngineService(EngineService):' | |||
|
871 | 896 | 'method':'execute', |
|
872 | 897 | 'args':[lines]} |
|
873 | 898 | |
|
874 |
d = threads.deferToThread(self. |
|
|
899 | d = threads.deferToThread(self.wrapped_execute, msg, lines) | |
|
875 | 900 | d.addCallback(self.addIDToResult) |
|
876 | 901 | return d |
General Comments 0
You need to be logged in to leave comments.
Login now