##// END OF EJS Templates
merged from trunk. cocoa_frontend refactored to keep input prompt and input ranges. no crash, but garbage output
Barry Wark -
r1322:e14c46f7 merge
parent child Browse files
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=>NSRange
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 self.insert_text(self.input_prompt(),
218 textRange=NSMakeRange(self.blockRanges[blockID].location,0),
219 scrollToVisible=False
220 )
285 prompt = self.input_prompt()
286
221 287 else:
222 self.insert_text(self.input_prompt(number=result['number']),
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 NSMakeRange(self.textView.textStorage().length(),
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 self.current_block_range(),
331 string)
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 self.current_block_range().location,
402 0))
478 self.current_block_range().inputRange.location,
479 0))
403 480 return True
404 481 elif(selector == 'moveToEndOfParagraph:'):
405 482 textView.setSelectedRange_(NSMakeRange(
406 self.current_block_range().location + \
407 self.current_block_range().length, 0))
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 r.location < self.current_block_range().location):
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.addCallbacks(self._add_block_id_for_result,
394 errback=self._add_block_id_for_failure,
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.shell.execute, lines)
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