Show More
@@ -1,27 +1,28 b'' | |||||
1 | # encoding: utf-8 |
|
1 | # encoding: utf-8 | |
2 |
# -*- test-case-name: |
|
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.EngineService. | |||
5 |
|
6 | |||
6 | The Cocoa frontend is divided into two classes: |
|
7 | To add an IPython interpreter to a cocoa app, instantiate an | |
7 | - IPythonCocoaController |
|
8 | IPythonCocoaController in a XIB and connect its textView outlet to an | |
8 | - IPythonCLITextViewDelegate |
|
9 | NSTextView instance in your UI. That's it. | |
9 |
|
10 | |||
10 | To add an IPython interpreter to a cocoa app, instantiate both of these classes in an XIB...[FINISH] |
|
11 | Author: Barry Wark | |
11 | """ |
|
12 | """ | |
12 |
|
13 | |||
13 | __docformat__ = "restructuredtext en" |
|
14 | __docformat__ = "restructuredtext en" | |
14 |
|
15 | |||
15 |
#----------------------------------------------------------------------------- |
|
16 | #----------------------------------------------------------------------------- | |
16 | # Copyright (C) 2008 Barry Wark <barrywark@gmail.com> |
|
17 | # Copyright (C) 2008 The IPython Development Team | |
17 | # |
|
18 | # | |
18 | # 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 | |
19 | # the file COPYING, distributed as part of this software. |
|
20 | # the file COPYING, distributed as part of this software. | |
20 |
#----------------------------------------------------------------------------- |
|
21 | #----------------------------------------------------------------------------- | |
21 |
|
22 | |||
22 |
#----------------------------------------------------------------------------- |
|
23 | #----------------------------------------------------------------------------- | |
23 | # Imports |
|
24 | # Imports | |
24 |
#----------------------------------------------------------------------------- |
|
25 | #----------------------------------------------------------------------------- | |
25 |
|
26 | |||
26 | import objc |
|
27 | import objc | |
27 | import uuid |
|
28 | import uuid | |
@@ -42,12 +43,13 b' from IPython.frontend.frontendbase import FrontEndBase' | |||||
42 | from twisted.internet.threads import blockingCallFromThread |
|
43 | from twisted.internet.threads import blockingCallFromThread | |
43 | from twisted.python.failure import Failure |
|
44 | from twisted.python.failure import Failure | |
44 |
|
45 | |||
45 |
#------------------------------------------------------------------------------ |
|
46 | #------------------------------------------------------------------------------ | |
46 | # Classes to implement the Cocoa frontend |
|
47 | # Classes to implement the Cocoa frontend | |
47 |
#------------------------------------------------------------------------------ |
|
48 | #------------------------------------------------------------------------------ | |
48 |
|
49 | |||
49 | # TODO: |
|
50 | # TODO: | |
50 |
# 1. use MultiEngineClient and out-of-process engine rather than |
|
51 | # 1. use MultiEngineClient and out-of-process engine rather than | |
|
52 | # ThreadedEngineService? | |||
51 | # 2. integrate Xgrid launching of engines |
|
53 | # 2. integrate Xgrid launching of engines | |
52 |
|
54 | |||
53 |
|
55 | |||
@@ -89,16 +91,17 b' class IPythonCocoaController(NSObject, FrontEndBase):' | |||||
89 | NSLog('IPython engine started') |
|
91 | NSLog('IPython engine started') | |
90 |
|
92 | |||
91 | # Register for app termination |
|
93 | # Register for app termination | |
92 |
NSNotificationCenter.defaultCenter().addObserver_selector_name_object_( |
|
94 | NSNotificationCenter.defaultCenter().addObserver_selector_name_object_( | |
93 |
|
|
95 | self, | |
94 |
|
|
96 | 'appWillTerminate:', | |
95 |
|
|
97 | NSApplicationWillTerminateNotification, | |
|
98 | None) | |||
96 |
|
99 | |||
97 | self.textView.setDelegate_(self) |
|
100 | self.textView.setDelegate_(self) | |
98 | self.textView.enclosingScrollView().setHasVerticalRuler_(True) |
|
101 | self.textView.enclosingScrollView().setHasVerticalRuler_(True) | |
99 | self.verticalRulerView = NSRulerView.alloc().initWithScrollView_orientation_( |
|
102 | self.verticalRulerView = NSRulerView.alloc().initWithScrollView_orientation_( | |
100 | self.textView.enclosingScrollView(), |
|
103 | self.textView.enclosingScrollView(), | |
101 | NSVerticalRuler) |
|
104 | NSVerticalRuler) | |
102 | self.verticalRulerView.setClientView_(self.textView) |
|
105 | self.verticalRulerView.setClientView_(self.textView) | |
103 | self.startCLIForTextView() |
|
106 | self.startCLIForTextView() | |
104 |
|
107 | |||
@@ -163,7 +166,8 b' class IPythonCocoaController(NSObject, FrontEndBase):' | |||||
163 | def startCLIForTextView(self): |
|
166 | def startCLIForTextView(self): | |
164 | """Print banner""" |
|
167 | """Print banner""" | |
165 |
|
168 | |||
166 |
banner = """IPython1 %s -- An enhanced Interactive Python.""" % |
|
169 | banner = """IPython1 %s -- An enhanced Interactive Python.""" % \ | |
|
170 | IPython.__version__ | |||
167 |
|
171 | |||
168 | self.insert_text(banner + '\n\n') |
|
172 | self.insert_text(banner + '\n\n') | |
169 |
|
173 | |||
@@ -206,14 +210,18 b' class IPythonCocoaController(NSObject, FrontEndBase):' | |||||
206 | return True |
|
210 | return True | |
207 |
|
211 | |||
208 | elif(selector == 'moveToBeginningOfParagraph:'): |
|
212 | elif(selector == 'moveToBeginningOfParagraph:'): | |
209 |
textView.setSelectedRange_(NSMakeRange( |
|
213 | textView.setSelectedRange_(NSMakeRange( | |
|
214 | self.currentBlockRange().location, | |||
|
215 | 0)) | |||
210 | return True |
|
216 | return True | |
211 | elif(selector == 'moveToEndOfParagraph:'): |
|
217 | elif(selector == 'moveToEndOfParagraph:'): | |
212 |
textView.setSelectedRange_(NSMakeRange( |
|
218 | textView.setSelectedRange_(NSMakeRange( | |
213 |
|
|
219 | self.currentBlockRange().location + \ | |
|
220 | self.currentBlockRange().length, 0)) | |||
214 | return True |
|
221 | return True | |
215 | elif(selector == 'deleteToEndOfParagraph:'): |
|
222 | elif(selector == 'deleteToEndOfParagraph:'): | |
216 |
if(textView.selectedRange().location <= |
|
223 | if(textView.selectedRange().location <= \ | |
|
224 | self.currentBlockRange().location): | |||
217 | # Intersect the selected range with the current line range |
|
225 | # Intersect the selected range with the current line range | |
218 | if(self.currentBlockRange().length < 0): |
|
226 | if(self.currentBlockRange().length < 0): | |
219 | self.blockRanges[self.currentBlockID].length = 0 |
|
227 | self.blockRanges[self.currentBlockID].length = 0 | |
@@ -235,7 +243,8 b' class IPythonCocoaController(NSObject, FrontEndBase):' | |||||
235 |
|
243 | |||
236 | elif(selector == 'deleteBackward:'): |
|
244 | elif(selector == 'deleteBackward:'): | |
237 | #if we're at the beginning of the current block, ignore |
|
245 | #if we're at the beginning of the current block, ignore | |
238 |
if(textView.selectedRange().location == |
|
246 | if(textView.selectedRange().location == \ | |
|
247 | self.currentBlockRange().location): | |||
239 | return True |
|
248 | return True | |
240 | else: |
|
249 | else: | |
241 | self.currentBlockRange().length-=1 |
|
250 | self.currentBlockRange().length-=1 | |
@@ -243,14 +252,15 b' class IPythonCocoaController(NSObject, FrontEndBase):' | |||||
243 | return False |
|
252 | return False | |
244 |
|
253 | |||
245 |
|
254 | |||
246 |
def textView_shouldChangeTextInRanges_replacementStrings_(self, |
|
255 | def textView_shouldChangeTextInRanges_replacementStrings_(self, | |
|
256 | textView, ranges, replacementStrings): | |||
247 | """ |
|
257 | """ | |
248 | Delegate method for NSTextView. |
|
258 | Delegate method for NSTextView. | |
249 |
|
259 | |||
250 |
Refuse change text in ranges not at end, but make those changes at |
|
260 | Refuse change text in ranges not at end, but make those changes at | |
|
261 | end. | |||
251 | """ |
|
262 | """ | |
252 |
|
263 | |||
253 | #print 'textView_shouldChangeTextInRanges_replacementStrings_:',ranges,replacementStrings |
|
|||
254 | assert(len(ranges) == len(replacementStrings)) |
|
264 | assert(len(ranges) == len(replacementStrings)) | |
255 | allow = True |
|
265 | allow = True | |
256 | for r,s in zip(ranges, replacementStrings): |
|
266 | for r,s in zip(ranges, replacementStrings): | |
@@ -261,13 +271,17 b' class IPythonCocoaController(NSObject, FrontEndBase):' | |||||
261 | allow = False |
|
271 | allow = False | |
262 |
|
272 | |||
263 |
|
273 | |||
264 |
self.blockRanges.setdefault(self.currentBlockID, |
|
274 | self.blockRanges.setdefault(self.currentBlockID, | |
|
275 | self.currentBlockRange()).length +=\ | |||
|
276 | len(s) | |||
265 |
|
277 | |||
266 | return allow |
|
278 | return allow | |
267 |
|
279 | |||
268 |
def textView_completions_forPartialWordRange_indexOfSelectedItem_(self, |
|
280 | def textView_completions_forPartialWordRange_indexOfSelectedItem_(self, | |
|
281 | textView, words, charRange, index): | |||
269 | try: |
|
282 | try: | |
270 |
t |
|
283 | ts = textView.textStorage() | |
|
284 | token = ts.string().substringWithRange_(charRange) | |||
271 | completions = blockingCallFromThread(self.complete, token) |
|
285 | completions = blockingCallFromThread(self.complete, token) | |
272 | except: |
|
286 | except: | |
273 | completions = objc.nil |
|
287 | completions = objc.nil | |
@@ -288,7 +302,9 b' class IPythonCocoaController(NSObject, FrontEndBase):' | |||||
288 | return uuid.uuid4() |
|
302 | return uuid.uuid4() | |
289 |
|
303 | |||
290 | def currentBlockRange(self): |
|
304 | def currentBlockRange(self): | |
291 |
return self.blockRanges.get(self.currentBlockID, |
|
305 | return self.blockRanges.get(self.currentBlockID, | |
|
306 | NSMakeRange(self.textView.textStorage().length(), | |||
|
307 | 0)) | |||
292 |
|
308 | |||
293 | def currentBlock(self): |
|
309 | def currentBlock(self): | |
294 | """The current block's text""" |
|
310 | """The current block's text""" | |
@@ -298,7 +314,8 b' class IPythonCocoaController(NSObject, FrontEndBase):' | |||||
298 | def textForRange(self, textRange): |
|
314 | def textForRange(self, textRange): | |
299 | """textForRange""" |
|
315 | """textForRange""" | |
300 |
|
316 | |||
301 |
|
|
317 | ts = self.textView.textStorage() | |
|
318 | return ts.string().substringWithRange_(textRange) | |||
302 |
|
319 | |||
303 | def currentLine(self): |
|
320 | def currentLine(self): | |
304 | block = self.textForRange(self.currentBlockRange()) |
|
321 | block = self.textForRange(self.currentBlockRange()) | |
@@ -313,9 +330,9 b' class IPythonCocoaController(NSObject, FrontEndBase):' | |||||
313 |
|
330 | |||
314 |
|
331 | |||
315 | self.insert_text(self.input_prompt(result=result), |
|
332 | self.insert_text(self.input_prompt(result=result), | |
316 |
|
|
333 | textRange=NSMakeRange(self.blockRanges[blockID].location,0), | |
317 |
|
|
334 | scrollToVisible=False | |
318 |
|
|
335 | ) | |
319 |
|
336 | |||
320 | return result |
|
337 | return result | |
321 |
|
338 | |||
@@ -327,10 +344,11 b' class IPythonCocoaController(NSObject, FrontEndBase):' | |||||
327 |
|
344 | |||
328 | #print inputRange,self.currentBlockRange() |
|
345 | #print inputRange,self.currentBlockRange() | |
329 | self.insert_text('\n' + |
|
346 | self.insert_text('\n' + | |
330 |
|
|
347 | self.output_prompt(result) + | |
331 |
|
|
348 | result.get('display',{}).get('pprint','') + | |
332 |
|
|
349 | '\n\n', | |
333 |
|
|
350 | textRange=NSMakeRange(inputRange.location+inputRange.length, | |
|
351 | 0)) | |||
334 | return result |
|
352 | return result | |
335 |
|
353 | |||
336 |
|
354 | |||
@@ -341,7 +359,9 b' class IPythonCocoaController(NSObject, FrontEndBase):' | |||||
341 |
|
359 | |||
342 |
|
360 | |||
343 | def insert_text(self, string=None, textRange=None, scrollToVisible=True): |
|
361 | def insert_text(self, string=None, textRange=None, scrollToVisible=True): | |
344 |
"""Insert text into textView at textRange, updating blockRanges |
|
362 | """Insert text into textView at textRange, updating blockRanges | |
|
363 | as necessary | |||
|
364 | """ | |||
345 |
|
365 | |||
346 | if(textRange == None): |
|
366 | if(textRange == None): | |
347 | textRange = NSMakeRange(self.textView.textStorage().length(), 0) #range for end of text |
|
367 | textRange = NSMakeRange(self.textView.textStorage().length(), 0) #range for end of text |
@@ -1,27 +1,20 b'' | |||||
1 | # encoding: utf-8 |
|
1 | # encoding: utf-8 | |
2 |
"""This file contains unittests for the |
|
2 | """This file contains unittests for the | |
3 |
|
3 | IPython.frontend.cocoa.cocoa_frontend module. | ||
4 | Things that should be tested: |
|
|||
5 |
|
||||
6 | - IPythonCocoaController instantiates an IEngineInteractive |
|
|||
7 | - IPythonCocoaController executes code on the engine |
|
|||
8 | - IPythonCocoaController mirrors engine's user_ns |
|
|||
9 | """ |
|
4 | """ | |
10 | __docformat__ = "restructuredtext en" |
|
5 | __docformat__ = "restructuredtext en" | |
11 |
|
6 | |||
12 |
#--------------------------------------------------------------------------- |
|
7 | #--------------------------------------------------------------------------- | |
13 | # Copyright (C) 2005 Fernando Perez <fperez@colorado.edu> |
|
8 | # Copyright (C) 2005 The IPython Development Team | |
14 | # Brian E Granger <ellisonbg@gmail.com> |
|
9 | # | |
15 | # Benjamin Ragan-Kelley <benjaminrk@gmail.com> |
|
10 | # Distributed under the terms of the BSD License. The full license is in | |
16 | # |
|
11 | # the file COPYING, distributed as part of this software. | |
17 | # Distributed under the terms of the BSD License. The full license is in |
|
12 | #--------------------------------------------------------------------------- | |
18 | # the file COPYING, distributed as part of this software. |
|
13 | ||
19 |
#--------------------------------------------------------------------------- |
|
14 | #--------------------------------------------------------------------------- | |
20 |
|
15 | # Imports | ||
21 |
#--------------------------------------------------------------------------- |
|
16 | #--------------------------------------------------------------------------- | |
22 | # Imports |
|
17 | from IPython.kernel.core.interpreter import Interpreter | |
23 | #------------------------------------------------------------------------------- |
|
|||
24 | from IPython.kernel.core.interpreter import Interpreter |
|
|||
25 | import IPython.kernel.engineservice as es |
|
18 | import IPython.kernel.engineservice as es | |
26 | from IPython.testing.util import DeferredTestCase |
|
19 | from IPython.testing.util import DeferredTestCase | |
27 | from twisted.internet.defer import succeed |
|
20 | from twisted.internet.defer import succeed | |
@@ -51,7 +44,9 b' class TestIPythonCocoaControler(DeferredTestCase):' | |||||
51 | del result['number'] |
|
44 | del result['number'] | |
52 | del result['id'] |
|
45 | del result['id'] | |
53 | return result |
|
46 | return result | |
54 | self.assertDeferredEquals(self.controller.execute(code).addCallback(removeNumberAndID), expected) |
|
47 | self.assertDeferredEquals( | |
|
48 | self.controller.execute(code).addCallback(removeNumberAndID), | |||
|
49 | expected) | |||
55 |
|
50 | |||
56 | def testControllerMirrorsUserNSWithValuesAsStrings(self): |
|
51 | def testControllerMirrorsUserNSWithValuesAsStrings(self): | |
57 | code = """userns1=1;userns2=2""" |
|
52 | code = """userns1=1;userns2=2""" |
@@ -1,6 +1,8 b'' | |||||
1 | # encoding: utf-8 |
|
1 | # encoding: utf-8 | |
|
2 | # -*- test-case-name: IPython.frontend.tests.test_frontendbase -*- | |||
2 | """ |
|
3 | """ | |
3 |
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. | |||
4 |
|
6 | |||
5 | Frontend implementations will likely want to subclass FrontEndBase. |
|
7 | Frontend implementations will likely want to subclass FrontEndBase. | |
6 |
|
8 | |||
@@ -57,20 +59,26 b' class IFrontEndFactory(zi.Interface):' | |||||
57 | class IFrontEnd(zi.Interface): |
|
59 | class IFrontEnd(zi.Interface): | |
58 | """Interface for frontends. All methods return t.i.d.Deferred""" |
|
60 | """Interface for frontends. All methods return t.i.d.Deferred""" | |
59 |
|
61 | |||
60 |
zi.Attribute("input_prompt_template", "string.Template instance |
|
62 | zi.Attribute("input_prompt_template", "string.Template instance\ | |
61 | zi.Attribute("output_prompt_template", "string.Template instance substituteable with execute result.") |
|
63 | substituteable with execute result.") | |
62 |
zi.Attribute(" |
|
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.") | |||
63 |
|
68 | |||
64 | def update_cell_prompt(self, result): |
|
69 | def update_cell_prompt(self, result): | |
65 | """Subclass may override to update the input prompt for a block. |
|
70 | """Subclass may override to update the input prompt for a block. | |
66 |
Since this method will be called as a |
|
71 | Since this method will be called as a | |
|
72 | twisted.internet.defer.Deferred's callback, | |||
67 | implementations should return result when finished.""" |
|
73 | implementations should return result when finished.""" | |
68 |
|
74 | |||
69 | pass |
|
75 | pass | |
70 |
|
76 | |||
71 | def render_result(self, result): |
|
77 | def render_result(self, result): | |
72 |
"""Render the result of an execute call. Implementors may choose the |
|
78 | """Render the result of an execute call. Implementors may choose the | |
73 | For example, a notebook-style frontend might render a Chaco plot inline. |
|
79 | method of rendering. | |
|
80 | For example, a notebook-style frontend might render a Chaco plot | |||
|
81 | inline. | |||
74 |
|
82 | |||
75 | Parameters: |
|
83 | Parameters: | |
76 | result : dict (result of IEngineBase.execute ) |
|
84 | result : dict (result of IEngineBase.execute ) | |
@@ -82,24 +90,31 b' class IFrontEnd(zi.Interface):' | |||||
82 | pass |
|
90 | pass | |
83 |
|
91 | |||
84 | def render_error(self, failure): |
|
92 | def render_error(self, failure): | |
85 |
"""Subclasses must override to render the failure. Since this method |
|
93 | """Subclasses must override to render the failure. Since this method | |
86 |
twisted.internet.defer.Deferred's callback, |
|
94 | ill be called as a twisted.internet.defer.Deferred's callback, | |
87 | when finished.""" |
|
95 | implementations should return result when finished. | |
|
96 | """ | |||
88 |
|
97 | |||
89 | pass |
|
98 | pass | |
90 |
|
99 | |||
91 |
|
100 | |||
92 | def input_prompt(result={}): |
|
101 | def input_prompt(result={}): | |
93 |
"""Returns the input prompt by subsituting into |
|
102 | """Returns the input prompt by subsituting into | |
|
103 | self.input_prompt_template | |||
|
104 | """ | |||
94 | pass |
|
105 | pass | |
95 |
|
106 | |||
96 | def output_prompt(result): |
|
107 | def output_prompt(result): | |
97 |
"""Returns the output prompt by subsituting into |
|
108 | """Returns the output prompt by subsituting into | |
|
109 | self.output_prompt_template | |||
|
110 | """ | |||
98 |
|
111 | |||
99 | pass |
|
112 | pass | |
100 |
|
113 | |||
101 | def continuation_prompt(): |
|
114 | def continuation_prompt(): | |
102 |
"""Returns the continuation prompt by subsituting into |
|
115 | """Returns the continuation prompt by subsituting into | |
|
116 | self.continuation_prompt_template | |||
|
117 | """ | |||
103 |
|
118 | |||
104 | pass |
|
119 | pass | |
105 |
|
120 | |||
@@ -221,7 +236,8 b' class FrontEndBase(object):' | |||||
221 | Parameters: |
|
236 | Parameters: | |
222 | block : {str, AST} |
|
237 | block : {str, AST} | |
223 | blockID : any |
|
238 | blockID : any | |
224 |
Caller may provide an ID to identify this block. |
|
239 | Caller may provide an ID to identify this block. | |
|
240 | result['blockID'] := blockID | |||
225 |
|
241 | |||
226 | Result: |
|
242 | Result: | |
227 | Deferred result of self.interpreter.execute |
|
243 | Deferred result of self.interpreter.execute | |
@@ -243,8 +259,8 b' class FrontEndBase(object):' | |||||
243 |
|
259 | |||
244 |
|
260 | |||
245 | def _add_block_id(self, result, blockID): |
|
261 | def _add_block_id(self, result, blockID): | |
246 |
"""Add the blockID to result or failure. Unfortunatley, we have to |
|
262 | """Add the blockID to result or failure. Unfortunatley, we have to | |
247 | differently than result dicts |
|
263 | treat failures differently than result dicts. | |
248 | """ |
|
264 | """ | |
249 |
|
265 | |||
250 | if(isinstance(result, Failure)): |
|
266 | if(isinstance(result, Failure)): | |
@@ -291,11 +307,12 b' class FrontEndBase(object):' | |||||
291 |
|
307 | |||
292 | def update_cell_prompt(self, result): |
|
308 | def update_cell_prompt(self, result): | |
293 | """Subclass may override to update the input prompt for a block. |
|
309 | """Subclass may override to update the input prompt for a block. | |
294 |
Since this method will be called as a |
|
310 | Since this method will be called as a | |
295 | implementations should return result when finished. |
|
311 | twisted.internet.defer.Deferred's callback, implementations should | |
|
312 | return result when finished. | |||
296 |
|
313 | |||
297 |
NP: result is a failure if the execute returned a failre. |
|
314 | NP: result is a failure if the execute returned a failre. | |
298 | do something like:: |
|
315 | To get the blockID, you should do something like:: | |
299 | if(isinstance(result, twisted.python.failure.Failure)): |
|
316 | if(isinstance(result, twisted.python.failure.Failure)): | |
300 | blockID = result.blockID |
|
317 | blockID = result.blockID | |
301 | else: |
|
318 | else: | |
@@ -308,17 +325,18 b' class FrontEndBase(object):' | |||||
308 |
|
325 | |||
309 |
|
326 | |||
310 | def render_result(self, result): |
|
327 | def render_result(self, result): | |
311 |
"""Subclasses must override to render result. Since this method will |
|
328 | """Subclasses must override to render result. Since this method will | |
312 |
twisted.internet.defer.Deferred's callback, |
|
329 | be called as a twisted.internet.defer.Deferred's callback, | |
313 | when finished.""" |
|
330 | implementations should return result when finished. | |
|
331 | """ | |||
314 |
|
332 | |||
315 | return result |
|
333 | return result | |
316 |
|
334 | |||
317 |
|
335 | |||
318 | def render_error(self, failure): |
|
336 | def render_error(self, failure): | |
319 |
"""Subclasses must override to render the failure. Since this method |
|
337 | """Subclasses must override to render the failure. Since this method | |
320 |
twisted.internet.defer.Deferred's callback, |
|
338 | will be called as a twisted.internet.defer.Deferred's callback, | |
321 | when finished.""" |
|
339 | implementations should return result when finished.""" | |
322 |
|
340 | |||
323 | return failure |
|
341 | return failure | |
324 |
|
342 |
@@ -4,16 +4,16 b'' | |||||
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 | |
@@ -22,7 +22,8 b' from IPython.kernel.engineservice import EngineService' | |||||
22 | class FrontEndCallbackChecker(frontendbase.FrontEndBase): |
|
22 | class FrontEndCallbackChecker(frontendbase.FrontEndBase): | |
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 | self.updateCalled = False |
|
27 | self.updateCalled = False | |
27 | self.renderResultCalled = False |
|
28 | self.renderResultCalled = False | |
28 | self.renderErrorCalled = False |
|
29 | self.renderErrorCalled = False | |
@@ -51,7 +52,8 b' class TestFrontendBase(unittest.TestCase):' | |||||
51 |
|
52 | |||
52 |
|
53 | |||
53 | def test_implements_IFrontEnd(self): |
|
54 | def test_implements_IFrontEnd(self): | |
54 |
assert(frontendbase.IFrontEnd.implementedBy( |
|
55 | assert(frontendbase.IFrontEnd.implementedBy( | |
|
56 | frontendbase.FrontEndBase)) | |||
55 |
|
57 | |||
56 |
|
58 | |||
57 | def test_is_complete_returns_False_for_incomplete_block(self): |
|
59 | def test_is_complete_returns_False_for_incomplete_block(self): |
@@ -847,11 +847,13 b' class Command(object):' | |||||
847 | self.deferred.errback(reason) |
|
847 | self.deferred.errback(reason) | |
848 |
|
848 | |||
849 | class ThreadedEngineService(EngineService): |
|
849 | class ThreadedEngineService(EngineService): | |
850 |
"""An EngineService subclass that defers execute commands to a separate |
|
850 | """An EngineService subclass that defers execute commands to a separate | |
|
851 | thread. | |||
851 |
|
852 | |||
852 |
ThreadedEngineService uses twisted.internet.threads.deferToThread to |
|
853 | ThreadedEngineService uses twisted.internet.threads.deferToThread to | |
853 |
requests to a separate thread. GUI frontends may want to |
|
854 | defer execute requests to a separate thread. GUI frontends may want to | |
854 | the engine in an IPython.frontend.frontendbase.FrontEndBase subclass to prevent |
|
855 | use ThreadedEngineService as the engine in an | |
|
856 | IPython.frontend.frontendbase.FrontEndBase subclass to prevent | |||
855 | block execution from blocking the GUI thread. |
|
857 | block execution from blocking the GUI thread. | |
856 | """ |
|
858 | """ | |
857 |
|
859 |
General Comments 0
You need to be logged in to leave comments.
Login now