##// END OF EJS Templates
Merged ipython-frontend branch. All changes in IPython.frontend except for updates to IPython.kernel.engineservice.ThreadedEngineService and associated tests.
Barry Wark -
r1294:defe9154 merge
parent child Browse files
Show More

The requested changes are too big and content was truncated. Show full diff

1 NO CONTENT: new file 100644
1 NO CONTENT: new file 100644, binary diff hidden
1 NO CONTENT: new file 100644
@@ -0,0 +1,425 b''
1 # encoding: utf-8
2 # -*- test-case-name: IPython.frontend.cocoa.tests.test_cocoa_frontend -*-
3
4 """PyObjC classes to provide a Cocoa frontend to the
5 IPython.kernel.engineservice.IEngineBase.
6
7 To add an IPython interpreter to a cocoa app, instantiate an
8 IPythonCocoaController in a XIB and connect its textView outlet to an
9 NSTextView instance in your UI. That's it.
10
11 Author: Barry Wark
12 """
13
14 __docformat__ = "restructuredtext en"
15
16 #-----------------------------------------------------------------------------
17 # Copyright (C) 2008 The IPython Development Team
18 #
19 # Distributed under the terms of the BSD License. The full license is in
20 # the file COPYING, distributed as part of this software.
21 #-----------------------------------------------------------------------------
22
23 #-----------------------------------------------------------------------------
24 # Imports
25 #-----------------------------------------------------------------------------
26
27 import objc
28 import uuid
29
30 from Foundation import NSObject, NSMutableArray, NSMutableDictionary,\
31 NSLog, NSNotificationCenter, NSMakeRange,\
32 NSLocalizedString, NSIntersectionRange
33
34 from AppKit import NSApplicationWillTerminateNotification, NSBeep,\
35 NSTextView, NSRulerView, NSVerticalRuler
36
37 from pprint import saferepr
38
39 import IPython
40 from IPython.kernel.engineservice import ThreadedEngineService
41 from IPython.frontend.frontendbase import FrontEndBase
42
43 from twisted.internet.threads import blockingCallFromThread
44 from twisted.python.failure import Failure
45
46 #------------------------------------------------------------------------------
47 # Classes to implement the Cocoa frontend
48 #------------------------------------------------------------------------------
49
50 # TODO:
51 # 1. use MultiEngineClient and out-of-process engine rather than
52 # ThreadedEngineService?
53 # 2. integrate Xgrid launching of engines
54
55
56
57
58 class IPythonCocoaController(NSObject, FrontEndBase):
59 userNS = objc.ivar() #mirror of engine.user_ns (key=>str(value))
60 waitingForEngine = objc.ivar().bool()
61 textView = objc.IBOutlet()
62
63 def init(self):
64 self = super(IPythonCocoaController, self).init()
65 FrontEndBase.__init__(self, engine=ThreadedEngineService())
66 if(self != None):
67 self._common_init()
68
69 return self
70
71 def _common_init(self):
72 """_common_init"""
73
74 self.userNS = NSMutableDictionary.dictionary()
75 self.waitingForEngine = False
76
77 self.lines = {}
78 self.tabSpaces = 4
79 self.tabUsesSpaces = True
80 self.currentBlockID = self.next_block_ID()
81 self.blockRanges = {} # blockID=>NSRange
82
83
84 def awakeFromNib(self):
85 """awakeFromNib"""
86
87 self._common_init()
88
89 # Start the IPython engine
90 self.engine.startService()
91 NSLog('IPython engine started')
92
93 # Register for app termination
94 nc = NSNotificationCenter.defaultCenter()
95 nc.addObserver_selector_name_object_(
96 self,
97 'appWillTerminate:',
98 NSApplicationWillTerminateNotification,
99 None)
100
101 self.textView.setDelegate_(self)
102 self.textView.enclosingScrollView().setHasVerticalRuler_(True)
103 r = NSRulerView.alloc().initWithScrollView_orientation_(
104 self.textView.enclosingScrollView(),
105 NSVerticalRuler)
106 self.verticalRulerView = r
107 self.verticalRulerView.setClientView_(self.textView)
108 self._start_cli_banner()
109
110
111 def appWillTerminate_(self, notification):
112 """appWillTerminate"""
113
114 self.engine.stopService()
115
116
117 def complete(self, token):
118 """Complete token in engine's user_ns
119
120 Parameters
121 ----------
122 token : string
123
124 Result
125 ------
126 Deferred result of
127 IPython.kernel.engineservice.IEngineBase.complete
128 """
129
130 return self.engine.complete(token)
131
132
133 def execute(self, block, blockID=None):
134 self.waitingForEngine = True
135 self.willChangeValueForKey_('commandHistory')
136 d = super(IPythonCocoaController, self).execute(block, blockID)
137 d.addBoth(self._engine_done)
138 d.addCallback(self._update_user_ns)
139
140 return d
141
142
143 def _engine_done(self, x):
144 self.waitingForEngine = False
145 self.didChangeValueForKey_('commandHistory')
146 return x
147
148 def _update_user_ns(self, result):
149 """Update self.userNS from self.engine's namespace"""
150 d = self.engine.keys()
151 d.addCallback(self._get_engine_namespace_values_for_keys)
152
153 return result
154
155
156 def _get_engine_namespace_values_for_keys(self, keys):
157 d = self.engine.pull(keys)
158 d.addCallback(self._store_engine_namespace_values, keys=keys)
159
160
161 def _store_engine_namespace_values(self, values, keys=[]):
162 assert(len(values) == len(keys))
163 self.willChangeValueForKey_('userNS')
164 for (k,v) in zip(keys,values):
165 self.userNS[k] = saferepr(v)
166 self.didChangeValueForKey_('userNS')
167
168
169 def update_cell_prompt(self, result):
170 if(isinstance(result, Failure)):
171 blockID = result.blockID
172 else:
173 blockID = result['blockID']
174
175
176 self.insert_text(self.input_prompt(result=result),
177 textRange=NSMakeRange(self.blockRanges[blockID].location,0),
178 scrollToVisible=False
179 )
180
181 return result
182
183
184 def render_result(self, result):
185 blockID = result['blockID']
186 inputRange = self.blockRanges[blockID]
187 del self.blockRanges[blockID]
188
189 #print inputRange,self.current_block_range()
190 self.insert_text('\n' +
191 self.output_prompt(result) +
192 result.get('display',{}).get('pprint','') +
193 '\n\n',
194 textRange=NSMakeRange(inputRange.location+inputRange.length,
195 0))
196 return result
197
198
199 def render_error(self, failure):
200 self.insert_text('\n\n'+str(failure)+'\n\n')
201 self.start_new_block()
202 return failure
203
204
205 def _start_cli_banner(self):
206 """Print banner"""
207
208 banner = """IPython1 %s -- An enhanced Interactive Python.""" % \
209 IPython.__version__
210
211 self.insert_text(banner + '\n\n')
212
213
214 def start_new_block(self):
215 """"""
216
217 self.currentBlockID = self.next_block_ID()
218
219
220
221 def next_block_ID(self):
222
223 return uuid.uuid4()
224
225 def current_block_range(self):
226 return self.blockRanges.get(self.currentBlockID,
227 NSMakeRange(self.textView.textStorage().length(),
228 0))
229
230 def current_block(self):
231 """The current block's text"""
232
233 return self.text_for_range(self.current_block_range())
234
235 def text_for_range(self, textRange):
236 """text_for_range"""
237
238 ts = self.textView.textStorage()
239 return ts.string().substringWithRange_(textRange)
240
241 def current_line(self):
242 block = self.text_for_range(self.current_block_range())
243 block = block.split('\n')
244 return block[-1]
245
246
247 def insert_text(self, string=None, textRange=None, scrollToVisible=True):
248 """Insert text into textView at textRange, updating blockRanges
249 as necessary
250 """
251
252 if(textRange == None):
253 #range for end of text
254 textRange = NSMakeRange(self.textView.textStorage().length(), 0)
255
256 for r in self.blockRanges.itervalues():
257 intersection = NSIntersectionRange(r,textRange)
258 if(intersection.length == 0): #ranges don't intersect
259 if r.location >= textRange.location:
260 r.location += len(string)
261 else: #ranges intersect
262 if(r.location <= textRange.location):
263 assert(intersection.length == textRange.length)
264 r.length += textRange.length
265 else:
266 r.location += intersection.length
267
268 self.textView.replaceCharactersInRange_withString_(
269 textRange, string)
270 self.textView.setSelectedRange_(
271 NSMakeRange(textRange.location+len(string), 0))
272 if(scrollToVisible):
273 self.textView.scrollRangeToVisible_(textRange)
274
275
276
277
278 def replace_current_block_with_string(self, textView, string):
279 textView.replaceCharactersInRange_withString_(
280 self.current_block_range(),
281 string)
282 self.current_block_range().length = len(string)
283 r = NSMakeRange(textView.textStorage().length(), 0)
284 textView.scrollRangeToVisible_(r)
285 textView.setSelectedRange_(r)
286
287
288 def current_indent_string(self):
289 """returns string for indent or None if no indent"""
290
291 if(len(self.current_block()) > 0):
292 lines = self.current_block().split('\n')
293 currentIndent = len(lines[-1]) - len(lines[-1])
294 if(currentIndent == 0):
295 currentIndent = self.tabSpaces
296
297 if(self.tabUsesSpaces):
298 result = ' ' * currentIndent
299 else:
300 result = '\t' * (currentIndent/self.tabSpaces)
301 else:
302 result = None
303
304 return result
305
306
307 # NSTextView delegate methods...
308 def textView_doCommandBySelector_(self, textView, selector):
309 assert(textView == self.textView)
310 NSLog("textView_doCommandBySelector_: "+selector)
311
312
313 if(selector == 'insertNewline:'):
314 indent = self.current_indent_string()
315 if(indent):
316 line = indent + self.current_line()
317 else:
318 line = self.current_line()
319
320 if(self.is_complete(self.current_block())):
321 self.execute(self.current_block(),
322 blockID=self.currentBlockID)
323 self.start_new_block()
324
325 return True
326
327 return False
328
329 elif(selector == 'moveUp:'):
330 prevBlock = self.get_history_previous(self.current_block())
331 if(prevBlock != None):
332 self.replace_current_block_with_string(textView, prevBlock)
333 else:
334 NSBeep()
335 return True
336
337 elif(selector == 'moveDown:'):
338 nextBlock = self.get_history_next()
339 if(nextBlock != None):
340 self.replace_current_block_with_string(textView, nextBlock)
341 else:
342 NSBeep()
343 return True
344
345 elif(selector == 'moveToBeginningOfParagraph:'):
346 textView.setSelectedRange_(NSMakeRange(
347 self.current_block_range().location,
348 0))
349 return True
350 elif(selector == 'moveToEndOfParagraph:'):
351 textView.setSelectedRange_(NSMakeRange(
352 self.current_block_range().location + \
353 self.current_block_range().length, 0))
354 return True
355 elif(selector == 'deleteToEndOfParagraph:'):
356 if(textView.selectedRange().location <= \
357 self.current_block_range().location):
358 # Intersect the selected range with the current line range
359 if(self.current_block_range().length < 0):
360 self.blockRanges[self.currentBlockID].length = 0
361
362 r = NSIntersectionRange(textView.rangesForUserTextChange()[0],
363 self.current_block_range())
364
365 if(r.length > 0): #no intersection
366 textView.setSelectedRange_(r)
367
368 return False # don't actually handle the delete
369
370 elif(selector == 'insertTab:'):
371 if(len(self.current_line().strip()) == 0): #only white space
372 return False
373 else:
374 self.textView.complete_(self)
375 return True
376
377 elif(selector == 'deleteBackward:'):
378 #if we're at the beginning of the current block, ignore
379 if(textView.selectedRange().location == \
380 self.current_block_range().location):
381 return True
382 else:
383 self.current_block_range().length-=1
384 return False
385 return False
386
387
388 def textView_shouldChangeTextInRanges_replacementStrings_(self,
389 textView, ranges, replacementStrings):
390 """
391 Delegate method for NSTextView.
392
393 Refuse change text in ranges not at end, but make those changes at
394 end.
395 """
396
397 assert(len(ranges) == len(replacementStrings))
398 allow = True
399 for r,s in zip(ranges, replacementStrings):
400 r = r.rangeValue()
401 if(textView.textStorage().length() > 0 and
402 r.location < self.current_block_range().location):
403 self.insert_text(s)
404 allow = False
405
406
407 self.blockRanges.setdefault(self.currentBlockID,
408 self.current_block_range()).length +=\
409 len(s)
410
411 return allow
412
413 def textView_completions_forPartialWordRange_indexOfSelectedItem_(self,
414 textView, words, charRange, index):
415 try:
416 ts = textView.textStorage()
417 token = ts.string().substringWithRange_(charRange)
418 completions = blockingCallFromThread(self.complete, token)
419 except:
420 completions = objc.nil
421 NSBeep()
422
423 return (completions,0)
424
425
1 NO CONTENT: new file 100644, binary diff hidden
1 NO CONTENT: new file 100644, binary diff hidden
1 NO CONTENT: new file 100644, binary diff hidden
1 NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
@@ -0,0 +1,293 b''
1 // !$*UTF8*$!
2 {
3 archiveVersion = 1;
4 classes = {
5 };
6 objectVersion = 44;
7 objects = {
8
9 /* Begin PBXBuildFile section */
10 77631A270C06C501005415CB /* Python.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 77631A260C06C501005415CB /* Python.framework */; };
11 77631A3F0C0748CF005415CB /* main.py in Resources */ = {isa = PBXBuildFile; fileRef = 77631A3E0C0748CF005415CB /* main.py */; };
12 7790198F0C07548A00326F66 /* IPython1SandboxAppDelegate.py in Resources */ = {isa = PBXBuildFile; fileRef = 7790198E0C07548A00326F66 /* IPython1SandboxAppDelegate.py */; };
13 77C8C1F90C07829500965286 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 77C8C1F70C07829500965286 /* MainMenu.xib */; };
14 8D11072B0486CEB800E47090 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 089C165CFE840E0CC02AAC07 /* InfoPlist.strings */; };
15 8D11072D0486CEB800E47090 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 29B97316FDCFA39411CA2CEA /* main.m */; settings = {ATTRIBUTES = (); }; };
16 8D11072F0486CEB800E47090 /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1058C7A1FEA54F0111CA2CBB /* Cocoa.framework */; };
17 /* End PBXBuildFile section */
18
19 /* Begin PBXFileReference section */
20 089C165DFE840E0CC02AAC07 /* English */ = {isa = PBXFileReference; fileEncoding = 10; lastKnownFileType = text.plist.strings; name = English; path = English.lproj/InfoPlist.strings; sourceTree = "<group>"; };
21 1058C7A1FEA54F0111CA2CBB /* Cocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = /System/Library/Frameworks/Cocoa.framework; sourceTree = "<absolute>"; };
22 13E42FB307B3F0F600E4EEF1 /* CoreData.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreData.framework; path = /System/Library/Frameworks/CoreData.framework; sourceTree = "<absolute>"; };
23 29B97316FDCFA39411CA2CEA /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = "<group>"; };
24 29B97324FDCFA39411CA2CEA /* AppKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AppKit.framework; path = /System/Library/Frameworks/AppKit.framework; sourceTree = "<absolute>"; };
25 29B97325FDCFA39411CA2CEA /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = /System/Library/Frameworks/Foundation.framework; sourceTree = "<absolute>"; };
26 32CA4F630368D1EE00C91783 /* IPython1Sandbox_Prefix.pch */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = IPython1Sandbox_Prefix.pch; sourceTree = "<group>"; };
27 4CA32F870D8879B100311764 /* IPythonCocoaController Tests-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "IPythonCocoaController Tests-Info.plist"; sourceTree = "<group>"; };
28 77631A260C06C501005415CB /* Python.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Python.framework; path = /System/Library/Frameworks/Python.framework; sourceTree = "<absolute>"; };
29 77631A3E0C0748CF005415CB /* main.py */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.python; path = main.py; sourceTree = "<group>"; };
30 7790198E0C07548A00326F66 /* IPython1SandboxAppDelegate.py */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.python; path = IPython1SandboxAppDelegate.py; sourceTree = "<group>"; };
31 77C8C1F80C07829500965286 /* English */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = English; path = English.lproj/MainMenu.xib; sourceTree = "<group>"; };
32 8D1107310486CEB800E47090 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
33 8D1107320486CEB800E47090 /* IPython1Sandbox.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = IPython1Sandbox.app; sourceTree = BUILT_PRODUCTS_DIR; };
34 /* End PBXFileReference section */
35
36 /* Begin PBXFrameworksBuildPhase section */
37 8D11072E0486CEB800E47090 /* Frameworks */ = {
38 isa = PBXFrameworksBuildPhase;
39 buildActionMask = 2147483647;
40 files = (
41 8D11072F0486CEB800E47090 /* Cocoa.framework in Frameworks */,
42 77631A270C06C501005415CB /* Python.framework in Frameworks */,
43 );
44 runOnlyForDeploymentPostprocessing = 0;
45 };
46 /* End PBXFrameworksBuildPhase section */
47
48 /* Begin PBXGroup section */
49 080E96DDFE201D6D7F000001 /* Classes */ = {
50 isa = PBXGroup;
51 children = (
52 7790198E0C07548A00326F66 /* IPython1SandboxAppDelegate.py */,
53 );
54 name = Classes;
55 sourceTree = "<group>";
56 };
57 1058C7A0FEA54F0111CA2CBB /* Linked Frameworks */ = {
58 isa = PBXGroup;
59 children = (
60 77631A260C06C501005415CB /* Python.framework */,
61 1058C7A1FEA54F0111CA2CBB /* Cocoa.framework */,
62 );
63 name = "Linked Frameworks";
64 sourceTree = "<group>";
65 };
66 1058C7A2FEA54F0111CA2CBB /* Other Frameworks */ = {
67 isa = PBXGroup;
68 children = (
69 29B97324FDCFA39411CA2CEA /* AppKit.framework */,
70 13E42FB307B3F0F600E4EEF1 /* CoreData.framework */,
71 29B97325FDCFA39411CA2CEA /* Foundation.framework */,
72 );
73 name = "Other Frameworks";
74 sourceTree = "<group>";
75 };
76 19C28FACFE9D520D11CA2CBB /* Products */ = {
77 isa = PBXGroup;
78 children = (
79 8D1107320486CEB800E47090 /* IPython1Sandbox.app */,
80 );
81 name = Products;
82 sourceTree = "<group>";
83 };
84 29B97314FDCFA39411CA2CEA /* IPython1Sandbox */ = {
85 isa = PBXGroup;
86 children = (
87 080E96DDFE201D6D7F000001 /* Classes */,
88 29B97315FDCFA39411CA2CEA /* Other Sources */,
89 29B97317FDCFA39411CA2CEA /* Resources */,
90 29B97323FDCFA39411CA2CEA /* Frameworks */,
91 19C28FACFE9D520D11CA2CBB /* Products */,
92 4CA32F870D8879B100311764 /* IPythonCocoaController Tests-Info.plist */,
93 );
94 name = IPython1Sandbox;
95 sourceTree = "<group>";
96 };
97 29B97315FDCFA39411CA2CEA /* Other Sources */ = {
98 isa = PBXGroup;
99 children = (
100 32CA4F630368D1EE00C91783 /* IPython1Sandbox_Prefix.pch */,
101 29B97316FDCFA39411CA2CEA /* main.m */,
102 77631A3E0C0748CF005415CB /* main.py */,
103 );
104 name = "Other Sources";
105 sourceTree = "<group>";
106 };
107 29B97317FDCFA39411CA2CEA /* Resources */ = {
108 isa = PBXGroup;
109 children = (
110 77C8C1F70C07829500965286 /* MainMenu.xib */,
111 8D1107310486CEB800E47090 /* Info.plist */,
112 089C165CFE840E0CC02AAC07 /* InfoPlist.strings */,
113 );
114 name = Resources;
115 sourceTree = "<group>";
116 };
117 29B97323FDCFA39411CA2CEA /* Frameworks */ = {
118 isa = PBXGroup;
119 children = (
120 1058C7A0FEA54F0111CA2CBB /* Linked Frameworks */,
121 1058C7A2FEA54F0111CA2CBB /* Other Frameworks */,
122 );
123 name = Frameworks;
124 sourceTree = "<group>";
125 };
126 /* End PBXGroup section */
127
128 /* Begin PBXNativeTarget section */
129 8D1107260486CEB800E47090 /* IPython1Sandbox */ = {
130 isa = PBXNativeTarget;
131 buildConfigurationList = C01FCF4A08A954540054247B /* Build configuration list for PBXNativeTarget "IPython1Sandbox" */;
132 buildPhases = (
133 8D1107290486CEB800E47090 /* Resources */,
134 8D11072C0486CEB800E47090 /* Sources */,
135 8D11072E0486CEB800E47090 /* Frameworks */,
136 );
137 buildRules = (
138 );
139 dependencies = (
140 );
141 name = IPython1Sandbox;
142 productInstallPath = "$(HOME)/Applications";
143 productName = IPython1Sandbox;
144 productReference = 8D1107320486CEB800E47090 /* IPython1Sandbox.app */;
145 productType = "com.apple.product-type.application";
146 };
147 /* End PBXNativeTarget section */
148
149 /* Begin PBXProject section */
150 29B97313FDCFA39411CA2CEA /* Project object */ = {
151 isa = PBXProject;
152 buildConfigurationList = C01FCF4E08A954540054247B /* Build configuration list for PBXProject "IPython1Sandbox" */;
153 compatibilityVersion = "Xcode 3.0";
154 hasScannedForEncodings = 1;
155 mainGroup = 29B97314FDCFA39411CA2CEA /* IPython1Sandbox */;
156 projectDirPath = "";
157 projectRoot = "";
158 targets = (
159 8D1107260486CEB800E47090 /* IPython1Sandbox */,
160 );
161 };
162 /* End PBXProject section */
163
164 /* Begin PBXResourcesBuildPhase section */
165 8D1107290486CEB800E47090 /* Resources */ = {
166 isa = PBXResourcesBuildPhase;
167 buildActionMask = 2147483647;
168 files = (
169 8D11072B0486CEB800E47090 /* InfoPlist.strings in Resources */,
170 77631A3F0C0748CF005415CB /* main.py in Resources */,
171 7790198F0C07548A00326F66 /* IPython1SandboxAppDelegate.py in Resources */,
172 77C8C1F90C07829500965286 /* MainMenu.xib in Resources */,
173 );
174 runOnlyForDeploymentPostprocessing = 0;
175 };
176 /* End PBXResourcesBuildPhase section */
177
178 /* Begin PBXSourcesBuildPhase section */
179 8D11072C0486CEB800E47090 /* Sources */ = {
180 isa = PBXSourcesBuildPhase;
181 buildActionMask = 2147483647;
182 files = (
183 8D11072D0486CEB800E47090 /* main.m in Sources */,
184 );
185 runOnlyForDeploymentPostprocessing = 0;
186 };
187 /* End PBXSourcesBuildPhase section */
188
189 /* Begin PBXVariantGroup section */
190 089C165CFE840E0CC02AAC07 /* InfoPlist.strings */ = {
191 isa = PBXVariantGroup;
192 children = (
193 089C165DFE840E0CC02AAC07 /* English */,
194 );
195 name = InfoPlist.strings;
196 sourceTree = "<group>";
197 };
198 77C8C1F70C07829500965286 /* MainMenu.xib */ = {
199 isa = PBXVariantGroup;
200 children = (
201 77C8C1F80C07829500965286 /* English */,
202 );
203 name = MainMenu.xib;
204 sourceTree = "<group>";
205 };
206 /* End PBXVariantGroup section */
207
208 /* Begin XCBuildConfiguration section */
209 C01FCF4B08A954540054247B /* Debug */ = {
210 isa = XCBuildConfiguration;
211 buildSettings = {
212 COPY_PHASE_STRIP = NO;
213 CURRENT_PROJECT_VERSION = 1;
214 GCC_DYNAMIC_NO_PIC = NO;
215 GCC_ENABLE_FIX_AND_CONTINUE = YES;
216 GCC_MODEL_TUNING = G5;
217 GCC_OPTIMIZATION_LEVEL = 0;
218 GCC_PRECOMPILE_PREFIX_HEADER = YES;
219 GCC_PREFIX_HEADER = IPython1Sandbox_Prefix.pch;
220 INFOPLIST_FILE = Info.plist;
221 INSTALL_PATH = "$(HOME)/Applications";
222 PRODUCT_NAME = IPython1Sandbox;
223 VERSIONING_SYSTEM = "apple-generic";
224 WRAPPER_EXTENSION = app;
225 ZERO_LINK = YES;
226 };
227 name = Debug;
228 };
229 C01FCF4C08A954540054247B /* Release */ = {
230 isa = XCBuildConfiguration;
231 buildSettings = {
232 CURRENT_PROJECT_VERSION = 1;
233 DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
234 GCC_MODEL_TUNING = G5;
235 GCC_PRECOMPILE_PREFIX_HEADER = YES;
236 GCC_PREFIX_HEADER = IPython1Sandbox_Prefix.pch;
237 INFOPLIST_FILE = Info.plist;
238 INSTALL_PATH = "$(HOME)/Applications";
239 PRODUCT_NAME = IPython1Sandbox;
240 VERSIONING_SYSTEM = "apple-generic";
241 WRAPPER_EXTENSION = app;
242 };
243 name = Release;
244 };
245 C01FCF4F08A954540054247B /* Debug */ = {
246 isa = XCBuildConfiguration;
247 buildSettings = {
248 GCC_WARN_ABOUT_RETURN_TYPE = YES;
249 GCC_WARN_UNUSED_VARIABLE = YES;
250 PREBINDING = NO;
251 SDKROOT = "$(DEVELOPER_SDK_DIR)/MacOSX10.5.sdk";
252 };
253 name = Debug;
254 };
255 C01FCF5008A954540054247B /* Release */ = {
256 isa = XCBuildConfiguration;
257 buildSettings = {
258 ARCHS = (
259 ppc,
260 i386,
261 );
262 GCC_WARN_ABOUT_RETURN_TYPE = YES;
263 GCC_WARN_UNUSED_VARIABLE = YES;
264 PREBINDING = NO;
265 SDKROOT = "$(DEVELOPER_SDK_DIR)/MacOSX10.5.sdk";
266 };
267 name = Release;
268 };
269 /* End XCBuildConfiguration section */
270
271 /* Begin XCConfigurationList section */
272 C01FCF4A08A954540054247B /* Build configuration list for PBXNativeTarget "IPython1Sandbox" */ = {
273 isa = XCConfigurationList;
274 buildConfigurations = (
275 C01FCF4B08A954540054247B /* Debug */,
276 C01FCF4C08A954540054247B /* Release */,
277 );
278 defaultConfigurationIsVisible = 0;
279 defaultConfigurationName = Release;
280 };
281 C01FCF4E08A954540054247B /* Build configuration list for PBXProject "IPython1Sandbox" */ = {
282 isa = XCConfigurationList;
283 buildConfigurations = (
284 C01FCF4F08A954540054247B /* Debug */,
285 C01FCF5008A954540054247B /* Release */,
286 );
287 defaultConfigurationIsVisible = 0;
288 defaultConfigurationName = Release;
289 };
290 /* End XCConfigurationList section */
291 };
292 rootObject = 29B97313FDCFA39411CA2CEA /* Project object */;
293 }
@@ -0,0 +1,39 b''
1 #
2 # IPython1SandboxAppDelegate.py
3 # IPython1Sandbox
4 #
5 # Created by Barry Wark on 3/4/08.
6 # Copyright __MyCompanyName__ 2008. All rights reserved.
7 #
8
9 from Foundation import NSObject, NSPredicate
10 import objc
11 import threading
12
13 from PyObjCTools import AppHelper
14
15 from twisted.internet import reactor
16
17 class IPython1SandboxAppDelegate(NSObject):
18 ipythonController = objc.IBOutlet()
19
20 def applicationShouldTerminate_(self, sender):
21 if reactor.running:
22 reactor.addSystemEventTrigger(
23 'after', 'shutdown', AppHelper.stopEventLoop)
24 reactor.stop()
25 return False
26 return True
27
28
29 def applicationDidFinishLaunching_(self, sender):
30 reactor.interleave(AppHelper.callAfter)
31 assert(reactor.running)
32
33
34 def workspaceFilterPredicate(self):
35 return NSPredicate.predicateWithFormat_("NOT (self.value BEGINSWITH '<')")
36
37
38
39
@@ -0,0 +1,7 b''
1 //
2 // Prefix header for all source files of the 'IPython1Sandbox' target in the 'IPython1Sandbox' project
3 //
4
5 #ifdef __OBJC__
6 #import <Cocoa/Cocoa.h>
7 #endif
@@ -0,0 +1,20 b''
1 <?xml version="1.0" encoding="UTF-8"?>
2 <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3 <plist version="1.0">
4 <dict>
5 <key>CFBundleDevelopmentRegion</key>
6 <string>English</string>
7 <key>CFBundleExecutable</key>
8 <string>${EXECUTABLE_NAME}</string>
9 <key>CFBundleIdentifier</key>
10 <string>com.yourcompany.IPythonCocoaController Tests</string>
11 <key>CFBundleInfoDictionaryVersion</key>
12 <string>6.0</string>
13 <key>CFBundlePackageType</key>
14 <string>BNDL</string>
15 <key>CFBundleSignature</key>
16 <string>????</string>
17 <key>CFBundleVersion</key>
18 <string>1.0</string>
19 </dict>
20 </plist>
@@ -0,0 +1,30 b''
1 <?xml version="1.0" encoding="UTF-8"?>
2 <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3 <plist version="1.0">
4 <dict>
5 <key>CFBundleDevelopmentRegion</key>
6 <string>English</string>
7 <key>CFBundleExecutable</key>
8 <string>${EXECUTABLE_NAME}</string>
9 <key>CFBundleIconFile</key>
10 <string></string>
11 <key>CFBundleIdentifier</key>
12 <string>com.yourcompany.IPython1Sandbox</string>
13 <key>CFBundleInfoDictionaryVersion</key>
14 <string>6.0</string>
15 <key>CFBundleName</key>
16 <string>${PRODUCT_NAME}</string>
17 <key>CFBundlePackageType</key>
18 <string>APPL</string>
19 <key>CFBundleShortVersionString</key>
20 <string>0.1</string>
21 <key>CFBundleSignature</key>
22 <string>????</string>
23 <key>CFBundleVersion</key>
24 <string>1.0</string>
25 <key>NSMainNibFile</key>
26 <string>MainMenu</string>
27 <key>NSPrincipalClass</key>
28 <string>NSApplication</string>
29 </dict>
30 </plist>
@@ -0,0 +1,49 b''
1 //
2 // main.m
3 // IPython1Sandbox
4 //
5 // Created by Barry Wark on 3/4/08.
6 // Copyright __MyCompanyName__ 2008. All rights reserved.
7 //
8
9 #import <Python/Python.h>
10 #import <Cocoa/Cocoa.h>
11
12 int main(int argc, char *argv[])
13 {
14 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
15
16 NSBundle *mainBundle = [NSBundle mainBundle];
17 NSString *resourcePath = [mainBundle resourcePath];
18 NSArray *pythonPathArray = [NSArray arrayWithObjects: resourcePath, [resourcePath stringByAppendingPathComponent:@"PyObjC"], nil];
19
20 setenv("PYTHONPATH", [[pythonPathArray componentsJoinedByString:@":"] UTF8String], 1);
21
22 NSArray *possibleMainExtensions = [NSArray arrayWithObjects: @"py", @"pyc", @"pyo", nil];
23 NSString *mainFilePath = nil;
24
25 for (NSString *possibleMainExtension in possibleMainExtensions) {
26 mainFilePath = [mainBundle pathForResource: @"main" ofType: possibleMainExtension];
27 if ( mainFilePath != nil ) break;
28 }
29
30 if ( !mainFilePath ) {
31 [NSException raise: NSInternalInconsistencyException format: @"%s:%d main() Failed to find the Main.{py,pyc,pyo} file in the application wrapper's Resources directory.", __FILE__, __LINE__];
32 }
33
34 Py_SetProgramName("/usr/bin/python");
35 Py_Initialize();
36 PySys_SetArgv(argc, (char **)argv);
37
38 const char *mainFilePathPtr = [mainFilePath UTF8String];
39 FILE *mainFile = fopen(mainFilePathPtr, "r");
40 int result = PyRun_SimpleFile(mainFile, (char *)[[mainFilePath lastPathComponent] UTF8String]);
41
42 if ( result != 0 )
43 [NSException raise: NSInternalInconsistencyException
44 format: @"%s:%d main() PyRun_SimpleFile failed with file '%@'. See console for errors.", __FILE__, __LINE__, mainFilePath];
45
46 [pool drain];
47
48 return result;
49 }
@@ -0,0 +1,24 b''
1 #
2 # main.py
3 # IPython1Sandbox
4 #
5 # Created by Barry Wark on 3/4/08.
6 # Copyright __MyCompanyName__ 2008. All rights reserved.
7 #
8
9 #import modules required by application
10 import objc
11 import Foundation
12 import AppKit
13
14 from PyObjCTools import AppHelper
15
16 from twisted.internet import _threadedselect
17 reactor = _threadedselect.install()
18
19 # import modules containing classes required to start application and load MainMenu.nib
20 import IPython1SandboxAppDelegate
21 import IPython.frontend.cocoa.cocoa_frontend
22
23 # pass control to AppKit
24 AppHelper.runEventLoop()
1 NO CONTENT: new file 100644
@@ -0,0 +1,27 b''
1 2008-03-14 00:06:49-0700 [-] Log opened.
2 2008-03-14 00:06:49-0700 [-] --> ipython1.frontend.cocoa.tests.test_cocoa_frontend.TestIPythonCocoaControler.testControllerCompletesToken <--
3 2008-03-14 00:06:49-0700 [-] Unhandled error in Deferred:
4 2008-03-14 00:06:49-0700 [-] Unhandled Error
5 Traceback (most recent call last):
6 File "/Library/Python/2.5/site-packages/Twisted-2.5.0_rUnknown-py2.5-macosx-10.3-i386.egg/twisted/internet/utils.py", line 144, in runWithWarningsSuppressed
7 result = f(*a, **kw)
8 File "/Users/barry/Desktop/ipython1-cocoa/ipython1/frontend/cocoa/tests/test_cocoa_frontend.py", line 94, in testControllerCompletesToken
9 self.controller.executeRequest([code]).addCallback(testCompletes)
10 File "/Library/Python/2.5/site-packages/Twisted-2.5.0_rUnknown-py2.5-macosx-10.3-i386.egg/twisted/internet/defer.py", line 196, in addCallback
11 callbackKeywords=kw)
12 File "/Library/Python/2.5/site-packages/Twisted-2.5.0_rUnknown-py2.5-macosx-10.3-i386.egg/twisted/internet/defer.py", line 187, in addCallbacks
13 self._runCallbacks()
14 --- <exception caught here> ---
15 File "/Library/Python/2.5/site-packages/Twisted-2.5.0_rUnknown-py2.5-macosx-10.3-i386.egg/twisted/internet/defer.py", line 325, in _runCallbacks
16 self.result = callback(self.result, *args, **kw)
17 File "/Users/barry/Desktop/ipython1-cocoa/ipython1/frontend/cocoa/tests/test_cocoa_frontend.py", line 89, in testCompletes
18 self.assert_("longNameVariable" in result)
19 File "/Library/Python/2.5/site-packages/Twisted-2.5.0_rUnknown-py2.5-macosx-10.3-i386.egg/twisted/trial/unittest.py", line 136, in failUnless
20 raise self.failureException(msg)
21 twisted.trial.unittest.FailTest: None
22
23 2008-03-14 00:06:49-0700 [-] --> ipython1.frontend.cocoa.tests.test_cocoa_frontend.TestIPythonCocoaControler.testControllerExecutesCode <--
24 2008-03-14 00:06:49-0700 [-] --> ipython1.frontend.cocoa.tests.test_cocoa_frontend.TestIPythonCocoaControler.testControllerInstantiatesIEngineInteractive <--
25 2008-03-14 00:06:49-0700 [-] --> ipython1.frontend.cocoa.tests.test_cocoa_frontend.TestIPythonCocoaControler.testControllerMirrorsUserNSWithValuesAsStrings <--
26 2008-03-14 00:06:49-0700 [-] --> ipython1.frontend.cocoa.tests.test_cocoa_frontend.TestIPythonCocoaControler.testControllerRaisesCompilerErrorForIllegalCode <--
27 2008-03-14 00:06:49-0700 [-] --> ipython1.frontend.cocoa.tests.test_cocoa_frontend.TestIPythonCocoaControler.testControllerReturnsNoneForIncompleteCode <--
@@ -0,0 +1,72 b''
1 # encoding: utf-8
2 """This file contains unittests for the
3 IPython.frontend.cocoa.cocoa_frontend module.
4 """
5 __docformat__ = "restructuredtext en"
6
7 #---------------------------------------------------------------------------
8 # Copyright (C) 2005 The IPython Development Team
9 #
10 # Distributed under the terms of the BSD License. The full license is in
11 # the file COPYING, distributed as part of this software.
12 #---------------------------------------------------------------------------
13
14 #---------------------------------------------------------------------------
15 # Imports
16 #---------------------------------------------------------------------------
17 from IPython.kernel.core.interpreter import Interpreter
18 import IPython.kernel.engineservice as es
19 from IPython.testing.util import DeferredTestCase
20 from twisted.internet.defer import succeed
21 from IPython.frontend.cocoa.cocoa_frontend import IPythonCocoaController
22
23 from Foundation import NSMakeRect
24 from AppKit import NSTextView, NSScrollView
25
26 class TestIPythonCocoaControler(DeferredTestCase):
27 """Tests for IPythonCocoaController"""
28
29 def setUp(self):
30 self.controller = IPythonCocoaController.alloc().init()
31 self.engine = es.EngineService()
32 self.engine.startService()
33
34
35 def tearDown(self):
36 self.controller = None
37 self.engine.stopService()
38
39 def testControllerExecutesCode(self):
40 code ="""5+5"""
41 expected = Interpreter().execute(code)
42 del expected['number']
43 def removeNumberAndID(result):
44 del result['number']
45 del result['id']
46 return result
47 self.assertDeferredEquals(
48 self.controller.execute(code).addCallback(removeNumberAndID),
49 expected)
50
51 def testControllerMirrorsUserNSWithValuesAsStrings(self):
52 code = """userns1=1;userns2=2"""
53 def testControllerUserNS(result):
54 self.assertEquals(self.controller.userNS['userns1'], 1)
55 self.assertEquals(self.controller.userNS['userns2'], 2)
56
57 self.controller.execute(code).addCallback(testControllerUserNS)
58
59
60 def testControllerInstantiatesIEngine(self):
61 self.assert_(es.IEngineBase.providedBy(self.controller.engine))
62
63 def testControllerCompletesToken(self):
64 code = """longNameVariable=10"""
65 def testCompletes(result):
66 self.assert_("longNameVariable" in result)
67
68 def testCompleteToken(result):
69 self.controller.complete("longNa").addCallback(testCompletes)
70
71 self.controller.execute(code).addCallback(testCompletes)
72
@@ -0,0 +1,352 b''
1 # encoding: utf-8
2 # -*- test-case-name: IPython.frontend.tests.test_frontendbase -*-
3 """
4 frontendbase provides an interface and base class for GUI frontends for
5 IPython.kernel/IPython.kernel.core.
6
7 Frontend implementations will likely want to subclass FrontEndBase.
8
9 Author: Barry Wark
10 """
11 __docformat__ = "restructuredtext en"
12
13 #-------------------------------------------------------------------------------
14 # Copyright (C) 2008 The IPython Development Team
15 #
16 # Distributed under the terms of the BSD License. The full license is in
17 # the file COPYING, distributed as part of this software.
18 #-------------------------------------------------------------------------------
19
20 #-------------------------------------------------------------------------------
21 # Imports
22 #-------------------------------------------------------------------------------
23 import string
24 import uuid
25 import _ast
26
27 import zope.interface as zi
28
29 from IPython.kernel.core.history import FrontEndHistory
30 from IPython.kernel.core.util import Bunch
31 from IPython.kernel.engineservice import IEngineCore
32
33 from twisted.python.failure import Failure
34
35 ##############################################################################
36 # TEMPORARY!!! fake configuration, while we decide whether to use tconfig or
37 # not
38
39 rc = Bunch()
40 rc.prompt_in1 = r'In [$number]: '
41 rc.prompt_in2 = r'...'
42 rc.prompt_out = r'Out [$number]: '
43
44 ##############################################################################
45
46 class IFrontEndFactory(zi.Interface):
47 """Factory interface for frontends."""
48
49 def __call__(engine=None, history=None):
50 """
51 Parameters:
52 interpreter : IPython.kernel.engineservice.IEngineCore
53 """
54
55 pass
56
57
58
59 class IFrontEnd(zi.Interface):
60 """Interface for frontends. All methods return t.i.d.Deferred"""
61
62 zi.Attribute("input_prompt_template", "string.Template instance\
63 substituteable with execute result.")
64 zi.Attribute("output_prompt_template", "string.Template instance\
65 substituteable with execute result.")
66 zi.Attribute("continuation_prompt_template", "string.Template instance\
67 substituteable with execute result.")
68
69 def update_cell_prompt(self, result):
70 """Subclass may override to update the input prompt for a block.
71 Since this method will be called as a
72 twisted.internet.defer.Deferred's callback,
73 implementations should return result when finished.
74
75 NB: result is a failure if the execute returned a failre.
76 To get the blockID, you should do something like::
77 if(isinstance(result, twisted.python.failure.Failure)):
78 blockID = result.blockID
79 else:
80 blockID = result['blockID']
81 """
82
83 pass
84
85 def render_result(self, result):
86 """Render the result of an execute call. Implementors may choose the
87 method of rendering.
88 For example, a notebook-style frontend might render a Chaco plot
89 inline.
90
91 Parameters:
92 result : dict (result of IEngineBase.execute )
93
94 Result:
95 Output of frontend rendering
96 """
97
98 pass
99
100 def render_error(self, failure):
101 """Subclasses must override to render the failure. Since this method
102 ill be called as a twisted.internet.defer.Deferred's callback,
103 implementations should return result when finished.
104 """
105
106 pass
107
108
109 def input_prompt(result={}):
110 """Returns the input prompt by subsituting into
111 self.input_prompt_template
112 """
113 pass
114
115 def output_prompt(result):
116 """Returns the output prompt by subsituting into
117 self.output_prompt_template
118 """
119
120 pass
121
122 def continuation_prompt():
123 """Returns the continuation prompt by subsituting into
124 self.continuation_prompt_template
125 """
126
127 pass
128
129 def is_complete(block):
130 """Returns True if block is complete, False otherwise."""
131
132 pass
133
134 def compile_ast(block):
135 """Compiles block to an _ast.AST"""
136
137 pass
138
139
140 def get_history_previous(currentBlock):
141 """Returns the block previous in the history. Saves currentBlock if
142 the history_cursor is currently at the end of the input history"""
143 pass
144
145 def get_history_next():
146 """Returns the next block in the history."""
147
148 pass
149
150
151 class FrontEndBase(object):
152 """
153 FrontEndBase manages the state tasks for a CLI frontend:
154 - Input and output history management
155 - Input/continuation and output prompt generation
156
157 Some issues (due to possibly unavailable engine):
158 - How do we get the current cell number for the engine?
159 - How do we handle completions?
160 """
161
162 zi.implements(IFrontEnd)
163 zi.classProvides(IFrontEndFactory)
164
165 history_cursor = 0
166
167 current_indent_level = 0
168
169
170 input_prompt_template = string.Template(rc.prompt_in1)
171 output_prompt_template = string.Template(rc.prompt_out)
172 continuation_prompt_template = string.Template(rc.prompt_in2)
173
174 def __init__(self, engine=None, history=None):
175 assert(engine==None or IEngineCore.providedBy(engine))
176 self.engine = IEngineCore(engine)
177 if history is None:
178 self.history = FrontEndHistory(input_cache=[''])
179 else:
180 self.history = history
181
182
183 def input_prompt(self, result={}):
184 """Returns the current input prompt
185
186 It would be great to use ipython1.core.prompts.Prompt1 here
187 """
188
189 result.setdefault('number','')
190
191 return self.input_prompt_template.safe_substitute(result)
192
193
194 def continuation_prompt(self):
195 """Returns the current continuation prompt"""
196
197 return self.continuation_prompt_template.safe_substitute()
198
199 def output_prompt(self, result):
200 """Returns the output prompt for result"""
201
202 return self.output_prompt_template.safe_substitute(result)
203
204
205 def is_complete(self, block):
206 """Determine if block is complete.
207
208 Parameters
209 block : string
210
211 Result
212 True if block can be sent to the engine without compile errors.
213 False otherwise.
214 """
215
216 try:
217 ast = self.compile_ast(block)
218 except:
219 return False
220
221 lines = block.split('\n')
222 return (len(lines)==1 or str(lines[-1])=='')
223
224
225 def compile_ast(self, block):
226 """Compile block to an AST
227
228 Parameters:
229 block : str
230
231 Result:
232 AST
233
234 Throws:
235 Exception if block cannot be compiled
236 """
237
238 return compile(block, "<string>", "exec", _ast.PyCF_ONLY_AST)
239
240
241 def execute(self, block, blockID=None):
242 """Execute the block and return result.
243
244 Parameters:
245 block : {str, AST}
246 blockID : any
247 Caller may provide an ID to identify this block.
248 result['blockID'] := blockID
249
250 Result:
251 Deferred result of self.interpreter.execute
252 """
253
254 if(not self.is_complete(block)):
255 return Failure(Exception("Block is not compilable"))
256
257 if(blockID == None):
258 blockID = uuid.uuid4() #random UUID
259
260 d = self.engine.execute(block)
261 d.addCallback(self._add_history, block=block)
262 d.addBoth(self._add_block_id, blockID)
263 d.addBoth(self.update_cell_prompt)
264 d.addCallbacks(self.render_result, errback=self.render_error)
265
266 return d
267
268
269 def _add_block_id(self, result, blockID):
270 """Add the blockID to result or failure. Unfortunatley, we have to
271 treat failures differently than result dicts.
272 """
273
274 if(isinstance(result, Failure)):
275 result.blockID = blockID
276 else:
277 result['blockID'] = blockID
278
279 return result
280
281 def _add_history(self, result, block=None):
282 """Add block to the history"""
283
284 assert(block != None)
285 self.history.add_items([block])
286 self.history_cursor += 1
287
288 return result
289
290
291 def get_history_previous(self, currentBlock):
292 """ Returns previous history string and decrement history cursor.
293 """
294 command = self.history.get_history_item(self.history_cursor - 1)
295
296 if command is not None:
297 if(self.history_cursor == len(self.history.input_cache)):
298 self.history.input_cache[self.history_cursor] = currentBlock
299 self.history_cursor -= 1
300 return command
301
302
303 def get_history_next(self):
304 """ Returns next history string and increment history cursor.
305 """
306 command = self.history.get_history_item(self.history_cursor+1)
307
308 if command is not None:
309 self.history_cursor += 1
310 return command
311
312 ###
313 # Subclasses probably want to override these methods...
314 ###
315
316 def update_cell_prompt(self, result):
317 """Subclass may override to update the input prompt for a block.
318 Since this method will be called as a
319 twisted.internet.defer.Deferred's callback, implementations should
320 return result when finished.
321
322 NB: result is a failure if the execute returned a failre.
323 To get the blockID, you should do something like::
324 if(isinstance(result, twisted.python.failure.Failure)):
325 blockID = result.blockID
326 else:
327 blockID = result['blockID']
328
329
330 """
331
332 return result
333
334
335 def render_result(self, result):
336 """Subclasses must override to render result. Since this method will
337 be called as a twisted.internet.defer.Deferred's callback,
338 implementations should return result when finished.
339 """
340
341 return result
342
343
344 def render_error(self, failure):
345 """Subclasses must override to render the failure. Since this method
346 will be called as a twisted.internet.defer.Deferred's callback,
347 implementations should return result when finished."""
348
349 return failure
350
351
352
1 NO CONTENT: new file 100644
@@ -0,0 +1,151 b''
1 # encoding: utf-8
2
3 """This file contains unittests for the frontendbase module."""
4
5 __docformat__ = "restructuredtext en"
6
7 #---------------------------------------------------------------------------
8 # Copyright (C) 2008 The IPython Development Team
9 #
10 # Distributed under the terms of the BSD License. The full license is in
11 # the file COPYING, distributed as part of this software.
12 #---------------------------------------------------------------------------
13
14 #---------------------------------------------------------------------------
15 # Imports
16 #---------------------------------------------------------------------------
17
18 import unittest
19 from IPython.frontend import frontendbase
20 from IPython.kernel.engineservice import EngineService
21
22 class FrontEndCallbackChecker(frontendbase.FrontEndBase):
23 """FrontEndBase subclass for checking callbacks"""
24 def __init__(self, engine=None, history=None):
25 super(FrontEndCallbackChecker, self).__init__(engine=engine,
26 history=history)
27 self.updateCalled = False
28 self.renderResultCalled = False
29 self.renderErrorCalled = False
30
31 def update_cell_prompt(self, result):
32 self.updateCalled = True
33 return result
34
35 def render_result(self, result):
36 self.renderResultCalled = True
37 return result
38
39
40 def render_error(self, failure):
41 self.renderErrorCalled = True
42 return failure
43
44
45
46
47 class TestFrontendBase(unittest.TestCase):
48 def setUp(self):
49 """Setup the EngineService and FrontEndBase"""
50
51 self.fb = FrontEndCallbackChecker(engine=EngineService())
52
53
54 def test_implements_IFrontEnd(self):
55 assert(frontendbase.IFrontEnd.implementedBy(
56 frontendbase.FrontEndBase))
57
58
59 def test_is_complete_returns_False_for_incomplete_block(self):
60 """"""
61
62 block = """def test(a):"""
63
64 assert(self.fb.is_complete(block) == False)
65
66 def test_is_complete_returns_True_for_complete_block(self):
67 """"""
68
69 block = """def test(a): pass"""
70
71 assert(self.fb.is_complete(block))
72
73 block = """a=3"""
74
75 assert(self.fb.is_complete(block))
76
77
78 def test_blockID_added_to_result(self):
79 block = """3+3"""
80
81 d = self.fb.execute(block, blockID='TEST_ID')
82
83 d.addCallback(self.checkBlockID, expected='TEST_ID')
84
85 def test_blockID_added_to_failure(self):
86 block = "raise Exception()"
87
88 d = self.fb.execute(block,blockID='TEST_ID')
89 d.addErrback(self.checkFailureID, expected='TEST_ID')
90
91 def checkBlockID(self, result, expected=""):
92 assert(result['blockID'] == expected)
93
94
95 def checkFailureID(self, failure, expected=""):
96 assert(failure.blockID == expected)
97
98
99 def test_callbacks_added_to_execute(self):
100 """test that
101 update_cell_prompt
102 render_result
103
104 are added to execute request
105 """
106
107 d = self.fb.execute("10+10")
108 d.addCallback(self.checkCallbacks)
109
110
111 def checkCallbacks(self, result):
112 assert(self.fb.updateCalled)
113 assert(self.fb.renderResultCalled)
114
115
116 def test_error_callback_added_to_execute(self):
117 """test that render_error called on execution error"""
118
119 d = self.fb.execute("raise Exception()")
120 d.addCallback(self.checkRenderError)
121
122 def checkRenderError(self, result):
123 assert(self.fb.renderErrorCalled)
124
125 def test_history_returns_expected_block(self):
126 """Make sure history browsing doesn't fail"""
127
128 blocks = ["a=1","a=2","a=3"]
129 for b in blocks:
130 d = self.fb.execute(b)
131
132 # d is now the deferred for the last executed block
133 d.addCallback(self.historyTests, blocks)
134
135
136 def historyTests(self, result, blocks):
137 """historyTests"""
138
139 assert(len(blocks) >= 3)
140 assert(self.fb.get_history_previous("") == blocks[-2])
141 assert(self.fb.get_history_previous("") == blocks[-3])
142 assert(self.fb.get_history_next() == blocks[-2])
143
144
145 def test_history_returns_none_at_startup(self):
146 """test_history_returns_none_at_startup"""
147
148 assert(self.fb.get_history_previous("")==None)
149 assert(self.fb.get_history_next()==None)
150
151
@@ -847,18 +847,30 b' class Command(object):'
847 847 self.deferred.errback(reason)
848 848
849 849 class ThreadedEngineService(EngineService):
850 """An EngineService subclass that defers execute commands to a separate
851 thread.
852
853 ThreadedEngineService uses twisted.internet.threads.deferToThread to
854 defer execute requests to a separate thread. GUI frontends may want to
855 use ThreadedEngineService as the engine in an
856 IPython.frontend.frontendbase.FrontEndBase subclass to prevent
857 block execution from blocking the GUI thread.
858 """
850 859
851 860 zi.implements(IEngineBase)
852 861
853 862 def __init__(self, shellClass=Interpreter, mpi=None):
854 863 EngineService.__init__(self, shellClass, mpi)
864
865
866 def execute(self, lines):
855 867 # Only import this if we are going to use this class
856 868 from twisted.internet import threads
857 869
858 def execute(self, lines):
859 msg = """engine: %r
860 method: execute(lines)
861 lines = %s""" % (self.id, lines)
862 d = threads.deferToThread(self.executeAndRaise, msg, self.shell.execute, lines)
870 msg = {'engineid':self.id,
871 'method':'execute',
872 'args':[lines]}
873
874 d = threads.deferToThread(self.shell.execute, lines)
863 875 d.addCallback(self.addIDToResult)
864 876 return d
@@ -48,6 +48,18 b' else:'
48 48
49 49 def tearDown(self):
50 50 return self.engine.stopService()
51
52 class ThreadedEngineServiceTest(DeferredTestCase,
53 IEngineCoreTestCase,
54 IEngineSerializedTestCase,
55 IEnginePropertiesTestCase):
56
57 def setUp(self):
58 self.engine = es.ThreadedEngineService()
59 self.engine.startService()
60
61 def tearDown(self):
62 return self.engine.stopService()
51 63
52 64 class QueuedEngineServiceTest(DeferredTestCase,
53 65 IEngineCoreTestCase,
General Comments 0
You need to be logged in to leave comments. Login now