Show More
@@ -45,9 +45,9 b' from IPython.frontend.frontendbase import AsyncFrontEndBase' | |||||
45 | from twisted.internet.threads import blockingCallFromThread |
|
45 | from twisted.internet.threads import blockingCallFromThread | |
46 | from twisted.python.failure import Failure |
|
46 | from twisted.python.failure import Failure | |
47 |
|
47 | |||
48 |
#----------------------------------------------------------------------------- |
|
48 | #----------------------------------------------------------------------------- | |
49 | # Classes to implement the Cocoa frontend |
|
49 | # Classes to implement the Cocoa frontend | |
50 |
#----------------------------------------------------------------------------- |
|
50 | #----------------------------------------------------------------------------- | |
51 |
|
51 | |||
52 | # TODO: |
|
52 | # TODO: | |
53 | # 1. use MultiEngineClient and out-of-process engine rather than |
|
53 | # 1. use MultiEngineClient and out-of-process engine rather than | |
@@ -61,41 +61,94 b' class AutoreleasePoolWrappedThreadedEngineService(ThreadedEngineService):' | |||||
61 | """wrapped_execute""" |
|
61 | """wrapped_execute""" | |
62 | try: |
|
62 | try: | |
63 | p = NSAutoreleasePool.alloc().init() |
|
63 | p = NSAutoreleasePool.alloc().init() | |
64 | result = self.shell.execute(lines) |
|
64 | result = super(AutoreleasePoolWrappedThreadedEngineService, | |
65 | except Exception,e: |
|
65 | self).wrapped_execute(msg, lines) | |
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: |
|
66 | finally: | |
82 | p.drain() |
|
67 | p.drain() | |
83 |
|
68 | |||
84 | return result |
|
69 | return result | |
85 |
|
70 | |||
86 | def execute(self, lines): |
|
71 | ||
87 | # Only import this if we are going to use this class |
|
72 | ||
88 | from twisted.internet import threads |
|
73 | class Cell(NSObject): | |
|
74 | """ | |||
|
75 | Representation of the prompts, input and output of a cell in the | |||
|
76 | frontend | |||
|
77 | """ | |||
|
78 | ||||
|
79 | blockNumber = objc.ivar().unsigned_long() | |||
|
80 | blockID = objc.ivar() | |||
|
81 | inputBlock = objc.ivar() | |||
|
82 | output = objc.ivar() | |||
|
83 | ||||
|
84 | ||||
|
85 | ||||
|
86 | class CellBlock(object): | |||
|
87 | """ | |||
|
88 | Storage for information about text ranges relating to a single cell | |||
|
89 | """ | |||
|
90 | ||||
89 |
|
91 | |||
90 | msg = {'engineid':self.id, |
|
92 | def __init__(self, inputPromptRange, inputRange=None, outputPromptRange=None, | |
91 | 'method':'execute', |
|
93 | outputRange=None): | |
92 | 'args':[lines]} |
|
94 | super(CellBlock, self).__init__() | |
|
95 | self.inputPromptRange = inputPromptRange | |||
|
96 | self.inputRange = inputRange | |||
|
97 | self.outputPromptRange = outputPromptRange | |||
|
98 | self.outputRange = outputRange | |||
|
99 | ||||
|
100 | def update_ranges_for_insertion(self, text, textRange): | |||
|
101 | """Update ranges for text insertion at textRange""" | |||
|
102 | ||||
|
103 | for r in [self.inputPromptRange,self.inputRange, | |||
|
104 | self.outputPromptRange, self.outputRange]: | |||
|
105 | if(r == None): | |||
|
106 | continue | |||
|
107 | intersection = NSIntersectionRange(r,textRange) | |||
|
108 | if(intersection.length == 0): #ranges don't intersect | |||
|
109 | if r.location >= textRange.location: | |||
|
110 | r.location += len(text) | |||
|
111 | else: #ranges intersect | |||
|
112 | if(r.location > textRange.location): | |||
|
113 | offset = len(text) - intersection.length | |||
|
114 | r.length -= offset | |||
|
115 | r.location += offset | |||
|
116 | elif(r.location == textRange.location): | |||
|
117 | r.length += len(text) - intersection.length | |||
|
118 | else: | |||
|
119 | r.length -= intersection.length | |||
|
120 | ||||
|
121 | ||||
|
122 | def update_ranges_for_deletion(self, textRange): | |||
|
123 | """Update ranges for text deletion at textRange""" | |||
93 |
|
124 | |||
94 | d = threads.deferToThread(self.wrapped_execute, msg, lines) |
|
125 | for r in [self.inputPromptRange,self.inputRange, | |
95 | d.addCallback(self.addIDToResult) |
|
126 | self.outputPromptRange, self.outputRange]: | |
96 | return d |
|
127 | if(r==None): | |
|
128 | continue | |||
|
129 | intersection = NSIntersectionRange(r, textRange) | |||
|
130 | if(intersection.length == 0): #ranges don't intersect | |||
|
131 | if r.location >= textRange.location: | |||
|
132 | r.location -= textRange.length | |||
|
133 | else: #ranges intersect | |||
|
134 | if(r.location > textRange.location): | |||
|
135 | offset = intersection.length | |||
|
136 | r.length -= offset | |||
|
137 | r.location += offset | |||
|
138 | elif(r.location == textRange.location): | |||
|
139 | r.length += intersection.length | |||
|
140 | else: | |||
|
141 | r.length -= intersection.length | |||
|
142 | ||||
|
143 | def __repr__(self): | |||
|
144 | return 'CellBlock('+ str((self.inputPromptRange, | |||
|
145 | self.inputRange, | |||
|
146 | self.outputPromptRange, | |||
|
147 | self.outputRange)) + ')' | |||
|
148 | ||||
97 |
|
149 | |||
98 |
|
150 | |||
|
151 | ||||
99 | class IPythonCocoaController(NSObject, AsyncFrontEndBase): |
|
152 | class IPythonCocoaController(NSObject, AsyncFrontEndBase): | |
100 | userNS = objc.ivar() #mirror of engine.user_ns (key=>str(value)) |
|
153 | userNS = objc.ivar() #mirror of engine.user_ns (key=>str(value)) | |
101 | waitingForEngine = objc.ivar().bool() |
|
154 | waitingForEngine = objc.ivar().bool() | |
@@ -120,7 +173,7 b' class IPythonCocoaController(NSObject, AsyncFrontEndBase):' | |||||
120 | self.tabSpaces = 4 |
|
173 | self.tabSpaces = 4 | |
121 | self.tabUsesSpaces = True |
|
174 | self.tabUsesSpaces = True | |
122 | self.currentBlockID = self.next_block_ID() |
|
175 | self.currentBlockID = self.next_block_ID() | |
123 |
self.blockRanges = {} # blockID=> |
|
176 | self.blockRanges = {} # blockID=>CellBlock | |
124 |
|
177 | |||
125 |
|
178 | |||
126 | def awakeFromNib(self): |
|
179 | def awakeFromNib(self): | |
@@ -148,6 +201,7 b' class IPythonCocoaController(NSObject, AsyncFrontEndBase):' | |||||
148 | self.verticalRulerView = r |
|
201 | self.verticalRulerView = r | |
149 | self.verticalRulerView.setClientView_(self.textView) |
|
202 | self.verticalRulerView.setClientView_(self.textView) | |
150 | self._start_cli_banner() |
|
203 | self._start_cli_banner() | |
|
204 | self.start_new_block() | |||
151 |
|
205 | |||
152 |
|
206 | |||
153 | def appWillTerminate_(self, notification): |
|
207 | def appWillTerminate_(self, notification): | |
@@ -239,14 +293,16 b' class IPythonCocoaController(NSObject, AsyncFrontEndBase):' | |||||
239 |
|
293 | |||
240 |
|
294 | |||
241 | def update_cell_prompt(self, result, blockID=None): |
|
295 | def update_cell_prompt(self, result, blockID=None): | |
|
296 | print self.blockRanges | |||
242 | if(isinstance(result, Failure)): |
|
297 | if(isinstance(result, Failure)): | |
243 |
|
|
298 | prompt = self.input_prompt() | |
244 | textRange=NSMakeRange(self.blockRanges[blockID].location,0), |
|
299 | ||
245 | scrollToVisible=False |
|
|||
246 | ) |
|
|||
247 | else: |
|
300 | else: | |
248 |
|
|
301 | prompt = self.input_prompt(number=result['number']) | |
249 | textRange=NSMakeRange(self.blockRanges[blockID].location,0), |
|
302 | ||
|
303 | r = self.blockRanges[blockID].inputPromptRange | |||
|
304 | self.insert_text(prompt, | |||
|
305 | textRange=r, | |||
250 | scrollToVisible=False |
|
306 | scrollToVisible=False | |
251 | ) |
|
307 | ) | |
252 |
|
308 | |||
@@ -255,7 +311,7 b' class IPythonCocoaController(NSObject, AsyncFrontEndBase):' | |||||
255 |
|
311 | |||
256 | def render_result(self, result): |
|
312 | def render_result(self, result): | |
257 | blockID = result['blockID'] |
|
313 | blockID = result['blockID'] | |
258 | inputRange = self.blockRanges[blockID] |
|
314 | inputRange = self.blockRanges[blockID].inputRange | |
259 | del self.blockRanges[blockID] |
|
315 | del self.blockRanges[blockID] | |
260 |
|
316 | |||
261 | #print inputRange,self.current_block_range() |
|
317 | #print inputRange,self.current_block_range() | |
@@ -269,11 +325,17 b' class IPythonCocoaController(NSObject, AsyncFrontEndBase):' | |||||
269 |
|
325 | |||
270 |
|
326 | |||
271 | def render_error(self, failure): |
|
327 | def render_error(self, failure): | |
|
328 | print failure | |||
|
329 | blockID = failure.blockID | |||
|
330 | inputRange = self.blockRanges[blockID].inputRange | |||
272 | self.insert_text('\n' + |
|
331 | self.insert_text('\n' + | |
273 | self.output_prompt() + |
|
332 | self.output_prompt() + | |
274 | '\n' + |
|
333 | '\n' + | |
275 | failure.getErrorMessage() + |
|
334 | failure.getErrorMessage() + | |
276 |
'\n\n' |
|
335 | '\n\n', | |
|
336 | textRange=NSMakeRange(inputRange.location + | |||
|
337 | inputRange.length, | |||
|
338 | 0)) | |||
277 | self.start_new_block() |
|
339 | self.start_new_block() | |
278 | return failure |
|
340 | return failure | |
279 |
|
341 | |||
@@ -291,6 +353,9 b' class IPythonCocoaController(NSObject, AsyncFrontEndBase):' | |||||
291 | """""" |
|
353 | """""" | |
292 |
|
354 | |||
293 | self.currentBlockID = self.next_block_ID() |
|
355 | self.currentBlockID = self.next_block_ID() | |
|
356 | self.blockRanges[self.currentBlockID] = self.new_cell_block() | |||
|
357 | self.insert_text(self.input_prompt(), | |||
|
358 | textRange=self.current_block_range().inputPromptRange) | |||
294 |
|
359 | |||
295 |
|
360 | |||
296 |
|
361 | |||
@@ -298,15 +363,23 b' class IPythonCocoaController(NSObject, AsyncFrontEndBase):' | |||||
298 |
|
363 | |||
299 | return uuid.uuid4() |
|
364 | return uuid.uuid4() | |
300 |
|
365 | |||
|
366 | def new_cell_block(self): | |||
|
367 | """A new CellBlock at the end of self.textView.textStorage()""" | |||
|
368 | ||||
|
369 | return CellBlock(NSMakeRange(self.textView.textStorage().length(), | |||
|
370 | 0), #len(self.input_prompt())), | |||
|
371 | NSMakeRange(self.textView.textStorage().length(),# + len(self.input_prompt()), | |||
|
372 | 0)) | |||
|
373 | ||||
|
374 | ||||
301 | def current_block_range(self): |
|
375 | def current_block_range(self): | |
302 | return self.blockRanges.get(self.currentBlockID, |
|
376 | return self.blockRanges.get(self.currentBlockID, | |
303 |
|
|
377 | self.new_cell_block()) | |
304 | 0)) |
|
|||
305 |
|
378 | |||
306 | def current_block(self): |
|
379 | def current_block(self): | |
307 | """The current block's text""" |
|
380 | """The current block's text""" | |
308 |
|
381 | |||
309 | return self.text_for_range(self.current_block_range()) |
|
382 | return self.text_for_range(self.current_block_range().inputRange) | |
310 |
|
383 | |||
311 | def text_for_range(self, textRange): |
|
384 | def text_for_range(self, textRange): | |
312 | """text_for_range""" |
|
385 | """text_for_range""" | |
@@ -315,7 +388,7 b' class IPythonCocoaController(NSObject, AsyncFrontEndBase):' | |||||
315 | return ts.string().substringWithRange_(textRange) |
|
388 | return ts.string().substringWithRange_(textRange) | |
316 |
|
389 | |||
317 | def current_line(self): |
|
390 | def current_line(self): | |
318 | block = self.text_for_range(self.current_block_range()) |
|
391 | block = self.text_for_range(self.current_block_range().inputRange) | |
319 | block = block.split('\n') |
|
392 | block = block.split('\n') | |
320 | return block[-1] |
|
393 | return block[-1] | |
321 |
|
394 | |||
@@ -324,38 +397,28 b' class IPythonCocoaController(NSObject, AsyncFrontEndBase):' | |||||
324 | """Insert text into textView at textRange, updating blockRanges |
|
397 | """Insert text into textView at textRange, updating blockRanges | |
325 | as necessary |
|
398 | as necessary | |
326 | """ |
|
399 | """ | |
327 |
|
||||
328 | if(textRange == None): |
|
400 | if(textRange == None): | |
329 | #range for end of text |
|
401 | #range for end of text | |
330 | textRange = NSMakeRange(self.textView.textStorage().length(), 0) |
|
402 | textRange = NSMakeRange(self.textView.textStorage().length(), 0) | |
331 |
|
403 | |||
332 | for r in self.blockRanges.itervalues(): |
|
|||
333 | intersection = NSIntersectionRange(r,textRange) |
|
|||
334 | if(intersection.length == 0): #ranges don't intersect |
|
|||
335 | if r.location >= textRange.location: |
|
|||
336 | r.location += len(string) |
|
|||
337 | else: #ranges intersect |
|
|||
338 | if(r.location <= textRange.location): |
|
|||
339 | assert(intersection.length == textRange.length) |
|
|||
340 | r.length += textRange.length |
|
|||
341 | else: |
|
|||
342 | r.location += intersection.length |
|
|||
343 |
|
404 | |||
344 | self.textView.replaceCharactersInRange_withString_( |
|
405 | self.textView.replaceCharactersInRange_withString_( | |
345 | textRange, string) |
|
406 | textRange, string) | |
346 | self.textView.setSelectedRange_( |
|
407 | ||
347 | NSMakeRange(textRange.location+len(string), 0)) |
|
408 | for r in self.blockRanges.itervalues(): | |
|
409 | r.update_ranges_for_insertion(string, textRange) | |||
|
410 | ||||
|
411 | self.textView.setSelectedRange_(textRange) | |||
348 | if(scrollToVisible): |
|
412 | if(scrollToVisible): | |
349 | self.textView.scrollRangeToVisible_(textRange) |
|
413 | self.textView.scrollRangeToVisible_(textRange) | |
350 |
|
||||
351 |
|
414 | |||
352 |
|
415 | |||
353 |
|
416 | |||
354 | def replace_current_block_with_string(self, textView, string): |
|
417 | def replace_current_block_with_string(self, textView, string): | |
355 | textView.replaceCharactersInRange_withString_( |
|
418 | textView.replaceCharactersInRange_withString_( | |
356 |
|
|
419 | self.current_block_range().inputRange, | |
357 |
|
|
420 | string) | |
358 | self.current_block_range().length = len(string) |
|
421 | self.current_block_range().inputRange.length = len(string) | |
359 | r = NSMakeRange(textView.textStorage().length(), 0) |
|
422 | r = NSMakeRange(textView.textStorage().length(), 0) | |
360 | textView.scrollRangeToVisible_(r) |
|
423 | textView.scrollRangeToVisible_(r) | |
361 | textView.setSelectedRange_(r) |
|
424 | textView.setSelectedRange_(r) | |
@@ -424,26 +487,18 b' class IPythonCocoaController(NSObject, AsyncFrontEndBase):' | |||||
424 |
|
487 | |||
425 | elif(selector == 'moveToBeginningOfParagraph:'): |
|
488 | elif(selector == 'moveToBeginningOfParagraph:'): | |
426 | textView.setSelectedRange_(NSMakeRange( |
|
489 | textView.setSelectedRange_(NSMakeRange( | |
427 |
|
|
490 | self.current_block_range().inputRange.location, | |
428 |
|
|
491 | 0)) | |
429 | return True |
|
492 | return True | |
430 | elif(selector == 'moveToEndOfParagraph:'): |
|
493 | elif(selector == 'moveToEndOfParagraph:'): | |
431 | textView.setSelectedRange_(NSMakeRange( |
|
494 | textView.setSelectedRange_(NSMakeRange( | |
432 |
|
|
495 | self.current_block_range().inputRange.location + \ | |
433 |
|
|
496 | self.current_block_range().inputRange.length, 0)) | |
434 | return True |
|
497 | return True | |
435 | elif(selector == 'deleteToEndOfParagraph:'): |
|
498 | elif(selector == 'deleteToEndOfParagraph:'): | |
436 | if(textView.selectedRange().location <= \ |
|
499 | if(textView.selectedRange().location <= \ | |
437 | self.current_block_range().location): |
|
500 | self.current_block_range().location): | |
438 | # Intersect the selected range with the current line range |
|
501 | raise NotImplemented() | |
439 | if(self.current_block_range().length < 0): |
|
|||
440 | self.blockRanges[self.currentBlockID].length = 0 |
|
|||
441 |
|
||||
442 | r = NSIntersectionRange(textView.rangesForUserTextChange()[0], |
|
|||
443 | self.current_block_range()) |
|
|||
444 |
|
||||
445 | if(r.length > 0): #no intersection |
|
|||
446 | textView.setSelectedRange_(r) |
|
|||
447 |
|
502 | |||
448 | return False # don't actually handle the delete |
|
503 | return False # don't actually handle the delete | |
449 |
|
504 | |||
@@ -457,10 +512,15 b' class IPythonCocoaController(NSObject, AsyncFrontEndBase):' | |||||
457 | elif(selector == 'deleteBackward:'): |
|
512 | elif(selector == 'deleteBackward:'): | |
458 | #if we're at the beginning of the current block, ignore |
|
513 | #if we're at the beginning of the current block, ignore | |
459 | if(textView.selectedRange().location == \ |
|
514 | if(textView.selectedRange().location == \ | |
460 | self.current_block_range().location): |
|
515 | self.current_block_range().inputRange.location): | |
461 | return True |
|
516 | return True | |
462 | else: |
|
517 | else: | |
463 | self.current_block_range().length-=1 |
|
518 | for r in self.blockRanges.itervalues(): | |
|
519 | deleteRange = textView.selectedRange | |||
|
520 | if(deleteRange.length == 0): | |||
|
521 | deleteRange.location -= 1 | |||
|
522 | deleteRange.length = 1 | |||
|
523 | r.update_ranges_for_deletion(deleteRange) | |||
464 | return False |
|
524 | return False | |
465 | return False |
|
525 | return False | |
466 |
|
526 | |||
@@ -479,14 +539,9 b' class IPythonCocoaController(NSObject, AsyncFrontEndBase):' | |||||
479 | for r,s in zip(ranges, replacementStrings): |
|
539 | for r,s in zip(ranges, replacementStrings): | |
480 | r = r.rangeValue() |
|
540 | r = r.rangeValue() | |
481 | if(textView.textStorage().length() > 0 and |
|
541 | if(textView.textStorage().length() > 0 and | |
482 |
|
|
542 | r.location < self.current_block_range().inputRange.location): | |
483 | self.insert_text(s) |
|
543 | self.insert_text(s) | |
484 | allow = False |
|
544 | allow = False | |
485 |
|
||||
486 |
|
||||
487 | self.blockRanges.setdefault(self.currentBlockID, |
|
|||
488 | self.current_block_range()).length +=\ |
|
|||
489 | len(s) |
|
|||
490 |
|
545 | |||
491 | return allow |
|
546 | return allow | |
492 |
|
547 |
@@ -37,11 +37,6 b' from IPython.kernel.core.history import FrontEndHistory' | |||||
37 | from IPython.kernel.core.util import Bunch |
|
37 | from IPython.kernel.core.util import Bunch | |
38 | from IPython.kernel.engineservice import IEngineCore |
|
38 | from IPython.kernel.engineservice import IEngineCore | |
39 |
|
39 | |||
40 | try: |
|
|||
41 | from twisted.python.failure import Failure |
|
|||
42 | except ImportError: |
|
|||
43 | #Twisted not available |
|
|||
44 | Failure = Exception |
|
|||
45 |
|
40 | |||
46 | ############################################################################## |
|
41 | ############################################################################## | |
47 | # TEMPORARY!!! fake configuration, while we decide whether to use tconfig or |
|
42 | # TEMPORARY!!! fake configuration, while we decide whether to use tconfig or | |
@@ -286,7 +281,6 b' class FrontEndBase(object):' | |||||
286 |
|
281 | |||
287 | def _add_block_id_for_failure(self, failure, blockID): |
|
282 | def _add_block_id_for_failure(self, failure, blockID): | |
288 | """_add_block_id_for_failure""" |
|
283 | """_add_block_id_for_failure""" | |
289 |
|
||||
290 | failure.blockID = blockID |
|
284 | failure.blockID = blockID | |
291 | return failure |
|
285 | return failure | |
292 |
|
286 | |||
@@ -387,6 +381,7 b' class AsyncFrontEndBase(FrontEndBase):' | |||||
387 | """ |
|
381 | """ | |
388 |
|
382 | |||
389 | if(not self.is_complete(block)): |
|
383 | if(not self.is_complete(block)): | |
|
384 | from twisted.python.failure import Failure | |||
390 | return Failure(Exception("Block is not compilable")) |
|
385 | return Failure(Exception("Block is not compilable")) | |
391 |
|
386 | |||
392 | if(blockID == None): |
|
387 | if(blockID == None): | |
@@ -394,10 +389,8 b' class AsyncFrontEndBase(FrontEndBase):' | |||||
394 |
|
389 | |||
395 | d = self.engine.execute(block) |
|
390 | d = self.engine.execute(block) | |
396 | d.addCallback(self._add_history, block=block) |
|
391 | d.addCallback(self._add_history, block=block) | |
397 |
d.addCallback |
|
392 | d.addCallback(self._add_block_id_for_result, blockID) | |
398 |
|
|
393 | d.addErrback(self._add_block_id_for_failure, blockID) | |
399 | callbackArgs=(blockID,), |
|
|||
400 | errbackArgs=(blockID,)) |
|
|||
401 | d.addBoth(self.update_cell_prompt, blockID=blockID) |
|
394 | d.addBoth(self.update_cell_prompt, blockID=blockID) | |
402 | d.addCallbacks(self.render_result, |
|
395 | d.addCallbacks(self.render_result, | |
403 | errback=self.render_error) |
|
396 | errback=self.render_error) |
General Comments 0
You need to be logged in to leave comments.
Login now