##// END OF EJS Templates
fix for cocoa frontend current_indent_string; refactor to make testing easier and added a test for _indent_for_block
Barry Wark -
Show More
@@ -1,425 +1,429 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 objc
27 import objc
28 import uuid
28 import uuid
29
29
30 from Foundation import NSObject, NSMutableArray, NSMutableDictionary,\
30 from Foundation import NSObject, NSMutableArray, NSMutableDictionary,\
31 NSLog, NSNotificationCenter, NSMakeRange,\
31 NSLog, NSNotificationCenter, NSMakeRange,\
32 NSLocalizedString, NSIntersectionRange
32 NSLocalizedString, NSIntersectionRange
33
33
34 from AppKit import NSApplicationWillTerminateNotification, NSBeep,\
34 from AppKit import NSApplicationWillTerminateNotification, NSBeep,\
35 NSTextView, NSRulerView, NSVerticalRuler
35 NSTextView, NSRulerView, NSVerticalRuler
36
36
37 from pprint import saferepr
37 from pprint import saferepr
38
38
39 import IPython
39 import IPython
40 from IPython.kernel.engineservice import ThreadedEngineService
40 from IPython.kernel.engineservice import ThreadedEngineService
41 from IPython.frontend.frontendbase import FrontEndBase
41 from IPython.frontend.frontendbase import FrontEndBase
42
42
43 from twisted.internet.threads import blockingCallFromThread
43 from twisted.internet.threads import blockingCallFromThread
44 from twisted.python.failure import Failure
44 from twisted.python.failure import Failure
45
45
46 #------------------------------------------------------------------------------
46 #------------------------------------------------------------------------------
47 # Classes to implement the Cocoa frontend
47 # Classes to implement the Cocoa frontend
48 #------------------------------------------------------------------------------
48 #------------------------------------------------------------------------------
49
49
50 # TODO:
50 # TODO:
51 # 1. use MultiEngineClient and out-of-process engine rather than
51 # 1. use MultiEngineClient and out-of-process engine rather than
52 # ThreadedEngineService?
52 # ThreadedEngineService?
53 # 2. integrate Xgrid launching of engines
53 # 2. integrate Xgrid launching of engines
54
54
55
55
56
56
57
57
58 class IPythonCocoaController(NSObject, FrontEndBase):
58 class IPythonCocoaController(NSObject, FrontEndBase):
59 userNS = objc.ivar() #mirror of engine.user_ns (key=>str(value))
59 userNS = objc.ivar() #mirror of engine.user_ns (key=>str(value))
60 waitingForEngine = objc.ivar().bool()
60 waitingForEngine = objc.ivar().bool()
61 textView = objc.IBOutlet()
61 textView = objc.IBOutlet()
62
62
63 def init(self):
63 def init(self):
64 self = super(IPythonCocoaController, self).init()
64 self = super(IPythonCocoaController, self).init()
65 FrontEndBase.__init__(self, engine=ThreadedEngineService())
65 FrontEndBase.__init__(self, engine=ThreadedEngineService())
66 if(self != None):
66 if(self != None):
67 self._common_init()
67 self._common_init()
68
68
69 return self
69 return self
70
70
71 def _common_init(self):
71 def _common_init(self):
72 """_common_init"""
72 """_common_init"""
73
73
74 self.userNS = NSMutableDictionary.dictionary()
74 self.userNS = NSMutableDictionary.dictionary()
75 self.waitingForEngine = False
75 self.waitingForEngine = False
76
76
77 self.lines = {}
77 self.lines = {}
78 self.tabSpaces = 4
78 self.tabSpaces = 4
79 self.tabUsesSpaces = True
79 self.tabUsesSpaces = True
80 self.currentBlockID = self.next_block_ID()
80 self.currentBlockID = self.next_block_ID()
81 self.blockRanges = {} # blockID=>NSRange
81 self.blockRanges = {} # blockID=>NSRange
82
82
83
83
84 def awakeFromNib(self):
84 def awakeFromNib(self):
85 """awakeFromNib"""
85 """awakeFromNib"""
86
86
87 self._common_init()
87 self._common_init()
88
88
89 # Start the IPython engine
89 # Start the IPython engine
90 self.engine.startService()
90 self.engine.startService()
91 NSLog('IPython engine started')
91 NSLog('IPython engine started')
92
92
93 # Register for app termination
93 # Register for app termination
94 nc = NSNotificationCenter.defaultCenter()
94 nc = NSNotificationCenter.defaultCenter()
95 nc.addObserver_selector_name_object_(
95 nc.addObserver_selector_name_object_(
96 self,
96 self,
97 'appWillTerminate:',
97 'appWillTerminate:',
98 NSApplicationWillTerminateNotification,
98 NSApplicationWillTerminateNotification,
99 None)
99 None)
100
100
101 self.textView.setDelegate_(self)
101 self.textView.setDelegate_(self)
102 self.textView.enclosingScrollView().setHasVerticalRuler_(True)
102 self.textView.enclosingScrollView().setHasVerticalRuler_(True)
103 r = NSRulerView.alloc().initWithScrollView_orientation_(
103 r = NSRulerView.alloc().initWithScrollView_orientation_(
104 self.textView.enclosingScrollView(),
104 self.textView.enclosingScrollView(),
105 NSVerticalRuler)
105 NSVerticalRuler)
106 self.verticalRulerView = r
106 self.verticalRulerView = r
107 self.verticalRulerView.setClientView_(self.textView)
107 self.verticalRulerView.setClientView_(self.textView)
108 self._start_cli_banner()
108 self._start_cli_banner()
109
109
110
110
111 def appWillTerminate_(self, notification):
111 def appWillTerminate_(self, notification):
112 """appWillTerminate"""
112 """appWillTerminate"""
113
113
114 self.engine.stopService()
114 self.engine.stopService()
115
115
116
116
117 def complete(self, token):
117 def complete(self, token):
118 """Complete token in engine's user_ns
118 """Complete token in engine's user_ns
119
119
120 Parameters
120 Parameters
121 ----------
121 ----------
122 token : string
122 token : string
123
123
124 Result
124 Result
125 ------
125 ------
126 Deferred result of
126 Deferred result of
127 IPython.kernel.engineservice.IEngineBase.complete
127 IPython.kernel.engineservice.IEngineBase.complete
128 """
128 """
129
129
130 return self.engine.complete(token)
130 return self.engine.complete(token)
131
131
132
132
133 def execute(self, block, blockID=None):
133 def execute(self, block, blockID=None):
134 self.waitingForEngine = True
134 self.waitingForEngine = True
135 self.willChangeValueForKey_('commandHistory')
135 self.willChangeValueForKey_('commandHistory')
136 d = super(IPythonCocoaController, self).execute(block, blockID)
136 d = super(IPythonCocoaController, self).execute(block, blockID)
137 d.addBoth(self._engine_done)
137 d.addBoth(self._engine_done)
138 d.addCallback(self._update_user_ns)
138 d.addCallback(self._update_user_ns)
139
139
140 return d
140 return d
141
141
142
142
143 def _engine_done(self, x):
143 def _engine_done(self, x):
144 self.waitingForEngine = False
144 self.waitingForEngine = False
145 self.didChangeValueForKey_('commandHistory')
145 self.didChangeValueForKey_('commandHistory')
146 return x
146 return x
147
147
148 def _update_user_ns(self, result):
148 def _update_user_ns(self, result):
149 """Update self.userNS from self.engine's namespace"""
149 """Update self.userNS from self.engine's namespace"""
150 d = self.engine.keys()
150 d = self.engine.keys()
151 d.addCallback(self._get_engine_namespace_values_for_keys)
151 d.addCallback(self._get_engine_namespace_values_for_keys)
152
152
153 return result
153 return result
154
154
155
155
156 def _get_engine_namespace_values_for_keys(self, keys):
156 def _get_engine_namespace_values_for_keys(self, keys):
157 d = self.engine.pull(keys)
157 d = self.engine.pull(keys)
158 d.addCallback(self._store_engine_namespace_values, keys=keys)
158 d.addCallback(self._store_engine_namespace_values, keys=keys)
159
159
160
160
161 def _store_engine_namespace_values(self, values, keys=[]):
161 def _store_engine_namespace_values(self, values, keys=[]):
162 assert(len(values) == len(keys))
162 assert(len(values) == len(keys))
163 self.willChangeValueForKey_('userNS')
163 self.willChangeValueForKey_('userNS')
164 for (k,v) in zip(keys,values):
164 for (k,v) in zip(keys,values):
165 self.userNS[k] = saferepr(v)
165 self.userNS[k] = saferepr(v)
166 self.didChangeValueForKey_('userNS')
166 self.didChangeValueForKey_('userNS')
167
167
168
168
169 def update_cell_prompt(self, result):
169 def update_cell_prompt(self, result):
170 if(isinstance(result, Failure)):
170 if(isinstance(result, Failure)):
171 blockID = result.blockID
171 blockID = result.blockID
172 else:
172 else:
173 blockID = result['blockID']
173 blockID = result['blockID']
174
174
175
175
176 self.insert_text(self.input_prompt(result=result),
176 self.insert_text(self.input_prompt(result=result),
177 textRange=NSMakeRange(self.blockRanges[blockID].location,0),
177 textRange=NSMakeRange(self.blockRanges[blockID].location,0),
178 scrollToVisible=False
178 scrollToVisible=False
179 )
179 )
180
180
181 return result
181 return result
182
182
183
183
184 def render_result(self, result):
184 def render_result(self, result):
185 blockID = result['blockID']
185 blockID = result['blockID']
186 inputRange = self.blockRanges[blockID]
186 inputRange = self.blockRanges[blockID]
187 del self.blockRanges[blockID]
187 del self.blockRanges[blockID]
188
188
189 #print inputRange,self.current_block_range()
189 #print inputRange,self.current_block_range()
190 self.insert_text('\n' +
190 self.insert_text('\n' +
191 self.output_prompt(result) +
191 self.output_prompt(result) +
192 result.get('display',{}).get('pprint','') +
192 result.get('display',{}).get('pprint','') +
193 '\n\n',
193 '\n\n',
194 textRange=NSMakeRange(inputRange.location+inputRange.length,
194 textRange=NSMakeRange(inputRange.location+inputRange.length,
195 0))
195 0))
196 return result
196 return result
197
197
198
198
199 def render_error(self, failure):
199 def render_error(self, failure):
200 self.insert_text('\n\n'+str(failure)+'\n\n')
200 self.insert_text('\n\n'+str(failure)+'\n\n')
201 self.start_new_block()
201 self.start_new_block()
202 return failure
202 return failure
203
203
204
204
205 def _start_cli_banner(self):
205 def _start_cli_banner(self):
206 """Print banner"""
206 """Print banner"""
207
207
208 banner = """IPython1 %s -- An enhanced Interactive Python.""" % \
208 banner = """IPython1 %s -- An enhanced Interactive Python.""" % \
209 IPython.__version__
209 IPython.__version__
210
210
211 self.insert_text(banner + '\n\n')
211 self.insert_text(banner + '\n\n')
212
212
213
213
214 def start_new_block(self):
214 def start_new_block(self):
215 """"""
215 """"""
216
216
217 self.currentBlockID = self.next_block_ID()
217 self.currentBlockID = self.next_block_ID()
218
218
219
219
220
220
221 def next_block_ID(self):
221 def next_block_ID(self):
222
222
223 return uuid.uuid4()
223 return uuid.uuid4()
224
224
225 def current_block_range(self):
225 def current_block_range(self):
226 return self.blockRanges.get(self.currentBlockID,
226 return self.blockRanges.get(self.currentBlockID,
227 NSMakeRange(self.textView.textStorage().length(),
227 NSMakeRange(self.textView.textStorage().length(),
228 0))
228 0))
229
229
230 def current_block(self):
230 def current_block(self):
231 """The current block's text"""
231 """The current block's text"""
232
232
233 return self.text_for_range(self.current_block_range())
233 return self.text_for_range(self.current_block_range())
234
234
235 def text_for_range(self, textRange):
235 def text_for_range(self, textRange):
236 """text_for_range"""
236 """text_for_range"""
237
237
238 ts = self.textView.textStorage()
238 ts = self.textView.textStorage()
239 return ts.string().substringWithRange_(textRange)
239 return ts.string().substringWithRange_(textRange)
240
240
241 def current_line(self):
241 def current_line(self):
242 block = self.text_for_range(self.current_block_range())
242 block = self.text_for_range(self.current_block_range())
243 block = block.split('\n')
243 block = block.split('\n')
244 return block[-1]
244 return block[-1]
245
245
246
246
247 def insert_text(self, string=None, textRange=None, scrollToVisible=True):
247 def insert_text(self, string=None, textRange=None, scrollToVisible=True):
248 """Insert text into textView at textRange, updating blockRanges
248 """Insert text into textView at textRange, updating blockRanges
249 as necessary
249 as necessary
250 """
250 """
251
251
252 if(textRange == None):
252 if(textRange == None):
253 #range for end of text
253 #range for end of text
254 textRange = NSMakeRange(self.textView.textStorage().length(), 0)
254 textRange = NSMakeRange(self.textView.textStorage().length(), 0)
255
255
256 for r in self.blockRanges.itervalues():
256 for r in self.blockRanges.itervalues():
257 intersection = NSIntersectionRange(r,textRange)
257 intersection = NSIntersectionRange(r,textRange)
258 if(intersection.length == 0): #ranges don't intersect
258 if(intersection.length == 0): #ranges don't intersect
259 if r.location >= textRange.location:
259 if r.location >= textRange.location:
260 r.location += len(string)
260 r.location += len(string)
261 else: #ranges intersect
261 else: #ranges intersect
262 if(r.location <= textRange.location):
262 if(r.location <= textRange.location):
263 assert(intersection.length == textRange.length)
263 assert(intersection.length == textRange.length)
264 r.length += textRange.length
264 r.length += textRange.length
265 else:
265 else:
266 r.location += intersection.length
266 r.location += intersection.length
267
267
268 self.textView.replaceCharactersInRange_withString_(
268 self.textView.replaceCharactersInRange_withString_(
269 textRange, string)
269 textRange, string)
270 self.textView.setSelectedRange_(
270 self.textView.setSelectedRange_(
271 NSMakeRange(textRange.location+len(string), 0))
271 NSMakeRange(textRange.location+len(string), 0))
272 if(scrollToVisible):
272 if(scrollToVisible):
273 self.textView.scrollRangeToVisible_(textRange)
273 self.textView.scrollRangeToVisible_(textRange)
274
274
275
275
276
276
277
277
278 def replace_current_block_with_string(self, textView, string):
278 def replace_current_block_with_string(self, textView, string):
279 textView.replaceCharactersInRange_withString_(
279 textView.replaceCharactersInRange_withString_(
280 self.current_block_range(),
280 self.current_block_range(),
281 string)
281 string)
282 self.current_block_range().length = len(string)
282 self.current_block_range().length = len(string)
283 r = NSMakeRange(textView.textStorage().length(), 0)
283 r = NSMakeRange(textView.textStorage().length(), 0)
284 textView.scrollRangeToVisible_(r)
284 textView.scrollRangeToVisible_(r)
285 textView.setSelectedRange_(r)
285 textView.setSelectedRange_(r)
286
286
287
287
288 def current_indent_string(self):
288 def current_indent_string(self):
289 """returns string for indent or None if no indent"""
289 """returns string for indent or None if no indent"""
290
290
291 if(len(self.current_block()) > 0):
291 return self._indent_for_block(self.currentBlock())
292 lines = self.current_block().split('\n')
292
293 currentIndent = len(lines[-1]) - len(lines[-1])
293
294 def _indent_for_block(self, block):
295 lines = block.split('\n')
296 if(len(lines) > 1):
297 currentIndent = len(lines[-1]) - len(lines[-1].lstrip())
294 if(currentIndent == 0):
298 if(currentIndent == 0):
295 currentIndent = self.tabSpaces
299 currentIndent = self.tabSpaces
296
300
297 if(self.tabUsesSpaces):
301 if(self.tabUsesSpaces):
298 result = ' ' * currentIndent
302 result = ' ' * currentIndent
299 else:
303 else:
300 result = '\t' * (currentIndent/self.tabSpaces)
304 result = '\t' * (currentIndent/self.tabSpaces)
301 else:
305 else:
302 result = None
306 result = None
303
307
304 return result
308 return result
305
309
306
310
307 # NSTextView delegate methods...
311 # NSTextView delegate methods...
308 def textView_doCommandBySelector_(self, textView, selector):
312 def textView_doCommandBySelector_(self, textView, selector):
309 assert(textView == self.textView)
313 assert(textView == self.textView)
310 NSLog("textView_doCommandBySelector_: "+selector)
314 NSLog("textView_doCommandBySelector_: "+selector)
311
315
312
316
313 if(selector == 'insertNewline:'):
317 if(selector == 'insertNewline:'):
314 indent = self.current_indent_string()
318 indent = self.current_indent_string()
315 if(indent):
319 if(indent):
316 line = indent + self.current_line()
320 line = indent + self.current_line()
317 else:
321 else:
318 line = self.current_line()
322 line = self.current_line()
319
323
320 if(self.is_complete(self.current_block())):
324 if(self.is_complete(self.current_block())):
321 self.execute(self.current_block(),
325 self.execute(self.current_block(),
322 blockID=self.currentBlockID)
326 blockID=self.currentBlockID)
323 self.start_new_block()
327 self.start_new_block()
324
328
325 return True
329 return True
326
330
327 return False
331 return False
328
332
329 elif(selector == 'moveUp:'):
333 elif(selector == 'moveUp:'):
330 prevBlock = self.get_history_previous(self.current_block())
334 prevBlock = self.get_history_previous(self.current_block())
331 if(prevBlock != None):
335 if(prevBlock != None):
332 self.replace_current_block_with_string(textView, prevBlock)
336 self.replace_current_block_with_string(textView, prevBlock)
333 else:
337 else:
334 NSBeep()
338 NSBeep()
335 return True
339 return True
336
340
337 elif(selector == 'moveDown:'):
341 elif(selector == 'moveDown:'):
338 nextBlock = self.get_history_next()
342 nextBlock = self.get_history_next()
339 if(nextBlock != None):
343 if(nextBlock != None):
340 self.replace_current_block_with_string(textView, nextBlock)
344 self.replace_current_block_with_string(textView, nextBlock)
341 else:
345 else:
342 NSBeep()
346 NSBeep()
343 return True
347 return True
344
348
345 elif(selector == 'moveToBeginningOfParagraph:'):
349 elif(selector == 'moveToBeginningOfParagraph:'):
346 textView.setSelectedRange_(NSMakeRange(
350 textView.setSelectedRange_(NSMakeRange(
347 self.current_block_range().location,
351 self.current_block_range().location,
348 0))
352 0))
349 return True
353 return True
350 elif(selector == 'moveToEndOfParagraph:'):
354 elif(selector == 'moveToEndOfParagraph:'):
351 textView.setSelectedRange_(NSMakeRange(
355 textView.setSelectedRange_(NSMakeRange(
352 self.current_block_range().location + \
356 self.current_block_range().location + \
353 self.current_block_range().length, 0))
357 self.current_block_range().length, 0))
354 return True
358 return True
355 elif(selector == 'deleteToEndOfParagraph:'):
359 elif(selector == 'deleteToEndOfParagraph:'):
356 if(textView.selectedRange().location <= \
360 if(textView.selectedRange().location <= \
357 self.current_block_range().location):
361 self.current_block_range().location):
358 # Intersect the selected range with the current line range
362 # Intersect the selected range with the current line range
359 if(self.current_block_range().length < 0):
363 if(self.current_block_range().length < 0):
360 self.blockRanges[self.currentBlockID].length = 0
364 self.blockRanges[self.currentBlockID].length = 0
361
365
362 r = NSIntersectionRange(textView.rangesForUserTextChange()[0],
366 r = NSIntersectionRange(textView.rangesForUserTextChange()[0],
363 self.current_block_range())
367 self.current_block_range())
364
368
365 if(r.length > 0): #no intersection
369 if(r.length > 0): #no intersection
366 textView.setSelectedRange_(r)
370 textView.setSelectedRange_(r)
367
371
368 return False # don't actually handle the delete
372 return False # don't actually handle the delete
369
373
370 elif(selector == 'insertTab:'):
374 elif(selector == 'insertTab:'):
371 if(len(self.current_line().strip()) == 0): #only white space
375 if(len(self.current_line().strip()) == 0): #only white space
372 return False
376 return False
373 else:
377 else:
374 self.textView.complete_(self)
378 self.textView.complete_(self)
375 return True
379 return True
376
380
377 elif(selector == 'deleteBackward:'):
381 elif(selector == 'deleteBackward:'):
378 #if we're at the beginning of the current block, ignore
382 #if we're at the beginning of the current block, ignore
379 if(textView.selectedRange().location == \
383 if(textView.selectedRange().location == \
380 self.current_block_range().location):
384 self.current_block_range().location):
381 return True
385 return True
382 else:
386 else:
383 self.current_block_range().length-=1
387 self.current_block_range().length-=1
384 return False
388 return False
385 return False
389 return False
386
390
387
391
388 def textView_shouldChangeTextInRanges_replacementStrings_(self,
392 def textView_shouldChangeTextInRanges_replacementStrings_(self,
389 textView, ranges, replacementStrings):
393 textView, ranges, replacementStrings):
390 """
394 """
391 Delegate method for NSTextView.
395 Delegate method for NSTextView.
392
396
393 Refuse change text in ranges not at end, but make those changes at
397 Refuse change text in ranges not at end, but make those changes at
394 end.
398 end.
395 """
399 """
396
400
397 assert(len(ranges) == len(replacementStrings))
401 assert(len(ranges) == len(replacementStrings))
398 allow = True
402 allow = True
399 for r,s in zip(ranges, replacementStrings):
403 for r,s in zip(ranges, replacementStrings):
400 r = r.rangeValue()
404 r = r.rangeValue()
401 if(textView.textStorage().length() > 0 and
405 if(textView.textStorage().length() > 0 and
402 r.location < self.current_block_range().location):
406 r.location < self.current_block_range().location):
403 self.insert_text(s)
407 self.insert_text(s)
404 allow = False
408 allow = False
405
409
406
410
407 self.blockRanges.setdefault(self.currentBlockID,
411 self.blockRanges.setdefault(self.currentBlockID,
408 self.current_block_range()).length +=\
412 self.current_block_range()).length +=\
409 len(s)
413 len(s)
410
414
411 return allow
415 return allow
412
416
413 def textView_completions_forPartialWordRange_indexOfSelectedItem_(self,
417 def textView_completions_forPartialWordRange_indexOfSelectedItem_(self,
414 textView, words, charRange, index):
418 textView, words, charRange, index):
415 try:
419 try:
416 ts = textView.textStorage()
420 ts = textView.textStorage()
417 token = ts.string().substringWithRange_(charRange)
421 token = ts.string().substringWithRange_(charRange)
418 completions = blockingCallFromThread(self.complete, token)
422 completions = blockingCallFromThread(self.complete, token)
419 except:
423 except:
420 completions = objc.nil
424 completions = objc.nil
421 NSBeep()
425 NSBeep()
422
426
423 return (completions,0)
427 return (completions,0)
424
428
425
429
@@ -1,72 +1,91 b''
1 # encoding: utf-8
1 # encoding: utf-8
2 """This file contains unittests for the
2 """This file contains unittests for the
3 IPython.frontend.cocoa.cocoa_frontend module.
3 IPython.frontend.cocoa.cocoa_frontend module.
4 """
4 """
5 __docformat__ = "restructuredtext en"
5 __docformat__ = "restructuredtext en"
6
6
7 #---------------------------------------------------------------------------
7 #---------------------------------------------------------------------------
8 # Copyright (C) 2005 The IPython Development Team
8 # Copyright (C) 2005 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 from IPython.kernel.core.interpreter import Interpreter
17 from IPython.kernel.core.interpreter import Interpreter
18 import IPython.kernel.engineservice as es
18 import IPython.kernel.engineservice as es
19 from IPython.testing.util import DeferredTestCase
19 from IPython.testing.util import DeferredTestCase
20 from twisted.internet.defer import succeed
20 from twisted.internet.defer import succeed
21 from IPython.frontend.cocoa.cocoa_frontend import IPythonCocoaController
21 from IPython.frontend.cocoa.cocoa_frontend import IPythonCocoaController
22
22
23 from Foundation import NSMakeRect
23 from Foundation import NSMakeRect
24 from AppKit import NSTextView, NSScrollView
24 from AppKit import NSTextView, NSScrollView
25
25
26 class TestIPythonCocoaControler(DeferredTestCase):
26 class TestIPythonCocoaControler(DeferredTestCase):
27 """Tests for IPythonCocoaController"""
27 """Tests for IPythonCocoaController"""
28
28
29 def setUp(self):
29 def setUp(self):
30 self.controller = IPythonCocoaController.alloc().init()
30 self.controller = IPythonCocoaController.alloc().init()
31 self.engine = es.EngineService()
31 self.engine = es.EngineService()
32 self.engine.startService()
32 self.engine.startService()
33
33
34
34
35 def tearDown(self):
35 def tearDown(self):
36 self.controller = None
36 self.controller = None
37 self.engine.stopService()
37 self.engine.stopService()
38
38
39 def testControllerExecutesCode(self):
39 def testControllerExecutesCode(self):
40 code ="""5+5"""
40 code ="""5+5"""
41 expected = Interpreter().execute(code)
41 expected = Interpreter().execute(code)
42 del expected['number']
42 del expected['number']
43 def removeNumberAndID(result):
43 def removeNumberAndID(result):
44 del result['number']
44 del result['number']
45 del result['id']
45 del result['id']
46 return result
46 return result
47 self.assertDeferredEquals(
47 self.assertDeferredEquals(
48 self.controller.execute(code).addCallback(removeNumberAndID),
48 self.controller.execute(code).addCallback(removeNumberAndID),
49 expected)
49 expected)
50
50
51 def testControllerMirrorsUserNSWithValuesAsStrings(self):
51 def testControllerMirrorsUserNSWithValuesAsStrings(self):
52 code = """userns1=1;userns2=2"""
52 code = """userns1=1;userns2=2"""
53 def testControllerUserNS(result):
53 def testControllerUserNS(result):
54 self.assertEquals(self.controller.userNS['userns1'], 1)
54 self.assertEquals(self.controller.userNS['userns1'], 1)
55 self.assertEquals(self.controller.userNS['userns2'], 2)
55 self.assertEquals(self.controller.userNS['userns2'], 2)
56
56
57 self.controller.execute(code).addCallback(testControllerUserNS)
57 self.controller.execute(code).addCallback(testControllerUserNS)
58
58
59
59
60 def testControllerInstantiatesIEngine(self):
60 def testControllerInstantiatesIEngine(self):
61 self.assert_(es.IEngineBase.providedBy(self.controller.engine))
61 self.assert_(es.IEngineBase.providedBy(self.controller.engine))
62
62
63 def testControllerCompletesToken(self):
63 def testControllerCompletesToken(self):
64 code = """longNameVariable=10"""
64 code = """longNameVariable=10"""
65 def testCompletes(result):
65 def testCompletes(result):
66 self.assert_("longNameVariable" in result)
66 self.assert_("longNameVariable" in result)
67
67
68 def testCompleteToken(result):
68 def testCompleteToken(result):
69 self.controller.complete("longNa").addCallback(testCompletes)
69 self.controller.complete("longNa").addCallback(testCompletes)
70
70
71 self.controller.execute(code).addCallback(testCompletes)
71 self.controller.execute(code).addCallback(testCompletes)
72
72
73
74 def testCurrentIndent(self):
75 """test that current_indent_string returns current indent or None.
76 Uses _indent_for_block for direct unit testing.
77 """
78
79 self.controller.tabUsesSpaces = True
80 self.assert_(self.controller._indent_for_block("""a=3""") == None)
81 self.assert_(self.controller._indent_for_block("") == None)
82 block = """def test():\n a=3"""
83 self.assert_(self.controller._indent_for_block(block) == \
84 ' ' * self.controller.tabSpaces)
85
86 block = """if(True):\n%sif(False):\n%spass""" % \
87 (' '*self.controller.tabSpaces,
88 2*' '*self.controller.tabSpaces)
89 self.assert_(self.controller._indent_for_block(block) == \
90 2*(' '*self.controller.tabSpaces))
91
General Comments 0
You need to be logged in to leave comments. Login now