4545from twisted .internet .threads import blockingCallFromThread
4646from twisted .python .failure import Failure
4747
48- #------------------------------------------------------------------------------
48+ #-----------------------------------------------------------------------------
4949# Classes to implement the Cocoa frontend
50- #------------------------------------------------------------------------------
50+ #-----------------------------------------------------------------------------
5151
5252# TODO:
5353# 1. use MultiEngineClient and out-of-process engine rather than
@@ -61,41 +61,94 @@ def wrapped_execute(self, msg, lines):
6161 """wrapped_execute"""
6262 try :
6363 p = NSAutoreleasePool .alloc ().init ()
64- result = self .shell .execute (lines )
65- except Exception ,e :
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
64+ result = super (AutoreleasePoolWrappedThreadedEngineService ,
65+ self ).wrapped_execute (msg , lines )
8166 finally :
8267 p .drain ()
8368
8469 return result
8570
86- def execute (self , lines ):
87- # Only import this if we are going to use this class
88- from twisted .internet import threads
71+
72+
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+
8991
90- msg = {'engineid' :self .id ,
91- 'method' :'execute' ,
92- 'args' :[lines ]}
92+ def __init__ (self , inputPromptRange , inputRange = None , outputPromptRange = None ,
93+ outputRange = None ):
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"""
93124
94- d = threads .deferToThread (self .wrapped_execute , msg , lines )
95- d .addCallback (self .addIDToResult )
96- return d
125+ for r in [self .inputPromptRange ,self .inputRange ,
126+ self .outputPromptRange , self .outputRange ]:
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+
97149
98150
151+
99152class IPythonCocoaController (NSObject , AsyncFrontEndBase ):
100153 userNS = objc .ivar () #mirror of engine.user_ns (key=>str(value))
101154 waitingForEngine = objc .ivar ().bool ()
@@ -120,7 +173,7 @@ def _common_init(self):
120173 self .tabSpaces = 4
121174 self .tabUsesSpaces = True
122175 self .currentBlockID = self .next_block_ID ()
123- self .blockRanges = {} # blockID=>NSRange
176+ self .blockRanges = {} # blockID=>CellBlock
124177
125178
126179 def awakeFromNib (self ):
@@ -148,6 +201,7 @@ def awakeFromNib(self):
148201 self .verticalRulerView = r
149202 self .verticalRulerView .setClientView_ (self .textView )
150203 self ._start_cli_banner ()
204+ self .start_new_block ()
151205
152206
153207 def appWillTerminate_ (self , notification ):
@@ -239,14 +293,16 @@ def _store_engine_namespace_values(self, values, keys=[]):
239293
240294
241295 def update_cell_prompt (self , result , blockID = None ):
296+ print self .blockRanges
242297 if (isinstance (result , Failure )):
243- self .insert_text (self .input_prompt (),
244- textRange = NSMakeRange (self .blockRanges [blockID ].location ,0 ),
245- scrollToVisible = False
246- )
298+ prompt = self .input_prompt ()
299+
247300 else :
248- self .insert_text (self .input_prompt (number = result ['number' ]),
249- textRange = NSMakeRange (self .blockRanges [blockID ].location ,0 ),
301+ prompt = self .input_prompt (number = result ['number' ])
302+
303+ r = self .blockRanges [blockID ].inputPromptRange
304+ self .insert_text (prompt ,
305+ textRange = r ,
250306 scrollToVisible = False
251307 )
252308
@@ -255,7 +311,7 @@ def update_cell_prompt(self, result, blockID=None):
255311
256312 def render_result (self , result ):
257313 blockID = result ['blockID' ]
258- inputRange = self .blockRanges [blockID ]
314+ inputRange = self .blockRanges [blockID ]. inputRange
259315 del self .blockRanges [blockID ]
260316
261317 #print inputRange,self.current_block_range()
@@ -269,11 +325,17 @@ def render_result(self, result):
269325
270326
271327 def render_error (self , failure ):
328+ print failure
329+ blockID = failure .blockID
330+ inputRange = self .blockRanges [blockID ].inputRange
272331 self .insert_text ('\n ' +
273332 self .output_prompt () +
274333 '\n ' +
275334 failure .getErrorMessage () +
276- '\n \n ' )
335+ '\n \n ' ,
336+ textRange = NSMakeRange (inputRange .location +
337+ inputRange .length ,
338+ 0 ))
277339 self .start_new_block ()
278340 return failure
279341
@@ -291,22 +353,33 @@ def start_new_block(self):
291353 """"""
292354
293355 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 )
294359
295360
296361
297362 def next_block_ID (self ):
298363
299364 return uuid .uuid4 ()
300365
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+
301375 def current_block_range (self ):
302376 return self .blockRanges .get (self .currentBlockID ,
303- NSMakeRange (self .textView .textStorage ().length (),
304- 0 ))
377+ self .new_cell_block ())
305378
306379 def current_block (self ):
307380 """The current block's text"""
308381
309- return self .text_for_range (self .current_block_range ())
382+ return self .text_for_range (self .current_block_range (). inputRange )
310383
311384 def text_for_range (self , textRange ):
312385 """text_for_range"""
@@ -315,7 +388,7 @@ def text_for_range(self, textRange):
315388 return ts .string ().substringWithRange_ (textRange )
316389
317390 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 )
319392 block = block .split ('\n ' )
320393 return block [- 1 ]
321394
@@ -324,38 +397,28 @@ def insert_text(self, string=None, textRange=None, scrollToVisible=True):
324397 """Insert text into textView at textRange, updating blockRanges
325398 as necessary
326399 """
327-
328400 if (textRange == None ):
329401 #range for end of text
330402 textRange = NSMakeRange (self .textView .textStorage ().length (), 0 )
331403
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
343404
344405 self .textView .replaceCharactersInRange_withString_ (
345406 textRange , string )
346- self .textView .setSelectedRange_ (
347- NSMakeRange (textRange .location + len (string ), 0 ))
407+
408+ for r in self .blockRanges .itervalues ():
409+ r .update_ranges_for_insertion (string , textRange )
410+
411+ self .textView .setSelectedRange_ (textRange )
348412 if (scrollToVisible ):
349413 self .textView .scrollRangeToVisible_ (textRange )
350-
351414
352415
353416
354417 def replace_current_block_with_string (self , textView , string ):
355418 textView .replaceCharactersInRange_withString_ (
356- self .current_block_range (),
357- string )
358- self .current_block_range ().length = len (string )
419+ self .current_block_range (). inputRange ,
420+ string )
421+ self .current_block_range ().inputRange . length = len (string )
359422 r = NSMakeRange (textView .textStorage ().length (), 0 )
360423 textView .scrollRangeToVisible_ (r )
361424 textView .setSelectedRange_ (r )
@@ -424,26 +487,18 @@ def textView_doCommandBySelector_(self, textView, selector):
424487
425488 elif (selector == 'moveToBeginningOfParagraph:' ):
426489 textView .setSelectedRange_ (NSMakeRange (
427- self .current_block_range ().location ,
428- 0 ))
490+ self .current_block_range (). inputRange .location ,
491+ 0 ))
429492 return True
430493 elif (selector == 'moveToEndOfParagraph:' ):
431494 textView .setSelectedRange_ (NSMakeRange (
432- self .current_block_range ().location + \
433- self .current_block_range ().length , 0 ))
495+ self .current_block_range (). inputRange .location + \
496+ self .current_block_range (). inputRange .length , 0 ))
434497 return True
435498 elif (selector == 'deleteToEndOfParagraph:' ):
436499 if (textView .selectedRange ().location <= \
437500 self .current_block_range ().location ):
438- # Intersect the selected range with the current line range
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 )
501+ raise NotImplemented ()
447502
448503 return False # don't actually handle the delete
449504
@@ -457,10 +512,15 @@ def textView_doCommandBySelector_(self, textView, selector):
457512 elif (selector == 'deleteBackward:' ):
458513 #if we're at the beginning of the current block, ignore
459514 if (textView .selectedRange ().location == \
460- self .current_block_range ().location ):
515+ self .current_block_range ().inputRange . location ):
461516 return True
462517 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 )
464524 return False
465525 return False
466526
@@ -479,14 +539,9 @@ def textView_shouldChangeTextInRanges_replacementStrings_(self,
479539 for r ,s in zip (ranges , replacementStrings ):
480540 r = r .rangeValue ()
481541 if (textView .textStorage ().length () > 0 and
482- r .location < self .current_block_range ().location ):
542+ r .location < self .current_block_range (). inputRange .location ):
483543 self .insert_text (s )
484544 allow = False
485-
486-
487- self .blockRanges .setdefault (self .currentBlockID ,
488- self .current_block_range ()).length += \
489- len (s )
490545
491546 return allow
492547
0 commit comments