##// 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 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=>NSRange
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 self.insert_text(self.input_prompt(),
285 prompt = self.input_prompt()
218 textRange=NSMakeRange(self.blockRanges[blockID].location,0),
286
219 scrollToVisible=False
220 )
221 else:
287 else:
222 self.insert_text(self.input_prompt(number=result['number']),
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 NSMakeRange(self.textView.textStorage().length(),
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
402
325
403
326
404
327
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 self.current_block_range(),
407 self.current_block_range().inputRange,
331 string)
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 self.current_block_range().location,
478 self.current_block_range().inputRange.location,
402 0))
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 self.current_block_range().location + \
483 self.current_block_range().inputRange.location + \
407 self.current_block_range().length, 0))
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,15 +527,10 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 r.location < self.current_block_range().location):
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
533
460
461 self.blockRanges.setdefault(self.currentBlockID,
462 self.current_block_range()).length +=\
463 len(s)
464
465 return allow
534 return allow
466
535
467 def textView_completions_forPartialWordRange_indexOfSelectedItem_(self,
536 def textView_completions_forPartialWordRange_indexOfSelectedItem_(self,
@@ -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.addCallbacks(self._add_block_id_for_result,
392 d.addCallback(self._add_block_id_for_result, blockID)
394 errback=self._add_block_id_for_failure,
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.shell.execute, lines)
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