##// END OF EJS Templates
renamed AsynchronousFrontEndBase to AsyncFrontEndBase
Barry Wark <barrywarkatgmaildotcom> -
Show More
@@ -1,504 +1,504 b''
1 # encoding: utf-8
1 # encoding: utf-8
2 # -*- test-case-name: IPython.frontend.cocoa.tests.test_cocoa_frontend -*-
2 # -*- test-case-name: IPython.frontend.cocoa.tests.test_cocoa_frontend -*-
3
3
4 """PyObjC classes to provide a Cocoa frontend to the
4 """PyObjC classes to provide a Cocoa frontend to the
5 IPython.kernel.engineservice.IEngineBase.
5 IPython.kernel.engineservice.IEngineBase.
6
6
7 To add an IPython interpreter to a cocoa app, instantiate an
7 To add an IPython interpreter to a cocoa app, instantiate an
8 IPythonCocoaController in a XIB and connect its textView outlet to an
8 IPythonCocoaController in a XIB and connect its textView outlet to an
9 NSTextView instance in your UI. That's it.
9 NSTextView instance in your UI. That's it.
10
10
11 Author: Barry Wark
11 Author: Barry Wark
12 """
12 """
13
13
14 __docformat__ = "restructuredtext en"
14 __docformat__ = "restructuredtext en"
15
15
16 #-----------------------------------------------------------------------------
16 #-----------------------------------------------------------------------------
17 # Copyright (C) 2008 The IPython Development Team
17 # Copyright (C) 2008 The IPython Development Team
18 #
18 #
19 # Distributed under the terms of the BSD License. The full license is in
19 # Distributed under the terms of the BSD License. The full license is in
20 # the file COPYING, distributed as part of this software.
20 # the file COPYING, distributed as part of this software.
21 #-----------------------------------------------------------------------------
21 #-----------------------------------------------------------------------------
22
22
23 #-----------------------------------------------------------------------------
23 #-----------------------------------------------------------------------------
24 # Imports
24 # Imports
25 #-----------------------------------------------------------------------------
25 #-----------------------------------------------------------------------------
26
26
27 import sys
27 import sys
28 import objc
28 import objc
29 import uuid
29 import uuid
30
30
31 from Foundation import NSObject, NSMutableArray, NSMutableDictionary,\
31 from Foundation import NSObject, NSMutableArray, NSMutableDictionary,\
32 NSLog, NSNotificationCenter, NSMakeRange,\
32 NSLog, NSNotificationCenter, NSMakeRange,\
33 NSLocalizedString, NSIntersectionRange,\
33 NSLocalizedString, NSIntersectionRange,\
34 NSString, NSAutoreleasePool
34 NSString, NSAutoreleasePool
35
35
36 from AppKit import NSApplicationWillTerminateNotification, NSBeep,\
36 from AppKit import NSApplicationWillTerminateNotification, NSBeep,\
37 NSTextView, NSRulerView, NSVerticalRuler
37 NSTextView, NSRulerView, NSVerticalRuler
38
38
39 from pprint import saferepr
39 from pprint import saferepr
40
40
41 import IPython
41 import IPython
42 from IPython.kernel.engineservice import ThreadedEngineService
42 from IPython.kernel.engineservice import ThreadedEngineService
43 from IPython.frontend.frontendbase import AsynchronousFrontEndBase
43 from IPython.frontend.frontendbase import AsyncFrontEndBase
44
44
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
54 # ThreadedEngineService?
54 # ThreadedEngineService?
55 # 2. integrate Xgrid launching of engines
55 # 2. integrate Xgrid launching of engines
56
56
57 class AutoreleasePoolWrappedThreadedEngineService(ThreadedEngineService):
57 class AutoreleasePoolWrappedThreadedEngineService(ThreadedEngineService):
58 """Wrap all blocks in an NSAutoreleasePool"""
58 """Wrap all blocks in an NSAutoreleasePool"""
59
59
60 def wrapped_execute(self, msg, lines):
60 def wrapped_execute(self, msg, lines):
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 = self.shell.execute(lines)
65 except Exception,e:
65 except Exception,e:
66 # This gives the following:
66 # This gives the following:
67 # et=exception class
67 # et=exception class
68 # ev=exception class instance
68 # ev=exception class instance
69 # tb=traceback object
69 # tb=traceback object
70 et,ev,tb = sys.exc_info()
70 et,ev,tb = sys.exc_info()
71 # This call adds attributes to the exception value
71 # This call adds attributes to the exception value
72 et,ev,tb = self.shell.formatTraceback(et,ev,tb,msg)
72 et,ev,tb = self.shell.formatTraceback(et,ev,tb,msg)
73 # Add another attribute
73 # Add another attribute
74
74
75 # Create a new exception with the new attributes
75 # Create a new exception with the new attributes
76 e = et(ev._ipython_traceback_text)
76 e = et(ev._ipython_traceback_text)
77 e._ipython_engine_info = msg
77 e._ipython_engine_info = msg
78
78
79 # Re-raise
79 # Re-raise
80 raise e
80 raise e
81 finally:
81 finally:
82 p.drain()
82 p.drain()
83
83
84 return result
84 return result
85
85
86 def execute(self, lines):
86 def execute(self, lines):
87 # Only import this if we are going to use this class
87 # Only import this if we are going to use this class
88 from twisted.internet import threads
88 from twisted.internet import threads
89
89
90 msg = {'engineid':self.id,
90 msg = {'engineid':self.id,
91 'method':'execute',
91 'method':'execute',
92 'args':[lines]}
92 'args':[lines]}
93
93
94 d = threads.deferToThread(self.wrapped_execute, msg, lines)
94 d = threads.deferToThread(self.wrapped_execute, msg, lines)
95 d.addCallback(self.addIDToResult)
95 d.addCallback(self.addIDToResult)
96 return d
96 return d
97
97
98
98
99 class IPythonCocoaController(NSObject, AsynchronousFrontEndBase):
99 class IPythonCocoaController(NSObject, AsyncFrontEndBase):
100 userNS = objc.ivar() #mirror of engine.user_ns (key=>str(value))
100 userNS = objc.ivar() #mirror of engine.user_ns (key=>str(value))
101 waitingForEngine = objc.ivar().bool()
101 waitingForEngine = objc.ivar().bool()
102 textView = objc.IBOutlet()
102 textView = objc.IBOutlet()
103
103
104 def init(self):
104 def init(self):
105 self = super(IPythonCocoaController, self).init()
105 self = super(IPythonCocoaController, self).init()
106 AsynchronousFrontEndBase.__init__(self,
106 AsyncFrontEndBase.__init__(self,
107 engine=AutoreleasePoolWrappedThreadedEngineService())
107 engine=AutoreleasePoolWrappedThreadedEngineService())
108 if(self != None):
108 if(self != None):
109 self._common_init()
109 self._common_init()
110
110
111 return self
111 return self
112
112
113 def _common_init(self):
113 def _common_init(self):
114 """_common_init"""
114 """_common_init"""
115
115
116 self.userNS = NSMutableDictionary.dictionary()
116 self.userNS = NSMutableDictionary.dictionary()
117 self.waitingForEngine = False
117 self.waitingForEngine = False
118
118
119 self.lines = {}
119 self.lines = {}
120 self.tabSpaces = 4
120 self.tabSpaces = 4
121 self.tabUsesSpaces = True
121 self.tabUsesSpaces = True
122 self.currentBlockID = self.next_block_ID()
122 self.currentBlockID = self.next_block_ID()
123 self.blockRanges = {} # blockID=>NSRange
123 self.blockRanges = {} # blockID=>NSRange
124
124
125
125
126 def awakeFromNib(self):
126 def awakeFromNib(self):
127 """awakeFromNib"""
127 """awakeFromNib"""
128
128
129 self._common_init()
129 self._common_init()
130
130
131 # Start the IPython engine
131 # Start the IPython engine
132 self.engine.startService()
132 self.engine.startService()
133 NSLog('IPython engine started')
133 NSLog('IPython engine started')
134
134
135 # Register for app termination
135 # Register for app termination
136 nc = NSNotificationCenter.defaultCenter()
136 nc = NSNotificationCenter.defaultCenter()
137 nc.addObserver_selector_name_object_(
137 nc.addObserver_selector_name_object_(
138 self,
138 self,
139 'appWillTerminate:',
139 'appWillTerminate:',
140 NSApplicationWillTerminateNotification,
140 NSApplicationWillTerminateNotification,
141 None)
141 None)
142
142
143 self.textView.setDelegate_(self)
143 self.textView.setDelegate_(self)
144 self.textView.enclosingScrollView().setHasVerticalRuler_(True)
144 self.textView.enclosingScrollView().setHasVerticalRuler_(True)
145 r = NSRulerView.alloc().initWithScrollView_orientation_(
145 r = NSRulerView.alloc().initWithScrollView_orientation_(
146 self.textView.enclosingScrollView(),
146 self.textView.enclosingScrollView(),
147 NSVerticalRuler)
147 NSVerticalRuler)
148 self.verticalRulerView = r
148 self.verticalRulerView = r
149 self.verticalRulerView.setClientView_(self.textView)
149 self.verticalRulerView.setClientView_(self.textView)
150 self._start_cli_banner()
150 self._start_cli_banner()
151
151
152
152
153 def appWillTerminate_(self, notification):
153 def appWillTerminate_(self, notification):
154 """appWillTerminate"""
154 """appWillTerminate"""
155
155
156 self.engine.stopService()
156 self.engine.stopService()
157
157
158
158
159 def complete(self, token):
159 def complete(self, token):
160 """Complete token in engine's user_ns
160 """Complete token in engine's user_ns
161
161
162 Parameters
162 Parameters
163 ----------
163 ----------
164 token : string
164 token : string
165
165
166 Result
166 Result
167 ------
167 ------
168 Deferred result of
168 Deferred result of
169 IPython.kernel.engineservice.IEngineBase.complete
169 IPython.kernel.engineservice.IEngineBase.complete
170 """
170 """
171
171
172 return self.engine.complete(token)
172 return self.engine.complete(token)
173
173
174
174
175 def execute(self, block, blockID=None):
175 def execute(self, block, blockID=None):
176 self.waitingForEngine = True
176 self.waitingForEngine = True
177 self.willChangeValueForKey_('commandHistory')
177 self.willChangeValueForKey_('commandHistory')
178 d = super(IPythonCocoaController, self).execute(block,
178 d = super(IPythonCocoaController, self).execute(block,
179 blockID)
179 blockID)
180 d.addBoth(self._engine_done)
180 d.addBoth(self._engine_done)
181 d.addCallback(self._update_user_ns)
181 d.addCallback(self._update_user_ns)
182
182
183 return d
183 return d
184
184
185
185
186 def push_(self, namespace):
186 def push_(self, namespace):
187 """Push dictionary of key=>values to python namespace"""
187 """Push dictionary of key=>values to python namespace"""
188
188
189 self.waitingForEngine = True
189 self.waitingForEngine = True
190 self.willChangeValueForKey_('commandHistory')
190 self.willChangeValueForKey_('commandHistory')
191 d = self.engine.push(namespace)
191 d = self.engine.push(namespace)
192 d.addBoth(self._engine_done)
192 d.addBoth(self._engine_done)
193 d.addCallback(self._update_user_ns)
193 d.addCallback(self._update_user_ns)
194
194
195
195
196 def pull_(self, keys):
196 def pull_(self, keys):
197 """Pull keys from python namespace"""
197 """Pull keys from python namespace"""
198
198
199 self.waitingForEngine = True
199 self.waitingForEngine = True
200 result = blockingCallFromThread(self.engine.pull, keys)
200 result = blockingCallFromThread(self.engine.pull, keys)
201 self.waitingForEngine = False
201 self.waitingForEngine = False
202
202
203 def executeFileAtPath_(self, path):
203 def executeFileAtPath_(self, path):
204 """Execute file at path in an empty namespace. Update the engine
204 """Execute file at path in an empty namespace. Update the engine
205 user_ns with the resulting locals."""
205 user_ns with the resulting locals."""
206
206
207 lines,err = NSString.stringWithContentsOfFile_encoding_error_(
207 lines,err = NSString.stringWithContentsOfFile_encoding_error_(
208 path,
208 path,
209 NSString.defaultCStringEncoding(),
209 NSString.defaultCStringEncoding(),
210 None)
210 None)
211 self.engine.execute(lines)
211 self.engine.execute(lines)
212
212
213
213
214 def _engine_done(self, x):
214 def _engine_done(self, x):
215 self.waitingForEngine = False
215 self.waitingForEngine = False
216 self.didChangeValueForKey_('commandHistory')
216 self.didChangeValueForKey_('commandHistory')
217 return x
217 return x
218
218
219 def _update_user_ns(self, result):
219 def _update_user_ns(self, result):
220 """Update self.userNS from self.engine's namespace"""
220 """Update self.userNS from self.engine's namespace"""
221 d = self.engine.keys()
221 d = self.engine.keys()
222 d.addCallback(self._get_engine_namespace_values_for_keys)
222 d.addCallback(self._get_engine_namespace_values_for_keys)
223
223
224 return result
224 return result
225
225
226
226
227 def _get_engine_namespace_values_for_keys(self, keys):
227 def _get_engine_namespace_values_for_keys(self, keys):
228 d = self.engine.pull(keys)
228 d = self.engine.pull(keys)
229 d.addCallback(self._store_engine_namespace_values, keys=keys)
229 d.addCallback(self._store_engine_namespace_values, keys=keys)
230
230
231
231
232 def _store_engine_namespace_values(self, values, keys=[]):
232 def _store_engine_namespace_values(self, values, keys=[]):
233 assert(len(values) == len(keys))
233 assert(len(values) == len(keys))
234 self.willChangeValueForKey_('userNS')
234 self.willChangeValueForKey_('userNS')
235 for (k,v) in zip(keys,values):
235 for (k,v) in zip(keys,values):
236 self.userNS[k] = saferepr(v)
236 self.userNS[k] = saferepr(v)
237 self.didChangeValueForKey_('userNS')
237 self.didChangeValueForKey_('userNS')
238
238
239
239
240 def update_cell_prompt(self, result, blockID=None):
240 def update_cell_prompt(self, result, blockID=None):
241 if(isinstance(result, Failure)):
241 if(isinstance(result, Failure)):
242 self.insert_text(self.input_prompt(),
242 self.insert_text(self.input_prompt(),
243 textRange=NSMakeRange(self.blockRanges[blockID].location,0),
243 textRange=NSMakeRange(self.blockRanges[blockID].location,0),
244 scrollToVisible=False
244 scrollToVisible=False
245 )
245 )
246 else:
246 else:
247 self.insert_text(self.input_prompt(number=result['number']),
247 self.insert_text(self.input_prompt(number=result['number']),
248 textRange=NSMakeRange(self.blockRanges[blockID].location,0),
248 textRange=NSMakeRange(self.blockRanges[blockID].location,0),
249 scrollToVisible=False
249 scrollToVisible=False
250 )
250 )
251
251
252 return result
252 return result
253
253
254
254
255 def render_result(self, result):
255 def render_result(self, result):
256 blockID = result['blockID']
256 blockID = result['blockID']
257 inputRange = self.blockRanges[blockID]
257 inputRange = self.blockRanges[blockID]
258 del self.blockRanges[blockID]
258 del self.blockRanges[blockID]
259
259
260 #print inputRange,self.current_block_range()
260 #print inputRange,self.current_block_range()
261 self.insert_text('\n' +
261 self.insert_text('\n' +
262 self.output_prompt(number=result['number']) +
262 self.output_prompt(number=result['number']) +
263 result.get('display',{}).get('pprint','') +
263 result.get('display',{}).get('pprint','') +
264 '\n\n',
264 '\n\n',
265 textRange=NSMakeRange(inputRange.location+inputRange.length,
265 textRange=NSMakeRange(inputRange.location+inputRange.length,
266 0))
266 0))
267 return result
267 return result
268
268
269
269
270 def render_error(self, failure):
270 def render_error(self, failure):
271 self.insert_text('\n' +
271 self.insert_text('\n' +
272 self.output_prompt() +
272 self.output_prompt() +
273 '\n' +
273 '\n' +
274 failure.getErrorMessage() +
274 failure.getErrorMessage() +
275 '\n\n')
275 '\n\n')
276 self.start_new_block()
276 self.start_new_block()
277 return failure
277 return failure
278
278
279
279
280 def _start_cli_banner(self):
280 def _start_cli_banner(self):
281 """Print banner"""
281 """Print banner"""
282
282
283 banner = """IPython1 %s -- An enhanced Interactive Python.""" % \
283 banner = """IPython1 %s -- An enhanced Interactive Python.""" % \
284 IPython.__version__
284 IPython.__version__
285
285
286 self.insert_text(banner + '\n\n')
286 self.insert_text(banner + '\n\n')
287
287
288
288
289 def start_new_block(self):
289 def start_new_block(self):
290 """"""
290 """"""
291
291
292 self.currentBlockID = self.next_block_ID()
292 self.currentBlockID = self.next_block_ID()
293
293
294
294
295
295
296 def next_block_ID(self):
296 def next_block_ID(self):
297
297
298 return uuid.uuid4()
298 return uuid.uuid4()
299
299
300 def current_block_range(self):
300 def current_block_range(self):
301 return self.blockRanges.get(self.currentBlockID,
301 return self.blockRanges.get(self.currentBlockID,
302 NSMakeRange(self.textView.textStorage().length(),
302 NSMakeRange(self.textView.textStorage().length(),
303 0))
303 0))
304
304
305 def current_block(self):
305 def current_block(self):
306 """The current block's text"""
306 """The current block's text"""
307
307
308 return self.text_for_range(self.current_block_range())
308 return self.text_for_range(self.current_block_range())
309
309
310 def text_for_range(self, textRange):
310 def text_for_range(self, textRange):
311 """text_for_range"""
311 """text_for_range"""
312
312
313 ts = self.textView.textStorage()
313 ts = self.textView.textStorage()
314 return ts.string().substringWithRange_(textRange)
314 return ts.string().substringWithRange_(textRange)
315
315
316 def current_line(self):
316 def current_line(self):
317 block = self.text_for_range(self.current_block_range())
317 block = self.text_for_range(self.current_block_range())
318 block = block.split('\n')
318 block = block.split('\n')
319 return block[-1]
319 return block[-1]
320
320
321
321
322 def insert_text(self, string=None, textRange=None, scrollToVisible=True):
322 def insert_text(self, string=None, textRange=None, scrollToVisible=True):
323 """Insert text into textView at textRange, updating blockRanges
323 """Insert text into textView at textRange, updating blockRanges
324 as necessary
324 as necessary
325 """
325 """
326
326
327 if(textRange == None):
327 if(textRange == None):
328 #range for end of text
328 #range for end of text
329 textRange = NSMakeRange(self.textView.textStorage().length(), 0)
329 textRange = NSMakeRange(self.textView.textStorage().length(), 0)
330
330
331 for r in self.blockRanges.itervalues():
331 for r in self.blockRanges.itervalues():
332 intersection = NSIntersectionRange(r,textRange)
332 intersection = NSIntersectionRange(r,textRange)
333 if(intersection.length == 0): #ranges don't intersect
333 if(intersection.length == 0): #ranges don't intersect
334 if r.location >= textRange.location:
334 if r.location >= textRange.location:
335 r.location += len(string)
335 r.location += len(string)
336 else: #ranges intersect
336 else: #ranges intersect
337 if(r.location <= textRange.location):
337 if(r.location <= textRange.location):
338 assert(intersection.length == textRange.length)
338 assert(intersection.length == textRange.length)
339 r.length += textRange.length
339 r.length += textRange.length
340 else:
340 else:
341 r.location += intersection.length
341 r.location += intersection.length
342
342
343 self.textView.replaceCharactersInRange_withString_(
343 self.textView.replaceCharactersInRange_withString_(
344 textRange, string)
344 textRange, string)
345 self.textView.setSelectedRange_(
345 self.textView.setSelectedRange_(
346 NSMakeRange(textRange.location+len(string), 0))
346 NSMakeRange(textRange.location+len(string), 0))
347 if(scrollToVisible):
347 if(scrollToVisible):
348 self.textView.scrollRangeToVisible_(textRange)
348 self.textView.scrollRangeToVisible_(textRange)
349
349
350
350
351
351
352
352
353 def replace_current_block_with_string(self, textView, string):
353 def replace_current_block_with_string(self, textView, string):
354 textView.replaceCharactersInRange_withString_(
354 textView.replaceCharactersInRange_withString_(
355 self.current_block_range(),
355 self.current_block_range(),
356 string)
356 string)
357 self.current_block_range().length = len(string)
357 self.current_block_range().length = len(string)
358 r = NSMakeRange(textView.textStorage().length(), 0)
358 r = NSMakeRange(textView.textStorage().length(), 0)
359 textView.scrollRangeToVisible_(r)
359 textView.scrollRangeToVisible_(r)
360 textView.setSelectedRange_(r)
360 textView.setSelectedRange_(r)
361
361
362
362
363 def current_indent_string(self):
363 def current_indent_string(self):
364 """returns string for indent or None if no indent"""
364 """returns string for indent or None if no indent"""
365
365
366 return self._indent_for_block(self.current_block())
366 return self._indent_for_block(self.current_block())
367
367
368
368
369 def _indent_for_block(self, block):
369 def _indent_for_block(self, block):
370 lines = block.split('\n')
370 lines = block.split('\n')
371 if(len(lines) > 1):
371 if(len(lines) > 1):
372 currentIndent = len(lines[-1]) - len(lines[-1].lstrip())
372 currentIndent = len(lines[-1]) - len(lines[-1].lstrip())
373 if(currentIndent == 0):
373 if(currentIndent == 0):
374 currentIndent = self.tabSpaces
374 currentIndent = self.tabSpaces
375
375
376 if(self.tabUsesSpaces):
376 if(self.tabUsesSpaces):
377 result = ' ' * currentIndent
377 result = ' ' * currentIndent
378 else:
378 else:
379 result = '\t' * (currentIndent/self.tabSpaces)
379 result = '\t' * (currentIndent/self.tabSpaces)
380 else:
380 else:
381 result = None
381 result = None
382
382
383 return result
383 return result
384
384
385
385
386 # NSTextView delegate methods...
386 # NSTextView delegate methods...
387 def textView_doCommandBySelector_(self, textView, selector):
387 def textView_doCommandBySelector_(self, textView, selector):
388 assert(textView == self.textView)
388 assert(textView == self.textView)
389 NSLog("textView_doCommandBySelector_: "+selector)
389 NSLog("textView_doCommandBySelector_: "+selector)
390
390
391
391
392 if(selector == 'insertNewline:'):
392 if(selector == 'insertNewline:'):
393 indent = self.current_indent_string()
393 indent = self.current_indent_string()
394 if(indent):
394 if(indent):
395 line = indent + self.current_line()
395 line = indent + self.current_line()
396 else:
396 else:
397 line = self.current_line()
397 line = self.current_line()
398
398
399 if(self.is_complete(self.current_block())):
399 if(self.is_complete(self.current_block())):
400 self.execute(self.current_block(),
400 self.execute(self.current_block(),
401 blockID=self.currentBlockID)
401 blockID=self.currentBlockID)
402 self.start_new_block()
402 self.start_new_block()
403
403
404 return True
404 return True
405
405
406 return False
406 return False
407
407
408 elif(selector == 'moveUp:'):
408 elif(selector == 'moveUp:'):
409 prevBlock = self.get_history_previous(self.current_block())
409 prevBlock = self.get_history_previous(self.current_block())
410 if(prevBlock != None):
410 if(prevBlock != None):
411 self.replace_current_block_with_string(textView, prevBlock)
411 self.replace_current_block_with_string(textView, prevBlock)
412 else:
412 else:
413 NSBeep()
413 NSBeep()
414 return True
414 return True
415
415
416 elif(selector == 'moveDown:'):
416 elif(selector == 'moveDown:'):
417 nextBlock = self.get_history_next()
417 nextBlock = self.get_history_next()
418 if(nextBlock != None):
418 if(nextBlock != None):
419 self.replace_current_block_with_string(textView, nextBlock)
419 self.replace_current_block_with_string(textView, nextBlock)
420 else:
420 else:
421 NSBeep()
421 NSBeep()
422 return True
422 return True
423
423
424 elif(selector == 'moveToBeginningOfParagraph:'):
424 elif(selector == 'moveToBeginningOfParagraph:'):
425 textView.setSelectedRange_(NSMakeRange(
425 textView.setSelectedRange_(NSMakeRange(
426 self.current_block_range().location,
426 self.current_block_range().location,
427 0))
427 0))
428 return True
428 return True
429 elif(selector == 'moveToEndOfParagraph:'):
429 elif(selector == 'moveToEndOfParagraph:'):
430 textView.setSelectedRange_(NSMakeRange(
430 textView.setSelectedRange_(NSMakeRange(
431 self.current_block_range().location + \
431 self.current_block_range().location + \
432 self.current_block_range().length, 0))
432 self.current_block_range().length, 0))
433 return True
433 return True
434 elif(selector == 'deleteToEndOfParagraph:'):
434 elif(selector == 'deleteToEndOfParagraph:'):
435 if(textView.selectedRange().location <= \
435 if(textView.selectedRange().location <= \
436 self.current_block_range().location):
436 self.current_block_range().location):
437 # Intersect the selected range with the current line range
437 # Intersect the selected range with the current line range
438 if(self.current_block_range().length < 0):
438 if(self.current_block_range().length < 0):
439 self.blockRanges[self.currentBlockID].length = 0
439 self.blockRanges[self.currentBlockID].length = 0
440
440
441 r = NSIntersectionRange(textView.rangesForUserTextChange()[0],
441 r = NSIntersectionRange(textView.rangesForUserTextChange()[0],
442 self.current_block_range())
442 self.current_block_range())
443
443
444 if(r.length > 0): #no intersection
444 if(r.length > 0): #no intersection
445 textView.setSelectedRange_(r)
445 textView.setSelectedRange_(r)
446
446
447 return False # don't actually handle the delete
447 return False # don't actually handle the delete
448
448
449 elif(selector == 'insertTab:'):
449 elif(selector == 'insertTab:'):
450 if(len(self.current_line().strip()) == 0): #only white space
450 if(len(self.current_line().strip()) == 0): #only white space
451 return False
451 return False
452 else:
452 else:
453 self.textView.complete_(self)
453 self.textView.complete_(self)
454 return True
454 return True
455
455
456 elif(selector == 'deleteBackward:'):
456 elif(selector == 'deleteBackward:'):
457 #if we're at the beginning of the current block, ignore
457 #if we're at the beginning of the current block, ignore
458 if(textView.selectedRange().location == \
458 if(textView.selectedRange().location == \
459 self.current_block_range().location):
459 self.current_block_range().location):
460 return True
460 return True
461 else:
461 else:
462 self.current_block_range().length-=1
462 self.current_block_range().length-=1
463 return False
463 return False
464 return False
464 return False
465
465
466
466
467 def textView_shouldChangeTextInRanges_replacementStrings_(self,
467 def textView_shouldChangeTextInRanges_replacementStrings_(self,
468 textView, ranges, replacementStrings):
468 textView, ranges, replacementStrings):
469 """
469 """
470 Delegate method for NSTextView.
470 Delegate method for NSTextView.
471
471
472 Refuse change text in ranges not at end, but make those changes at
472 Refuse change text in ranges not at end, but make those changes at
473 end.
473 end.
474 """
474 """
475
475
476 assert(len(ranges) == len(replacementStrings))
476 assert(len(ranges) == len(replacementStrings))
477 allow = True
477 allow = True
478 for r,s in zip(ranges, replacementStrings):
478 for r,s in zip(ranges, replacementStrings):
479 r = r.rangeValue()
479 r = r.rangeValue()
480 if(textView.textStorage().length() > 0 and
480 if(textView.textStorage().length() > 0 and
481 r.location < self.current_block_range().location):
481 r.location < self.current_block_range().location):
482 self.insert_text(s)
482 self.insert_text(s)
483 allow = False
483 allow = False
484
484
485
485
486 self.blockRanges.setdefault(self.currentBlockID,
486 self.blockRanges.setdefault(self.currentBlockID,
487 self.current_block_range()).length +=\
487 self.current_block_range()).length +=\
488 len(s)
488 len(s)
489
489
490 return allow
490 return allow
491
491
492 def textView_completions_forPartialWordRange_indexOfSelectedItem_(self,
492 def textView_completions_forPartialWordRange_indexOfSelectedItem_(self,
493 textView, words, charRange, index):
493 textView, words, charRange, index):
494 try:
494 try:
495 ts = textView.textStorage()
495 ts = textView.textStorage()
496 token = ts.string().substringWithRange_(charRange)
496 token = ts.string().substringWithRange_(charRange)
497 completions = blockingCallFromThread(self.complete, token)
497 completions = blockingCallFromThread(self.complete, token)
498 except:
498 except:
499 completions = objc.nil
499 completions = objc.nil
500 NSBeep()
500 NSBeep()
501
501
502 return (completions,0)
502 return (completions,0)
503
503
504
504
@@ -1,256 +1,256 b''
1 // !$*UTF8*$!
1 // !$*UTF8*$!
2 {
2 {
3 archiveVersion = 1;
3 archiveVersion = 1;
4 classes = {
4 classes = {
5 };
5 };
6 objectVersion = 42;
6 objectVersion = 42;
7 objects = {
7 objects = {
8
8
9 /* Begin PBXContainerItemProxy section */
9 /* Begin PBXContainerItemProxy section */
10 4C5B7ADB0E1A0BCD006CB905 /* PBXContainerItemProxy */ = {
10 4C5B7ADB0E1A0BCD006CB905 /* PBXContainerItemProxy */ = {
11 isa = PBXContainerItemProxy;
11 isa = PBXContainerItemProxy;
12 containerPortal = 4C96F4FE0E199AB500B03430 /* Project object */;
12 containerPortal = 4C96F4FE0E199AB500B03430 /* Project object */;
13 proxyType = 1;
13 proxyType = 1;
14 remoteGlobalIDString = 4C96F50C0E199AF100B03430 /* Cocoa Frontend Plugin */;
14 remoteGlobalIDString = 4C96F50C0E199AF100B03430;
15 remoteInfo = "Cocoa Frontend Plugin";
15 remoteInfo = "Cocoa Frontend Plugin";
16 };
16 };
17 /* End PBXContainerItemProxy section */
17 /* End PBXContainerItemProxy section */
18
18
19 /* Begin PBXFileReference section */
19 /* Begin PBXFileReference section */
20 4C5B7A8D0E1A0B4C006CB905 /* Plugin-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "Plugin-Info.plist"; sourceTree = "<group>"; };
20 4C5B7A8D0E1A0B4C006CB905 /* Plugin-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "Plugin-Info.plist"; sourceTree = "<group>"; };
21 4C5B7AD30E1A0BC8006CB905 /* Placeholder (Do Not Use).bundle */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "Placeholder (Do Not Use).bundle"; sourceTree = BUILT_PRODUCTS_DIR; };
21 4C5B7AD30E1A0BC8006CB905 /* Placeholder (Do Not Use).bundle */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "Placeholder (Do Not Use).bundle"; sourceTree = BUILT_PRODUCTS_DIR; };
22 4C5B7AD40E1A0BC8006CB905 /* Placeholder (Do Not Use)-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "Placeholder (Do Not Use)-Info.plist"; sourceTree = "<group>"; };
22 4C5B7AD40E1A0BC8006CB905 /* Placeholder (Do Not Use)-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "Placeholder (Do Not Use)-Info.plist"; sourceTree = "<group>"; };
23 /* End PBXFileReference section */
23 /* End PBXFileReference section */
24
24
25 /* Begin PBXFrameworksBuildPhase section */
25 /* Begin PBXFrameworksBuildPhase section */
26 4C5B7AD10E1A0BC8006CB905 /* Frameworks */ = {
26 4C5B7AD10E1A0BC8006CB905 /* Frameworks */ = {
27 isa = PBXFrameworksBuildPhase;
27 isa = PBXFrameworksBuildPhase;
28 buildActionMask = 2147483647;
28 buildActionMask = 2147483647;
29 files = (
29 files = (
30 );
30 );
31 runOnlyForDeploymentPostprocessing = 0;
31 runOnlyForDeploymentPostprocessing = 0;
32 };
32 };
33 /* End PBXFrameworksBuildPhase section */
33 /* End PBXFrameworksBuildPhase section */
34
34
35 /* Begin PBXGroup section */
35 /* Begin PBXGroup section */
36 4C5B7A8C0E1A0B4C006CB905 /* Products */ = {
36 4C5B7A8C0E1A0B4C006CB905 /* Products */ = {
37 isa = PBXGroup;
37 isa = PBXGroup;
38 children = (
38 children = (
39 4C5B7AD30E1A0BC8006CB905 /* Placeholder (Do Not Use).bundle */,
39 4C5B7AD30E1A0BC8006CB905 /* Placeholder (Do Not Use).bundle */,
40 );
40 );
41 name = Products;
41 name = Products;
42 sourceTree = "<group>";
42 sourceTree = "<group>";
43 };
43 };
44 4C96F4FC0E199AB500B03430 = {
44 4C96F4FC0E199AB500B03430 = {
45 isa = PBXGroup;
45 isa = PBXGroup;
46 children = (
46 children = (
47 4C5B7A8C0E1A0B4C006CB905 /* Products */,
47 4C5B7A8C0E1A0B4C006CB905 /* Products */,
48 4C5B7A8D0E1A0B4C006CB905 /* Plugin-Info.plist */,
48 4C5B7A8D0E1A0B4C006CB905 /* Plugin-Info.plist */,
49 4C5B7AD40E1A0BC8006CB905 /* Placeholder (Do Not Use)-Info.plist */,
49 4C5B7AD40E1A0BC8006CB905 /* Placeholder (Do Not Use)-Info.plist */,
50 );
50 );
51 sourceTree = "<group>";
51 sourceTree = "<group>";
52 };
52 };
53 /* End PBXGroup section */
53 /* End PBXGroup section */
54
54
55 /* Begin PBXLegacyTarget section */
55 /* Begin PBXLegacyTarget section */
56 4C96F50C0E199AF100B03430 /* Cocoa Frontend Plugin */ = {
56 4C96F50C0E199AF100B03430 /* Cocoa Frontend Plugin */ = {
57 isa = PBXLegacyTarget;
57 isa = PBXLegacyTarget;
58 buildArgumentsString = "$(ACTION)";
58 buildArgumentsString = "$(ACTION)";
59 buildConfigurationList = 4C96F5110E199B3300B03430 /* Build configuration list for PBXLegacyTarget "Cocoa Frontend Plugin" */;
59 buildConfigurationList = 4C96F5110E199B3300B03430 /* Build configuration list for PBXLegacyTarget "Cocoa Frontend Plugin" */;
60 buildPhases = (
60 buildPhases = (
61 );
61 );
62 buildToolPath = /usr/bin/make;
62 buildToolPath = /usr/bin/make;
63 buildWorkingDirectory = "";
63 buildWorkingDirectory = "";
64 dependencies = (
64 dependencies = (
65 );
65 );
66 name = "Cocoa Frontend Plugin";
66 name = "Cocoa Frontend Plugin";
67 passBuildSettingsInEnvironment = 1;
67 passBuildSettingsInEnvironment = 1;
68 productName = "Cocoa Frontend Plugin";
68 productName = "Cocoa Frontend Plugin";
69 };
69 };
70 /* End PBXLegacyTarget section */
70 /* End PBXLegacyTarget section */
71
71
72 /* Begin PBXNativeTarget section */
72 /* Begin PBXNativeTarget section */
73 4C5B7AD20E1A0BC8006CB905 /* Placeholder (Do Not Use) */ = {
73 4C5B7AD20E1A0BC8006CB905 /* Placeholder (Do Not Use) */ = {
74 isa = PBXNativeTarget;
74 isa = PBXNativeTarget;
75 buildConfigurationList = 4C5B7ADA0E1A0BC9006CB905 /* Build configuration list for PBXNativeTarget "Placeholder (Do Not Use)" */;
75 buildConfigurationList = 4C5B7ADA0E1A0BC9006CB905 /* Build configuration list for PBXNativeTarget "Placeholder (Do Not Use)" */;
76 buildPhases = (
76 buildPhases = (
77 4C5B7ACF0E1A0BC8006CB905 /* Resources */,
77 4C5B7ACF0E1A0BC8006CB905 /* Resources */,
78 4C5B7AD00E1A0BC8006CB905 /* Sources */,
78 4C5B7AD00E1A0BC8006CB905 /* Sources */,
79 4C5B7AD10E1A0BC8006CB905 /* Frameworks */,
79 4C5B7AD10E1A0BC8006CB905 /* Frameworks */,
80 );
80 );
81 buildRules = (
81 buildRules = (
82 );
82 );
83 dependencies = (
83 dependencies = (
84 4C5B7ADC0E1A0BCD006CB905 /* PBXTargetDependency */,
84 4C5B7ADC0E1A0BCD006CB905 /* PBXTargetDependency */,
85 );
85 );
86 name = "Placeholder (Do Not Use)";
86 name = "Placeholder (Do Not Use)";
87 productName = "Placeholder (Do Not Use)";
87 productName = "Placeholder (Do Not Use)";
88 productReference = 4C5B7AD30E1A0BC8006CB905 /* Placeholder (Do Not Use).bundle */;
88 productReference = 4C5B7AD30E1A0BC8006CB905 /* Placeholder (Do Not Use).bundle */;
89 productType = "com.apple.product-type.bundle";
89 productType = "com.apple.product-type.bundle";
90 };
90 };
91 /* End PBXNativeTarget section */
91 /* End PBXNativeTarget section */
92
92
93 /* Begin PBXProject section */
93 /* Begin PBXProject section */
94 4C96F4FE0E199AB500B03430 /* Project object */ = {
94 4C96F4FE0E199AB500B03430 /* Project object */ = {
95 isa = PBXProject;
95 isa = PBXProject;
96 buildConfigurationList = 4C96F5010E199AB500B03430 /* Build configuration list for PBXProject "CocoaFrontendPlugin" */;
96 buildConfigurationList = 4C96F5010E199AB500B03430 /* Build configuration list for PBXProject "CocoaFrontendPlugin" */;
97 compatibilityVersion = "Xcode 2.4";
97 compatibilityVersion = "Xcode 2.4";
98 hasScannedForEncodings = 0;
98 hasScannedForEncodings = 0;
99 mainGroup = 4C96F4FC0E199AB500B03430;
99 mainGroup = 4C96F4FC0E199AB500B03430;
100 productRefGroup = 4C5B7A8C0E1A0B4C006CB905 /* Products */;
100 productRefGroup = 4C5B7A8C0E1A0B4C006CB905 /* Products */;
101 projectDirPath = "";
101 projectDirPath = "";
102 projectRoot = "";
102 projectRoot = "";
103 targets = (
103 targets = (
104 4C96F50C0E199AF100B03430 /* Cocoa Frontend Plugin */,
104 4C96F50C0E199AF100B03430 /* Cocoa Frontend Plugin */,
105 4C5B7AD20E1A0BC8006CB905 /* Placeholder (Do Not Use) */,
105 4C5B7AD20E1A0BC8006CB905 /* Placeholder (Do Not Use) */,
106 );
106 );
107 };
107 };
108 /* End PBXProject section */
108 /* End PBXProject section */
109
109
110 /* Begin PBXResourcesBuildPhase section */
110 /* Begin PBXResourcesBuildPhase section */
111 4C5B7ACF0E1A0BC8006CB905 /* Resources */ = {
111 4C5B7ACF0E1A0BC8006CB905 /* Resources */ = {
112 isa = PBXResourcesBuildPhase;
112 isa = PBXResourcesBuildPhase;
113 buildActionMask = 2147483647;
113 buildActionMask = 2147483647;
114 files = (
114 files = (
115 );
115 );
116 runOnlyForDeploymentPostprocessing = 0;
116 runOnlyForDeploymentPostprocessing = 0;
117 };
117 };
118 /* End PBXResourcesBuildPhase section */
118 /* End PBXResourcesBuildPhase section */
119
119
120 /* Begin PBXSourcesBuildPhase section */
120 /* Begin PBXSourcesBuildPhase section */
121 4C5B7AD00E1A0BC8006CB905 /* Sources */ = {
121 4C5B7AD00E1A0BC8006CB905 /* Sources */ = {
122 isa = PBXSourcesBuildPhase;
122 isa = PBXSourcesBuildPhase;
123 buildActionMask = 2147483647;
123 buildActionMask = 2147483647;
124 files = (
124 files = (
125 );
125 );
126 runOnlyForDeploymentPostprocessing = 0;
126 runOnlyForDeploymentPostprocessing = 0;
127 };
127 };
128 /* End PBXSourcesBuildPhase section */
128 /* End PBXSourcesBuildPhase section */
129
129
130 /* Begin PBXTargetDependency section */
130 /* Begin PBXTargetDependency section */
131 4C5B7ADC0E1A0BCD006CB905 /* PBXTargetDependency */ = {
131 4C5B7ADC0E1A0BCD006CB905 /* PBXTargetDependency */ = {
132 isa = PBXTargetDependency;
132 isa = PBXTargetDependency;
133 target = 4C96F50C0E199AF100B03430 /* Cocoa Frontend Plugin */;
133 target = 4C96F50C0E199AF100B03430 /* Cocoa Frontend Plugin */;
134 targetProxy = 4C5B7ADB0E1A0BCD006CB905 /* PBXContainerItemProxy */;
134 targetProxy = 4C5B7ADB0E1A0BCD006CB905 /* PBXContainerItemProxy */;
135 };
135 };
136 /* End PBXTargetDependency section */
136 /* End PBXTargetDependency section */
137
137
138 /* Begin XCBuildConfiguration section */
138 /* Begin XCBuildConfiguration section */
139 4C5B7AD50E1A0BC9006CB905 /* Debug */ = {
139 4C5B7AD50E1A0BC9006CB905 /* Debug */ = {
140 isa = XCBuildConfiguration;
140 isa = XCBuildConfiguration;
141 buildSettings = {
141 buildSettings = {
142 COPY_PHASE_STRIP = NO;
142 COPY_PHASE_STRIP = NO;
143 GCC_DYNAMIC_NO_PIC = NO;
143 GCC_DYNAMIC_NO_PIC = NO;
144 GCC_ENABLE_FIX_AND_CONTINUE = YES;
144 GCC_ENABLE_FIX_AND_CONTINUE = YES;
145 GCC_MODEL_TUNING = G5;
145 GCC_MODEL_TUNING = G5;
146 GCC_OPTIMIZATION_LEVEL = 0;
146 GCC_OPTIMIZATION_LEVEL = 0;
147 GCC_PRECOMPILE_PREFIX_HEADER = YES;
147 GCC_PRECOMPILE_PREFIX_HEADER = YES;
148 GCC_PREFIX_HEADER = "$(SYSTEM_LIBRARY_DIR)/Frameworks/AppKit.framework/Headers/AppKit.h";
148 GCC_PREFIX_HEADER = "$(SYSTEM_LIBRARY_DIR)/Frameworks/AppKit.framework/Headers/AppKit.h";
149 INFOPLIST_FILE = "Placeholder (Do Not Use)-Info.plist";
149 INFOPLIST_FILE = "Placeholder (Do Not Use)-Info.plist";
150 INSTALL_PATH = "$(HOME)/Library/Bundles";
150 INSTALL_PATH = "$(HOME)/Library/Bundles";
151 OTHER_LDFLAGS = (
151 OTHER_LDFLAGS = (
152 "-framework",
152 "-framework",
153 Foundation,
153 Foundation,
154 "-framework",
154 "-framework",
155 AppKit,
155 AppKit,
156 );
156 );
157 PREBINDING = NO;
157 PREBINDING = NO;
158 PRODUCT_NAME = "Placeholder (Do Not Use)";
158 PRODUCT_NAME = "Placeholder (Do Not Use)";
159 WRAPPER_EXTENSION = bundle;
159 WRAPPER_EXTENSION = bundle;
160 ZERO_LINK = YES;
160 ZERO_LINK = YES;
161 };
161 };
162 name = Debug;
162 name = Debug;
163 };
163 };
164 4C5B7AD60E1A0BC9006CB905 /* Release */ = {
164 4C5B7AD60E1A0BC9006CB905 /* Release */ = {
165 isa = XCBuildConfiguration;
165 isa = XCBuildConfiguration;
166 buildSettings = {
166 buildSettings = {
167 COPY_PHASE_STRIP = YES;
167 COPY_PHASE_STRIP = YES;
168 DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
168 DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
169 GCC_ENABLE_FIX_AND_CONTINUE = NO;
169 GCC_ENABLE_FIX_AND_CONTINUE = NO;
170 GCC_MODEL_TUNING = G5;
170 GCC_MODEL_TUNING = G5;
171 GCC_PRECOMPILE_PREFIX_HEADER = YES;
171 GCC_PRECOMPILE_PREFIX_HEADER = YES;
172 GCC_PREFIX_HEADER = "$(SYSTEM_LIBRARY_DIR)/Frameworks/AppKit.framework/Headers/AppKit.h";
172 GCC_PREFIX_HEADER = "$(SYSTEM_LIBRARY_DIR)/Frameworks/AppKit.framework/Headers/AppKit.h";
173 INFOPLIST_FILE = "Placeholder (Do Not Use)-Info.plist";
173 INFOPLIST_FILE = "Placeholder (Do Not Use)-Info.plist";
174 INSTALL_PATH = "$(HOME)/Library/Bundles";
174 INSTALL_PATH = "$(HOME)/Library/Bundles";
175 OTHER_LDFLAGS = (
175 OTHER_LDFLAGS = (
176 "-framework",
176 "-framework",
177 Foundation,
177 Foundation,
178 "-framework",
178 "-framework",
179 AppKit,
179 AppKit,
180 );
180 );
181 PREBINDING = NO;
181 PREBINDING = NO;
182 PRODUCT_NAME = "Placeholder (Do Not Use)";
182 PRODUCT_NAME = "Placeholder (Do Not Use)";
183 WRAPPER_EXTENSION = bundle;
183 WRAPPER_EXTENSION = bundle;
184 ZERO_LINK = NO;
184 ZERO_LINK = NO;
185 };
185 };
186 name = Release;
186 name = Release;
187 };
187 };
188 4C96F4FF0E199AB500B03430 /* Debug */ = {
188 4C96F4FF0E199AB500B03430 /* Debug */ = {
189 isa = XCBuildConfiguration;
189 isa = XCBuildConfiguration;
190 buildSettings = {
190 buildSettings = {
191 COPY_PHASE_STRIP = NO;
191 COPY_PHASE_STRIP = NO;
192 };
192 };
193 name = Debug;
193 name = Debug;
194 };
194 };
195 4C96F5000E199AB500B03430 /* Release */ = {
195 4C96F5000E199AB500B03430 /* Release */ = {
196 isa = XCBuildConfiguration;
196 isa = XCBuildConfiguration;
197 buildSettings = {
197 buildSettings = {
198 COPY_PHASE_STRIP = YES;
198 COPY_PHASE_STRIP = YES;
199 };
199 };
200 name = Release;
200 name = Release;
201 };
201 };
202 4C96F50D0E199AF100B03430 /* Debug */ = {
202 4C96F50D0E199AF100B03430 /* Debug */ = {
203 isa = XCBuildConfiguration;
203 isa = XCBuildConfiguration;
204 buildSettings = {
204 buildSettings = {
205 COPY_PHASE_STRIP = NO;
205 COPY_PHASE_STRIP = NO;
206 GCC_DYNAMIC_NO_PIC = NO;
206 GCC_DYNAMIC_NO_PIC = NO;
207 GCC_OPTIMIZATION_LEVEL = 0;
207 GCC_OPTIMIZATION_LEVEL = 0;
208 PRODUCT_NAME = "Cocoa Frontend Plugin";
208 PRODUCT_NAME = "Cocoa Frontend Plugin";
209 };
209 };
210 name = Debug;
210 name = Debug;
211 };
211 };
212 4C96F50E0E199AF100B03430 /* Release */ = {
212 4C96F50E0E199AF100B03430 /* Release */ = {
213 isa = XCBuildConfiguration;
213 isa = XCBuildConfiguration;
214 buildSettings = {
214 buildSettings = {
215 COPY_PHASE_STRIP = YES;
215 COPY_PHASE_STRIP = YES;
216 DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
216 DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
217 GCC_ENABLE_FIX_AND_CONTINUE = NO;
217 GCC_ENABLE_FIX_AND_CONTINUE = NO;
218 PRODUCT_NAME = "Cocoa Frontend Plugin";
218 PRODUCT_NAME = "Cocoa Frontend Plugin";
219 ZERO_LINK = NO;
219 ZERO_LINK = NO;
220 };
220 };
221 name = Release;
221 name = Release;
222 };
222 };
223 /* End XCBuildConfiguration section */
223 /* End XCBuildConfiguration section */
224
224
225 /* Begin XCConfigurationList section */
225 /* Begin XCConfigurationList section */
226 4C5B7ADA0E1A0BC9006CB905 /* Build configuration list for PBXNativeTarget "Placeholder (Do Not Use)" */ = {
226 4C5B7ADA0E1A0BC9006CB905 /* Build configuration list for PBXNativeTarget "Placeholder (Do Not Use)" */ = {
227 isa = XCConfigurationList;
227 isa = XCConfigurationList;
228 buildConfigurations = (
228 buildConfigurations = (
229 4C5B7AD50E1A0BC9006CB905 /* Debug */,
229 4C5B7AD50E1A0BC9006CB905 /* Debug */,
230 4C5B7AD60E1A0BC9006CB905 /* Release */,
230 4C5B7AD60E1A0BC9006CB905 /* Release */,
231 );
231 );
232 defaultConfigurationIsVisible = 0;
232 defaultConfigurationIsVisible = 0;
233 defaultConfigurationName = Release;
233 defaultConfigurationName = Release;
234 };
234 };
235 4C96F5010E199AB500B03430 /* Build configuration list for PBXProject "CocoaFrontendPlugin" */ = {
235 4C96F5010E199AB500B03430 /* Build configuration list for PBXProject "CocoaFrontendPlugin" */ = {
236 isa = XCConfigurationList;
236 isa = XCConfigurationList;
237 buildConfigurations = (
237 buildConfigurations = (
238 4C96F4FF0E199AB500B03430 /* Debug */,
238 4C96F4FF0E199AB500B03430 /* Debug */,
239 4C96F5000E199AB500B03430 /* Release */,
239 4C96F5000E199AB500B03430 /* Release */,
240 );
240 );
241 defaultConfigurationIsVisible = 0;
241 defaultConfigurationIsVisible = 0;
242 defaultConfigurationName = Release;
242 defaultConfigurationName = Release;
243 };
243 };
244 4C96F5110E199B3300B03430 /* Build configuration list for PBXLegacyTarget "Cocoa Frontend Plugin" */ = {
244 4C96F5110E199B3300B03430 /* Build configuration list for PBXLegacyTarget "Cocoa Frontend Plugin" */ = {
245 isa = XCConfigurationList;
245 isa = XCConfigurationList;
246 buildConfigurations = (
246 buildConfigurations = (
247 4C96F50D0E199AF100B03430 /* Debug */,
247 4C96F50D0E199AF100B03430 /* Debug */,
248 4C96F50E0E199AF100B03430 /* Release */,
248 4C96F50E0E199AF100B03430 /* Release */,
249 );
249 );
250 defaultConfigurationIsVisible = 0;
250 defaultConfigurationIsVisible = 0;
251 defaultConfigurationName = Release;
251 defaultConfigurationName = Release;
252 };
252 };
253 /* End XCConfigurationList section */
253 /* End XCConfigurationList section */
254 };
254 };
255 rootObject = 4C96F4FE0E199AB500B03430 /* Project object */;
255 rootObject = 4C96F4FE0E199AB500B03430 /* Project object */;
256 }
256 }
@@ -1,407 +1,407 b''
1 # encoding: utf-8
1 # encoding: utf-8
2 # -*- test-case-name: IPython.frontend.tests.test_frontendbase -*-
2 # -*- test-case-name: IPython.frontend.tests.test_frontendbase -*-
3 """
3 """
4 frontendbase provides an interface and base class for GUI frontends for
4 frontendbase provides an interface and base class for GUI frontends for
5 IPython.kernel/IPython.kernel.core.
5 IPython.kernel/IPython.kernel.core.
6
6
7 Frontend implementations will likely want to subclass FrontEndBase.
7 Frontend implementations will likely want to subclass FrontEndBase.
8
8
9 Author: Barry Wark
9 Author: Barry Wark
10 """
10 """
11 __docformat__ = "restructuredtext en"
11 __docformat__ = "restructuredtext en"
12
12
13 #-------------------------------------------------------------------------------
13 #-------------------------------------------------------------------------------
14 # Copyright (C) 2008 The IPython Development Team
14 # Copyright (C) 2008 The IPython Development Team
15 #
15 #
16 # Distributed under the terms of the BSD License. The full license is in
16 # Distributed under the terms of the BSD License. The full license is in
17 # the file COPYING, distributed as part of this software.
17 # the file COPYING, distributed as part of this software.
18 #-------------------------------------------------------------------------------
18 #-------------------------------------------------------------------------------
19
19
20 #-------------------------------------------------------------------------------
20 #-------------------------------------------------------------------------------
21 # Imports
21 # Imports
22 #-------------------------------------------------------------------------------
22 #-------------------------------------------------------------------------------
23 import string
23 import string
24 import uuid
24 import uuid
25 import _ast
25 import _ast
26
26
27 try:
27 try:
28 from zope.interface import Interface, Attribute, implements, classProvides
28 from zope.interface import Interface, Attribute, implements, classProvides
29 except ImportError:
29 except ImportError:
30 #zope.interface is not available
30 #zope.interface is not available
31 Interface = object
31 Interface = object
32 def Attribute(name, doc): pass
32 def Attribute(name, doc): pass
33 def implements(interface): pass
33 def implements(interface): pass
34 def classProvides(interface): pass
34 def classProvides(interface): pass
35
35
36 from IPython.kernel.core.history import FrontEndHistory
36 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:
40 try:
41 from twisted.python.failure import Failure
41 from twisted.python.failure import Failure
42 except ImportError:
42 except ImportError:
43 #Twisted not available
43 #Twisted not available
44 Failure = Exception
44 Failure = Exception
45
45
46 ##############################################################################
46 ##############################################################################
47 # TEMPORARY!!! fake configuration, while we decide whether to use tconfig or
47 # TEMPORARY!!! fake configuration, while we decide whether to use tconfig or
48 # not
48 # not
49
49
50 rc = Bunch()
50 rc = Bunch()
51 rc.prompt_in1 = r'In [$number]: '
51 rc.prompt_in1 = r'In [$number]: '
52 rc.prompt_in2 = r'...'
52 rc.prompt_in2 = r'...'
53 rc.prompt_out = r'Out [$number]: '
53 rc.prompt_out = r'Out [$number]: '
54
54
55 ##############################################################################
55 ##############################################################################
56
56
57 class IFrontEndFactory(Interface):
57 class IFrontEndFactory(Interface):
58 """Factory interface for frontends."""
58 """Factory interface for frontends."""
59
59
60 def __call__(engine=None, history=None):
60 def __call__(engine=None, history=None):
61 """
61 """
62 Parameters:
62 Parameters:
63 interpreter : IPython.kernel.engineservice.IEngineCore
63 interpreter : IPython.kernel.engineservice.IEngineCore
64 """
64 """
65
65
66 pass
66 pass
67
67
68
68
69
69
70 class IFrontEnd(Interface):
70 class IFrontEnd(Interface):
71 """Interface for frontends. All methods return t.i.d.Deferred"""
71 """Interface for frontends. All methods return t.i.d.Deferred"""
72
72
73 Attribute("input_prompt_template", "string.Template instance\
73 Attribute("input_prompt_template", "string.Template instance\
74 substituteable with execute result.")
74 substituteable with execute result.")
75 Attribute("output_prompt_template", "string.Template instance\
75 Attribute("output_prompt_template", "string.Template instance\
76 substituteable with execute result.")
76 substituteable with execute result.")
77 Attribute("continuation_prompt_template", "string.Template instance\
77 Attribute("continuation_prompt_template", "string.Template instance\
78 substituteable with execute result.")
78 substituteable with execute result.")
79
79
80 def update_cell_prompt(result, blockID=None):
80 def update_cell_prompt(result, blockID=None):
81 """Subclass may override to update the input prompt for a block.
81 """Subclass may override to update the input prompt for a block.
82 Since this method will be called as a
82 Since this method will be called as a
83 twisted.internet.defer.Deferred's callback/errback,
83 twisted.internet.defer.Deferred's callback/errback,
84 implementations should return result when finished.
84 implementations should return result when finished.
85
85
86 Result is a result dict in case of success, and a
86 Result is a result dict in case of success, and a
87 twisted.python.util.failure.Failure in case of an error
87 twisted.python.util.failure.Failure in case of an error
88 """
88 """
89
89
90 pass
90 pass
91
91
92
92
93 def render_result(result):
93 def render_result(result):
94 """Render the result of an execute call. Implementors may choose the
94 """Render the result of an execute call. Implementors may choose the
95 method of rendering.
95 method of rendering.
96 For example, a notebook-style frontend might render a Chaco plot
96 For example, a notebook-style frontend might render a Chaco plot
97 inline.
97 inline.
98
98
99 Parameters:
99 Parameters:
100 result : dict (result of IEngineBase.execute )
100 result : dict (result of IEngineBase.execute )
101 blockID = result['blockID']
101 blockID = result['blockID']
102
102
103 Result:
103 Result:
104 Output of frontend rendering
104 Output of frontend rendering
105 """
105 """
106
106
107 pass
107 pass
108
108
109 def render_error(failure):
109 def render_error(failure):
110 """Subclasses must override to render the failure. Since this method
110 """Subclasses must override to render the failure. Since this method
111 will be called as a twisted.internet.defer.Deferred's callback,
111 will be called as a twisted.internet.defer.Deferred's callback,
112 implementations should return result when finished.
112 implementations should return result when finished.
113
113
114 blockID = failure.blockID
114 blockID = failure.blockID
115 """
115 """
116
116
117 pass
117 pass
118
118
119
119
120 def input_prompt(number=''):
120 def input_prompt(number=''):
121 """Returns the input prompt by subsituting into
121 """Returns the input prompt by subsituting into
122 self.input_prompt_template
122 self.input_prompt_template
123 """
123 """
124 pass
124 pass
125
125
126 def output_prompt(number=''):
126 def output_prompt(number=''):
127 """Returns the output prompt by subsituting into
127 """Returns the output prompt by subsituting into
128 self.output_prompt_template
128 self.output_prompt_template
129 """
129 """
130
130
131 pass
131 pass
132
132
133 def continuation_prompt():
133 def continuation_prompt():
134 """Returns the continuation prompt by subsituting into
134 """Returns the continuation prompt by subsituting into
135 self.continuation_prompt_template
135 self.continuation_prompt_template
136 """
136 """
137
137
138 pass
138 pass
139
139
140 def is_complete(block):
140 def is_complete(block):
141 """Returns True if block is complete, False otherwise."""
141 """Returns True if block is complete, False otherwise."""
142
142
143 pass
143 pass
144
144
145 def compile_ast(block):
145 def compile_ast(block):
146 """Compiles block to an _ast.AST"""
146 """Compiles block to an _ast.AST"""
147
147
148 pass
148 pass
149
149
150
150
151 def get_history_previous(currentBlock):
151 def get_history_previous(currentBlock):
152 """Returns the block previous in the history. Saves currentBlock if
152 """Returns the block previous in the history. Saves currentBlock if
153 the history_cursor is currently at the end of the input history"""
153 the history_cursor is currently at the end of the input history"""
154 pass
154 pass
155
155
156 def get_history_next():
156 def get_history_next():
157 """Returns the next block in the history."""
157 """Returns the next block in the history."""
158
158
159 pass
159 pass
160
160
161
161
162 class FrontEndBase(object):
162 class FrontEndBase(object):
163 """
163 """
164 FrontEndBase manages the state tasks for a CLI frontend:
164 FrontEndBase manages the state tasks for a CLI frontend:
165 - Input and output history management
165 - Input and output history management
166 - Input/continuation and output prompt generation
166 - Input/continuation and output prompt generation
167
167
168 Some issues (due to possibly unavailable engine):
168 Some issues (due to possibly unavailable engine):
169 - How do we get the current cell number for the engine?
169 - How do we get the current cell number for the engine?
170 - How do we handle completions?
170 - How do we handle completions?
171 """
171 """
172
172
173 history_cursor = 0
173 history_cursor = 0
174
174
175 current_indent_level = 0
175 current_indent_level = 0
176
176
177
177
178 input_prompt_template = string.Template(rc.prompt_in1)
178 input_prompt_template = string.Template(rc.prompt_in1)
179 output_prompt_template = string.Template(rc.prompt_out)
179 output_prompt_template = string.Template(rc.prompt_out)
180 continuation_prompt_template = string.Template(rc.prompt_in2)
180 continuation_prompt_template = string.Template(rc.prompt_in2)
181
181
182 def __init__(self, shell=None, history=None):
182 def __init__(self, shell=None, history=None):
183 self.shell = shell
183 self.shell = shell
184 if history is None:
184 if history is None:
185 self.history = FrontEndHistory(input_cache=[''])
185 self.history = FrontEndHistory(input_cache=[''])
186 else:
186 else:
187 self.history = history
187 self.history = history
188
188
189
189
190 def input_prompt(self, number=''):
190 def input_prompt(self, number=''):
191 """Returns the current input prompt
191 """Returns the current input prompt
192
192
193 It would be great to use ipython1.core.prompts.Prompt1 here
193 It would be great to use ipython1.core.prompts.Prompt1 here
194 """
194 """
195 return self.input_prompt_template.safe_substitute({'number':number})
195 return self.input_prompt_template.safe_substitute({'number':number})
196
196
197
197
198 def continuation_prompt(self):
198 def continuation_prompt(self):
199 """Returns the current continuation prompt"""
199 """Returns the current continuation prompt"""
200
200
201 return self.continuation_prompt_template.safe_substitute()
201 return self.continuation_prompt_template.safe_substitute()
202
202
203 def output_prompt(self, number=''):
203 def output_prompt(self, number=''):
204 """Returns the output prompt for result"""
204 """Returns the output prompt for result"""
205
205
206 return self.output_prompt_template.safe_substitute({'number':number})
206 return self.output_prompt_template.safe_substitute({'number':number})
207
207
208
208
209 def is_complete(self, block):
209 def is_complete(self, block):
210 """Determine if block is complete.
210 """Determine if block is complete.
211
211
212 Parameters
212 Parameters
213 block : string
213 block : string
214
214
215 Result
215 Result
216 True if block can be sent to the engine without compile errors.
216 True if block can be sent to the engine without compile errors.
217 False otherwise.
217 False otherwise.
218 """
218 """
219
219
220 try:
220 try:
221 ast = self.compile_ast(block)
221 ast = self.compile_ast(block)
222 except:
222 except:
223 return False
223 return False
224
224
225 lines = block.split('\n')
225 lines = block.split('\n')
226 return (len(lines)==1 or str(lines[-1])=='')
226 return (len(lines)==1 or str(lines[-1])=='')
227
227
228
228
229 def compile_ast(self, block):
229 def compile_ast(self, block):
230 """Compile block to an AST
230 """Compile block to an AST
231
231
232 Parameters:
232 Parameters:
233 block : str
233 block : str
234
234
235 Result:
235 Result:
236 AST
236 AST
237
237
238 Throws:
238 Throws:
239 Exception if block cannot be compiled
239 Exception if block cannot be compiled
240 """
240 """
241
241
242 return compile(block, "<string>", "exec", _ast.PyCF_ONLY_AST)
242 return compile(block, "<string>", "exec", _ast.PyCF_ONLY_AST)
243
243
244
244
245 def execute(self, block, blockID=None):
245 def execute(self, block, blockID=None):
246 """Execute the block and return the result.
246 """Execute the block and return the result.
247
247
248 Parameters:
248 Parameters:
249 block : {str, AST}
249 block : {str, AST}
250 blockID : any
250 blockID : any
251 Caller may provide an ID to identify this block.
251 Caller may provide an ID to identify this block.
252 result['blockID'] := blockID
252 result['blockID'] := blockID
253
253
254 Result:
254 Result:
255 Deferred result of self.interpreter.execute
255 Deferred result of self.interpreter.execute
256 """
256 """
257
257
258 if(not self.is_complete(block)):
258 if(not self.is_complete(block)):
259 raise Exception("Block is not compilable")
259 raise Exception("Block is not compilable")
260
260
261 if(blockID == None):
261 if(blockID == None):
262 blockID = uuid.uuid4() #random UUID
262 blockID = uuid.uuid4() #random UUID
263
263
264 try:
264 try:
265 result = self.shell.execute(block)
265 result = self.shell.execute(block)
266 except Exception,e:
266 except Exception,e:
267 e = self._add_block_id_for_failure(e, blockID=blockID)
267 e = self._add_block_id_for_failure(e, blockID=blockID)
268 e = self.update_cell_prompt(e, blockID=blockID)
268 e = self.update_cell_prompt(e, blockID=blockID)
269 e = self.render_error(e)
269 e = self.render_error(e)
270 else:
270 else:
271 result = self._add_block_id_for_result(result, blockID=blockID)
271 result = self._add_block_id_for_result(result, blockID=blockID)
272 result = self.update_cell_prompt(result, blockID=blockID)
272 result = self.update_cell_prompt(result, blockID=blockID)
273 result = self.render_result(result)
273 result = self.render_result(result)
274
274
275 return result
275 return result
276
276
277
277
278 def _add_block_id_for_result(self, result, blockID):
278 def _add_block_id_for_result(self, result, blockID):
279 """Add the blockID to result or failure. Unfortunatley, we have to
279 """Add the blockID to result or failure. Unfortunatley, we have to
280 treat failures differently than result dicts.
280 treat failures differently than result dicts.
281 """
281 """
282
282
283 result['blockID'] = blockID
283 result['blockID'] = blockID
284
284
285 return result
285 return result
286
286
287 def _add_block_id_for_failure(self, failure, blockID):
287 def _add_block_id_for_failure(self, failure, blockID):
288 """_add_block_id_for_failure"""
288 """_add_block_id_for_failure"""
289
289
290 failure.blockID = blockID
290 failure.blockID = blockID
291 return failure
291 return failure
292
292
293
293
294 def _add_history(self, result, block=None):
294 def _add_history(self, result, block=None):
295 """Add block to the history"""
295 """Add block to the history"""
296
296
297 assert(block != None)
297 assert(block != None)
298 self.history.add_items([block])
298 self.history.add_items([block])
299 self.history_cursor += 1
299 self.history_cursor += 1
300
300
301 return result
301 return result
302
302
303
303
304 def get_history_previous(self, currentBlock):
304 def get_history_previous(self, currentBlock):
305 """ Returns previous history string and decrement history cursor.
305 """ Returns previous history string and decrement history cursor.
306 """
306 """
307 command = self.history.get_history_item(self.history_cursor - 1)
307 command = self.history.get_history_item(self.history_cursor - 1)
308
308
309 if command is not None:
309 if command is not None:
310 if(self.history_cursor == len(self.history.input_cache)):
310 if(self.history_cursor == len(self.history.input_cache)):
311 self.history.input_cache[self.history_cursor] = currentBlock
311 self.history.input_cache[self.history_cursor] = currentBlock
312 self.history_cursor -= 1
312 self.history_cursor -= 1
313 return command
313 return command
314
314
315
315
316 def get_history_next(self):
316 def get_history_next(self):
317 """ Returns next history string and increment history cursor.
317 """ Returns next history string and increment history cursor.
318 """
318 """
319 command = self.history.get_history_item(self.history_cursor+1)
319 command = self.history.get_history_item(self.history_cursor+1)
320
320
321 if command is not None:
321 if command is not None:
322 self.history_cursor += 1
322 self.history_cursor += 1
323 return command
323 return command
324
324
325 ###
325 ###
326 # Subclasses probably want to override these methods...
326 # Subclasses probably want to override these methods...
327 ###
327 ###
328
328
329 def update_cell_prompt(self, result, blockID=None):
329 def update_cell_prompt(self, result, blockID=None):
330 """Subclass may override to update the input prompt for a block.
330 """Subclass may override to update the input prompt for a block.
331 Since this method will be called as a
331 Since this method will be called as a
332 twisted.internet.defer.Deferred's callback, implementations should
332 twisted.internet.defer.Deferred's callback, implementations should
333 return result when finished.
333 return result when finished.
334 """
334 """
335
335
336 return result
336 return result
337
337
338
338
339 def render_result(self, result):
339 def render_result(self, result):
340 """Subclasses must override to render result. Since this method will
340 """Subclasses must override to render result. Since this method will
341 be called as a twisted.internet.defer.Deferred's callback,
341 be called as a twisted.internet.defer.Deferred's callback,
342 implementations should return result when finished.
342 implementations should return result when finished.
343 """
343 """
344
344
345 return result
345 return result
346
346
347
347
348 def render_error(self, failure):
348 def render_error(self, failure):
349 """Subclasses must override to render the failure. Since this method
349 """Subclasses must override to render the failure. Since this method
350 will be called as a twisted.internet.defer.Deferred's callback,
350 will be called as a twisted.internet.defer.Deferred's callback,
351 implementations should return result when finished.
351 implementations should return result when finished.
352 """
352 """
353
353
354 return failure
354 return failure
355
355
356
356
357
357
358 class AsynchronousFrontEndBase(FrontEndBase):
358 class AsyncFrontEndBase(FrontEndBase):
359 """
359 """
360 Overrides FrontEndBase to wrap execute in a deferred result.
360 Overrides FrontEndBase to wrap execute in a deferred result.
361 All callbacks are made as callbacks on the deferred result.
361 All callbacks are made as callbacks on the deferred result.
362 """
362 """
363
363
364 implements(IFrontEnd)
364 implements(IFrontEnd)
365 classProvides(IFrontEndFactory)
365 classProvides(IFrontEndFactory)
366
366
367 def __init__(self, engine=None, history=None):
367 def __init__(self, engine=None, history=None):
368 assert(engine==None or IEngineCore.providedBy(engine))
368 assert(engine==None or IEngineCore.providedBy(engine))
369 self.engine = IEngineCore(engine)
369 self.engine = IEngineCore(engine)
370 if history is None:
370 if history is None:
371 self.history = FrontEndHistory(input_cache=[''])
371 self.history = FrontEndHistory(input_cache=[''])
372 else:
372 else:
373 self.history = history
373 self.history = history
374
374
375
375
376 def execute(self, block, blockID=None):
376 def execute(self, block, blockID=None):
377 """Execute the block and return the deferred result.
377 """Execute the block and return the deferred result.
378
378
379 Parameters:
379 Parameters:
380 block : {str, AST}
380 block : {str, AST}
381 blockID : any
381 blockID : any
382 Caller may provide an ID to identify this block.
382 Caller may provide an ID to identify this block.
383 result['blockID'] := blockID
383 result['blockID'] := blockID
384
384
385 Result:
385 Result:
386 Deferred result of self.interpreter.execute
386 Deferred result of self.interpreter.execute
387 """
387 """
388
388
389 if(not self.is_complete(block)):
389 if(not self.is_complete(block)):
390 return Failure(Exception("Block is not compilable"))
390 return Failure(Exception("Block is not compilable"))
391
391
392 if(blockID == None):
392 if(blockID == None):
393 blockID = uuid.uuid4() #random UUID
393 blockID = uuid.uuid4() #random UUID
394
394
395 d = self.engine.execute(block)
395 d = self.engine.execute(block)
396 d.addCallback(self._add_history, block=block)
396 d.addCallback(self._add_history, block=block)
397 d.addCallbacks(self._add_block_id_for_result,
397 d.addCallbacks(self._add_block_id_for_result,
398 errback=self._add_block_id_for_failure,
398 errback=self._add_block_id_for_failure,
399 callbackArgs=(blockID,),
399 callbackArgs=(blockID,),
400 errbackArgs=(blockID,))
400 errbackArgs=(blockID,))
401 d.addBoth(self.update_cell_prompt, blockID=blockID)
401 d.addBoth(self.update_cell_prompt, blockID=blockID)
402 d.addCallbacks(self.render_result,
402 d.addCallbacks(self.render_result,
403 errback=self.render_error)
403 errback=self.render_error)
404
404
405 return d
405 return d
406
406
407
407
@@ -1,151 +1,151 b''
1 # encoding: utf-8
1 # encoding: utf-8
2
2
3 """This file contains unittests for the frontendbase module."""
3 """This file contains unittests for the frontendbase module."""
4
4
5 __docformat__ = "restructuredtext en"
5 __docformat__ = "restructuredtext en"
6
6
7 #---------------------------------------------------------------------------
7 #---------------------------------------------------------------------------
8 # Copyright (C) 2008 The IPython Development Team
8 # Copyright (C) 2008 The IPython Development Team
9 #
9 #
10 # Distributed under the terms of the BSD License. The full license is in
10 # Distributed under the terms of the BSD License. The full license is in
11 # the file COPYING, distributed as part of this software.
11 # the file COPYING, distributed as part of this software.
12 #---------------------------------------------------------------------------
12 #---------------------------------------------------------------------------
13
13
14 #---------------------------------------------------------------------------
14 #---------------------------------------------------------------------------
15 # Imports
15 # Imports
16 #---------------------------------------------------------------------------
16 #---------------------------------------------------------------------------
17
17
18 import unittest
18 import unittest
19 from IPython.frontend import frontendbase
19 from IPython.frontend import frontendbase
20 from IPython.kernel.engineservice import EngineService
20 from IPython.kernel.engineservice import EngineService
21
21
22 class FrontEndCallbackChecker(frontendbase.AsynchronousFrontEndBase):
22 class FrontEndCallbackChecker(frontendbase.AsyncFrontEndBase):
23 """FrontEndBase subclass for checking callbacks"""
23 """FrontEndBase subclass for checking callbacks"""
24 def __init__(self, engine=None, history=None):
24 def __init__(self, engine=None, history=None):
25 super(FrontEndCallbackChecker, self).__init__(engine=engine,
25 super(FrontEndCallbackChecker, self).__init__(engine=engine,
26 history=history)
26 history=history)
27 self.updateCalled = False
27 self.updateCalled = False
28 self.renderResultCalled = False
28 self.renderResultCalled = False
29 self.renderErrorCalled = False
29 self.renderErrorCalled = False
30
30
31 def update_cell_prompt(self, result, blockID=None):
31 def update_cell_prompt(self, result, blockID=None):
32 self.updateCalled = True
32 self.updateCalled = True
33 return result
33 return result
34
34
35 def render_result(self, result):
35 def render_result(self, result):
36 self.renderResultCalled = True
36 self.renderResultCalled = True
37 return result
37 return result
38
38
39
39
40 def render_error(self, failure):
40 def render_error(self, failure):
41 self.renderErrorCalled = True
41 self.renderErrorCalled = True
42 return failure
42 return failure
43
43
44
44
45
45
46
46
47 class TestAsynchronousFrontendBase(unittest.TestCase):
47 class TestAsyncFrontendBase(unittest.TestCase):
48 def setUp(self):
48 def setUp(self):
49 """Setup the EngineService and FrontEndBase"""
49 """Setup the EngineService and FrontEndBase"""
50
50
51 self.fb = FrontEndCallbackChecker(engine=EngineService())
51 self.fb = FrontEndCallbackChecker(engine=EngineService())
52
52
53
53
54 def test_implements_IFrontEnd(self):
54 def test_implements_IFrontEnd(self):
55 assert(frontendbase.IFrontEnd.implementedBy(
55 assert(frontendbase.IFrontEnd.implementedBy(
56 frontendbase.AsynchronousFrontEndBase))
56 frontendbase.AsyncFrontEndBase))
57
57
58
58
59 def test_is_complete_returns_False_for_incomplete_block(self):
59 def test_is_complete_returns_False_for_incomplete_block(self):
60 """"""
60 """"""
61
61
62 block = """def test(a):"""
62 block = """def test(a):"""
63
63
64 assert(self.fb.is_complete(block) == False)
64 assert(self.fb.is_complete(block) == False)
65
65
66 def test_is_complete_returns_True_for_complete_block(self):
66 def test_is_complete_returns_True_for_complete_block(self):
67 """"""
67 """"""
68
68
69 block = """def test(a): pass"""
69 block = """def test(a): pass"""
70
70
71 assert(self.fb.is_complete(block))
71 assert(self.fb.is_complete(block))
72
72
73 block = """a=3"""
73 block = """a=3"""
74
74
75 assert(self.fb.is_complete(block))
75 assert(self.fb.is_complete(block))
76
76
77
77
78 def test_blockID_added_to_result(self):
78 def test_blockID_added_to_result(self):
79 block = """3+3"""
79 block = """3+3"""
80
80
81 d = self.fb.execute(block, blockID='TEST_ID')
81 d = self.fb.execute(block, blockID='TEST_ID')
82
82
83 d.addCallback(self.checkBlockID, expected='TEST_ID')
83 d.addCallback(self.checkBlockID, expected='TEST_ID')
84
84
85 def test_blockID_added_to_failure(self):
85 def test_blockID_added_to_failure(self):
86 block = "raise Exception()"
86 block = "raise Exception()"
87
87
88 d = self.fb.execute(block,blockID='TEST_ID')
88 d = self.fb.execute(block,blockID='TEST_ID')
89 d.addErrback(self.checkFailureID, expected='TEST_ID')
89 d.addErrback(self.checkFailureID, expected='TEST_ID')
90
90
91 def checkBlockID(self, result, expected=""):
91 def checkBlockID(self, result, expected=""):
92 assert(result['blockID'] == expected)
92 assert(result['blockID'] == expected)
93
93
94
94
95 def checkFailureID(self, failure, expected=""):
95 def checkFailureID(self, failure, expected=""):
96 assert(failure.blockID == expected)
96 assert(failure.blockID == expected)
97
97
98
98
99 def test_callbacks_added_to_execute(self):
99 def test_callbacks_added_to_execute(self):
100 """test that
100 """test that
101 update_cell_prompt
101 update_cell_prompt
102 render_result
102 render_result
103
103
104 are added to execute request
104 are added to execute request
105 """
105 """
106
106
107 d = self.fb.execute("10+10")
107 d = self.fb.execute("10+10")
108 d.addCallback(self.checkCallbacks)
108 d.addCallback(self.checkCallbacks)
109
109
110
110
111 def checkCallbacks(self, result):
111 def checkCallbacks(self, result):
112 assert(self.fb.updateCalled)
112 assert(self.fb.updateCalled)
113 assert(self.fb.renderResultCalled)
113 assert(self.fb.renderResultCalled)
114
114
115
115
116 def test_error_callback_added_to_execute(self):
116 def test_error_callback_added_to_execute(self):
117 """test that render_error called on execution error"""
117 """test that render_error called on execution error"""
118
118
119 d = self.fb.execute("raise Exception()")
119 d = self.fb.execute("raise Exception()")
120 d.addCallback(self.checkRenderError)
120 d.addCallback(self.checkRenderError)
121
121
122 def checkRenderError(self, result):
122 def checkRenderError(self, result):
123 assert(self.fb.renderErrorCalled)
123 assert(self.fb.renderErrorCalled)
124
124
125 def test_history_returns_expected_block(self):
125 def test_history_returns_expected_block(self):
126 """Make sure history browsing doesn't fail"""
126 """Make sure history browsing doesn't fail"""
127
127
128 blocks = ["a=1","a=2","a=3"]
128 blocks = ["a=1","a=2","a=3"]
129 for b in blocks:
129 for b in blocks:
130 d = self.fb.execute(b)
130 d = self.fb.execute(b)
131
131
132 # d is now the deferred for the last executed block
132 # d is now the deferred for the last executed block
133 d.addCallback(self.historyTests, blocks)
133 d.addCallback(self.historyTests, blocks)
134
134
135
135
136 def historyTests(self, result, blocks):
136 def historyTests(self, result, blocks):
137 """historyTests"""
137 """historyTests"""
138
138
139 assert(len(blocks) >= 3)
139 assert(len(blocks) >= 3)
140 assert(self.fb.get_history_previous("") == blocks[-2])
140 assert(self.fb.get_history_previous("") == blocks[-2])
141 assert(self.fb.get_history_previous("") == blocks[-3])
141 assert(self.fb.get_history_previous("") == blocks[-3])
142 assert(self.fb.get_history_next() == blocks[-2])
142 assert(self.fb.get_history_next() == blocks[-2])
143
143
144
144
145 def test_history_returns_none_at_startup(self):
145 def test_history_returns_none_at_startup(self):
146 """test_history_returns_none_at_startup"""
146 """test_history_returns_none_at_startup"""
147
147
148 assert(self.fb.get_history_previous("")==None)
148 assert(self.fb.get_history_previous("")==None)
149 assert(self.fb.get_history_next()==None)
149 assert(self.fb.get_history_next()==None)
150
150
151
151
General Comments 0
You need to be logged in to leave comments. Login now