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 | class IPythonCocoaController(NSObject, AsyncFrontEndBase): |
|
139 | class IPythonCocoaController(NSObject, AsyncFrontEndBase): | |
74 | userNS = objc.ivar() #mirror of engine.user_ns (key=>str(value)) |
|
140 | userNS = objc.ivar() #mirror of engine.user_ns (key=>str(value)) | |
75 | waitingForEngine = objc.ivar().bool() |
|
141 | waitingForEngine = objc.ivar().bool() | |
@@ -94,7 +160,7 b' class IPythonCocoaController(NSObject, AsyncFrontEndBase):' | |||||
94 | self.tabSpaces = 4 |
|
160 | self.tabSpaces = 4 | |
95 | self.tabUsesSpaces = True |
|
161 | self.tabUsesSpaces = True | |
96 | self.currentBlockID = self.next_block_ID() |
|
162 | self.currentBlockID = self.next_block_ID() | |
97 |
self.blockRanges = {} # blockID=> |
|
163 | self.blockRanges = {} # blockID=>CellBlock | |
98 |
|
164 | |||
99 |
|
165 | |||
100 | def awakeFromNib(self): |
|
166 | def awakeFromNib(self): | |
@@ -122,6 +188,7 b' class IPythonCocoaController(NSObject, AsyncFrontEndBase):' | |||||
122 | self.verticalRulerView = r |
|
188 | self.verticalRulerView = r | |
123 | self.verticalRulerView.setClientView_(self.textView) |
|
189 | self.verticalRulerView.setClientView_(self.textView) | |
124 | self._start_cli_banner() |
|
190 | self._start_cli_banner() | |
|
191 | self.start_new_block() | |||
125 |
|
192 | |||
126 |
|
193 | |||
127 | def appWillTerminate_(self, notification): |
|
194 | def appWillTerminate_(self, notification): | |
@@ -213,14 +280,16 b' class IPythonCocoaController(NSObject, AsyncFrontEndBase):' | |||||
213 |
|
280 | |||
214 |
|
281 | |||
215 | def update_cell_prompt(self, result, blockID=None): |
|
282 | def update_cell_prompt(self, result, blockID=None): | |
|
283 | print self.blockRanges | |||
216 | if(isinstance(result, Failure)): |
|
284 | if(isinstance(result, Failure)): | |
217 |
|
|
285 | prompt = self.input_prompt() | |
218 | textRange=NSMakeRange(self.blockRanges[blockID].location,0), |
|
286 | ||
219 | scrollToVisible=False |
|
|||
220 | ) |
|
|||
221 | else: |
|
287 | else: | |
222 |
|
|
288 | prompt = self.input_prompt(number=result['number']) | |
223 | textRange=NSMakeRange(self.blockRanges[blockID].location,0), |
|
289 | ||
|
290 | r = self.blockRanges[blockID].inputPromptRange | |||
|
291 | self.insert_text(prompt, | |||
|
292 | textRange=r, | |||
224 | scrollToVisible=False |
|
293 | scrollToVisible=False | |
225 | ) |
|
294 | ) | |
226 |
|
295 | |||
@@ -229,7 +298,7 b' class IPythonCocoaController(NSObject, AsyncFrontEndBase):' | |||||
229 |
|
298 | |||
230 | def render_result(self, result): |
|
299 | def render_result(self, result): | |
231 | blockID = result['blockID'] |
|
300 | blockID = result['blockID'] | |
232 | inputRange = self.blockRanges[blockID] |
|
301 | inputRange = self.blockRanges[blockID].inputRange | |
233 | del self.blockRanges[blockID] |
|
302 | del self.blockRanges[blockID] | |
234 |
|
303 | |||
235 | #print inputRange,self.current_block_range() |
|
304 | #print inputRange,self.current_block_range() | |
@@ -243,11 +312,17 b' class IPythonCocoaController(NSObject, AsyncFrontEndBase):' | |||||
243 |
|
312 | |||
244 |
|
313 | |||
245 | def render_error(self, failure): |
|
314 | def render_error(self, failure): | |
|
315 | print failure | |||
|
316 | blockID = failure.blockID | |||
|
317 | inputRange = self.blockRanges[blockID].inputRange | |||
246 | self.insert_text('\n' + |
|
318 | self.insert_text('\n' + | |
247 | self.output_prompt() + |
|
319 | self.output_prompt() + | |
248 | '\n' + |
|
320 | '\n' + | |
249 | failure.getErrorMessage() + |
|
321 | failure.getErrorMessage() + | |
250 |
'\n\n' |
|
322 | '\n\n', | |
|
323 | textRange=NSMakeRange(inputRange.location + | |||
|
324 | inputRange.length, | |||
|
325 | 0)) | |||
251 | self.start_new_block() |
|
326 | self.start_new_block() | |
252 | return failure |
|
327 | return failure | |
253 |
|
328 | |||
@@ -265,6 +340,9 b' class IPythonCocoaController(NSObject, AsyncFrontEndBase):' | |||||
265 | """""" |
|
340 | """""" | |
266 |
|
341 | |||
267 | self.currentBlockID = self.next_block_ID() |
|
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 | return uuid.uuid4() |
|
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 | def current_block_range(self): |
|
362 | def current_block_range(self): | |
276 | return self.blockRanges.get(self.currentBlockID, |
|
363 | return self.blockRanges.get(self.currentBlockID, | |
277 |
|
|
364 | self.new_cell_block()) | |
278 | 0)) |
|
|||
279 |
|
365 | |||
280 | def current_block(self): |
|
366 | def current_block(self): | |
281 | """The current block's text""" |
|
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 | def text_for_range(self, textRange): |
|
371 | def text_for_range(self, textRange): | |
286 | """text_for_range""" |
|
372 | """text_for_range""" | |
@@ -289,7 +375,7 b' class IPythonCocoaController(NSObject, AsyncFrontEndBase):' | |||||
289 | return ts.string().substringWithRange_(textRange) |
|
375 | return ts.string().substringWithRange_(textRange) | |
290 |
|
376 | |||
291 | def current_line(self): |
|
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 | block = block.split('\n') |
|
379 | block = block.split('\n') | |
294 | return block[-1] |
|
380 | return block[-1] | |
295 |
|
381 | |||
@@ -298,38 +384,29 b' class IPythonCocoaController(NSObject, AsyncFrontEndBase):' | |||||
298 | """Insert text into textView at textRange, updating blockRanges |
|
384 | """Insert text into textView at textRange, updating blockRanges | |
299 | as necessary |
|
385 | as necessary | |
300 | """ |
|
386 | """ | |
301 |
|
||||
302 | if(textRange == None): |
|
387 | if(textRange == None): | |
303 | #range for end of text |
|
388 | #range for end of text | |
304 | textRange = NSMakeRange(self.textView.textStorage().length(), 0) |
|
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 | self.textView.replaceCharactersInRange_withString_( |
|
393 | self.textView.replaceCharactersInRange_withString_( | |
319 | textRange, string) |
|
394 | textRange, string) | |
320 | self.textView.setSelectedRange_( |
|
395 | ||
321 | NSMakeRange(textRange.location+len(string), 0)) |
|
396 | for r in self.blockRanges.itervalues(): | |
|
397 | r.update_ranges_for_insertion(string, textRange) | |||
|
398 | ||||
|
399 | self.textView.setSelectedRange_(textRange) | |||
322 | if(scrollToVisible): |
|
400 | if(scrollToVisible): | |
323 | self.textView.scrollRangeToVisible_(textRange) |
|
401 | self.textView.scrollRangeToVisible_(textRange) | |
324 |
|
||||
325 |
|
402 | |||
326 |
|
403 | |||
327 |
|
404 | |||
328 | def replace_current_block_with_string(self, textView, string): |
|
405 | def replace_current_block_with_string(self, textView, string): | |
329 | textView.replaceCharactersInRange_withString_( |
|
406 | textView.replaceCharactersInRange_withString_( | |
330 |
|
|
407 | self.current_block_range().inputRange, | |
331 |
|
|
408 | string) | |
332 | self.current_block_range().length = len(string) |
|
409 | self.current_block_range().inputRange.length = len(string) | |
333 | r = NSMakeRange(textView.textStorage().length(), 0) |
|
410 | r = NSMakeRange(textView.textStorage().length(), 0) | |
334 | textView.scrollRangeToVisible_(r) |
|
411 | textView.scrollRangeToVisible_(r) | |
335 | textView.setSelectedRange_(r) |
|
412 | textView.setSelectedRange_(r) | |
@@ -398,26 +475,18 b' class IPythonCocoaController(NSObject, AsyncFrontEndBase):' | |||||
398 |
|
475 | |||
399 | elif(selector == 'moveToBeginningOfParagraph:'): |
|
476 | elif(selector == 'moveToBeginningOfParagraph:'): | |
400 | textView.setSelectedRange_(NSMakeRange( |
|
477 | textView.setSelectedRange_(NSMakeRange( | |
401 |
|
|
478 | self.current_block_range().inputRange.location, | |
402 |
|
|
479 | 0)) | |
403 | return True |
|
480 | return True | |
404 | elif(selector == 'moveToEndOfParagraph:'): |
|
481 | elif(selector == 'moveToEndOfParagraph:'): | |
405 | textView.setSelectedRange_(NSMakeRange( |
|
482 | textView.setSelectedRange_(NSMakeRange( | |
406 |
|
|
483 | self.current_block_range().inputRange.location + \ | |
407 |
|
|
484 | self.current_block_range().inputRange.length, 0)) | |
408 | return True |
|
485 | return True | |
409 | elif(selector == 'deleteToEndOfParagraph:'): |
|
486 | elif(selector == 'deleteToEndOfParagraph:'): | |
410 | if(textView.selectedRange().location <= \ |
|
487 | if(textView.selectedRange().location <= \ | |
411 | self.current_block_range().location): |
|
488 | self.current_block_range().location): | |
412 | # Intersect the selected range with the current line range |
|
489 | raise NotImplemented() | |
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) |
|
|||
421 |
|
490 | |||
422 | return False # don't actually handle the delete |
|
491 | return False # don't actually handle the delete | |
423 |
|
492 | |||
@@ -431,10 +500,15 b' class IPythonCocoaController(NSObject, AsyncFrontEndBase):' | |||||
431 | elif(selector == 'deleteBackward:'): |
|
500 | elif(selector == 'deleteBackward:'): | |
432 | #if we're at the beginning of the current block, ignore |
|
501 | #if we're at the beginning of the current block, ignore | |
433 | if(textView.selectedRange().location == \ |
|
502 | if(textView.selectedRange().location == \ | |
434 | self.current_block_range().location): |
|
503 | self.current_block_range().inputRange.location): | |
435 | return True |
|
504 | return True | |
436 | else: |
|
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 | return False |
|
512 | return False | |
439 | return False |
|
513 | return False | |
440 |
|
514 | |||
@@ -453,14 +527,9 b' class IPythonCocoaController(NSObject, AsyncFrontEndBase):' | |||||
453 | for r,s in zip(ranges, replacementStrings): |
|
527 | for r,s in zip(ranges, replacementStrings): | |
454 | r = r.rangeValue() |
|
528 | r = r.rangeValue() | |
455 | if(textView.textStorage().length() > 0 and |
|
529 | if(textView.textStorage().length() > 0 and | |
456 |
|
|
530 | r.location < self.current_block_range().inputRange.location): | |
457 | self.insert_text(s) |
|
531 | self.insert_text(s, textRange=r) | |
458 | allow = False |
|
532 | allow = False | |
459 |
|
||||
460 |
|
||||
461 | self.blockRanges.setdefault(self.currentBlockID, |
|
|||
462 | self.current_block_range()).length +=\ |
|
|||
463 | len(s) |
|
|||
464 |
|
533 | |||
465 | return allow |
|
534 | return allow | |
466 |
|
535 |
@@ -281,7 +281,6 b' class FrontEndBase(object):' | |||||
281 |
|
281 | |||
282 | def _add_block_id_for_failure(self, failure, blockID): |
|
282 | def _add_block_id_for_failure(self, failure, blockID): | |
283 | """_add_block_id_for_failure""" |
|
283 | """_add_block_id_for_failure""" | |
284 |
|
||||
285 | failure.blockID = blockID |
|
284 | failure.blockID = blockID | |
286 | return failure |
|
285 | return failure | |
287 |
|
286 | |||
@@ -390,10 +389,8 b' class AsyncFrontEndBase(FrontEndBase):' | |||||
390 |
|
389 | |||
391 | d = self.engine.execute(block) |
|
390 | d = self.engine.execute(block) | |
392 | d.addCallback(self._add_history, block=block) |
|
391 | d.addCallback(self._add_history, block=block) | |
393 |
d.addCallback |
|
392 | d.addCallback(self._add_block_id_for_result, blockID) | |
394 |
|
|
393 | d.addErrback(self._add_block_id_for_failure, blockID) | |
395 | callbackArgs=(blockID,), |
|
|||
396 | errbackArgs=(blockID,)) |
|
|||
397 | d.addBoth(self.update_cell_prompt, blockID=blockID) |
|
394 | d.addBoth(self.update_cell_prompt, blockID=blockID) | |
398 | d.addCallbacks(self.render_result, |
|
395 | d.addCallbacks(self.render_result, | |
399 | errback=self.render_error) |
|
396 | errback=self.render_error) |
@@ -395,6 +395,7 b' class EngineService(object, service.Service):' | |||||
395 |
|
395 | |||
396 | return d |
|
396 | return d | |
397 |
|
397 | |||
|
398 | ||||
398 | # The IEngine methods. See the interface for documentation. |
|
399 | # The IEngine methods. See the interface for documentation. | |
399 |
|
400 | |||
400 | def execute(self, lines): |
|
401 | def execute(self, lines): | |
@@ -862,6 +863,30 b' class ThreadedEngineService(EngineService):' | |||||
862 | def __init__(self, shellClass=Interpreter, mpi=None): |
|
863 | def __init__(self, shellClass=Interpreter, mpi=None): | |
863 | EngineService.__init__(self, shellClass, mpi) |
|
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 | def execute(self, lines): |
|
891 | def execute(self, lines): | |
867 | # Only import this if we are going to use this class |
|
892 | # Only import this if we are going to use this class | |
@@ -871,6 +896,6 b' class ThreadedEngineService(EngineService):' | |||||
871 | 'method':'execute', |
|
896 | 'method':'execute', | |
872 | 'args':[lines]} |
|
897 | 'args':[lines]} | |
873 |
|
898 | |||
874 |
d = threads.deferToThread(self. |
|
899 | d = threads.deferToThread(self.wrapped_execute, msg, lines) | |
875 | d.addCallback(self.addIDToResult) |
|
900 | d.addCallback(self.addIDToResult) | |
876 | return d |
|
901 | return d |
General Comments 0
You need to be logged in to leave comments.
Login now