diff --git a/IPython/frontend/cocoa/cocoa_frontend.py b/IPython/frontend/cocoa/cocoa_frontend.py index c28a665..e21da15 100644 --- a/IPython/frontend/cocoa/cocoa_frontend.py +++ b/IPython/frontend/cocoa/cocoa_frontend.py @@ -29,7 +29,8 @@ import uuid from Foundation import NSObject, NSMutableArray, NSMutableDictionary,\ NSLog, NSNotificationCenter, NSMakeRange,\ - NSLocalizedString, NSIntersectionRange + NSLocalizedString, NSIntersectionRange,\ + NSString, NSAutoreleasePool from AppKit import NSApplicationWillTerminateNotification, NSBeep,\ NSTextView, NSRulerView, NSVerticalRuler @@ -52,7 +53,29 @@ from twisted.python.failure import Failure # ThreadedEngineService? # 2. integrate Xgrid launching of engines +class AutoreleasePoolWrappedThreadedEngineService(ThreadedEngineService): + """Wrap all blocks in an NSAutoreleasePool""" + def wrapped_execute(self, lines): + """wrapped_execute""" + + p = NSAutoreleasePool.alloc().init() + result = self.shell.execute(lines) + p.drain() + + return result + + def execute(self, lines): + # Only import this if we are going to use this class + from twisted.internet import threads + + msg = {'engineid':self.id, + 'method':'execute', + 'args':[lines]} + + d = threads.deferToThread(self.wrapped_execute, lines) + d.addCallback(self.addIDToResult) + return d class IPythonCocoaController(NSObject, FrontEndBase): @@ -62,7 +85,8 @@ class IPythonCocoaController(NSObject, FrontEndBase): def init(self): self = super(IPythonCocoaController, self).init() - FrontEndBase.__init__(self, engine=ThreadedEngineService()) + FrontEndBase.__init__(self, + engine=AutoreleasePoolWrappedThreadedEngineService()) if(self != None): self._common_init() @@ -133,13 +157,42 @@ class IPythonCocoaController(NSObject, FrontEndBase): def execute(self, block, blockID=None): self.waitingForEngine = True self.willChangeValueForKey_('commandHistory') - d = super(IPythonCocoaController, self).execute(block, blockID) + d = super(IPythonCocoaController, self).execute(block, + blockID) d.addBoth(self._engine_done) d.addCallback(self._update_user_ns) return d + + def push_(self, namespace): + """Push dictionary of key=>values to python namespace""" + + self.waitingForEngine = True + self.willChangeValueForKey_('commandHistory') + d = self.engine.push(namespace) + d.addBoth(self._engine_done) + d.addCallback(self._update_user_ns) + + + def pull_(self, keys): + """Pull keys from python namespace""" + self.waitingForEngine = True + result = blockingCallFromThread(self.engine.pull, keys) + self.waitingForEngine = False + + def executeFileAtPath_(self, path): + """Execute file at path in an empty namespace. Update the engine + user_ns with the resulting locals.""" + + lines,err = NSString.stringWithContentsOfFile_encoding_error_( + path, + NSString.defaultCStringEncoding(), + None) + self.engine.execute(lines) + + def _engine_done(self, x): self.waitingForEngine = False self.didChangeValueForKey_('commandHistory') @@ -166,14 +219,14 @@ class IPythonCocoaController(NSObject, FrontEndBase): self.didChangeValueForKey_('userNS') - def update_cell_prompt(self, result): + def update_cell_prompt(self, result, blockID=None): if(isinstance(result, Failure)): - blockID = result.blockID + self.insert_text(self.input_prompt(), + textRange=NSMakeRange(self.blockRanges[blockID].location,0), + scrollToVisible=False + ) else: - blockID = result['blockID'] - - - self.insert_text(self.input_prompt(result=result), + self.insert_text(self.input_prompt(number=result['number']), textRange=NSMakeRange(self.blockRanges[blockID].location,0), scrollToVisible=False ) diff --git a/IPython/frontend/cocoa/plugin/FrontendLoader.py b/IPython/frontend/cocoa/plugin/FrontendLoader.py deleted file mode 100644 index 2c32f37..0000000 --- a/IPython/frontend/cocoa/plugin/FrontendLoader.py +++ /dev/null @@ -1,17 +0,0 @@ -# encoding: utf-8 -""" -Provides a namespace for loading the Cocoa frontend via a Cocoa plugin. - -Author: Barry Wark -""" -__docformat__ = "restructuredtext en" - -#----------------------------------------------------------------------------- -# Copyright (C) 2008 The IPython Development Team -# -# Distributed under the terms of the BSD License. The full license is in -# the file COPYING, distributed as part of this software. -#----------------------------------------------------------------------------- - -from Foundation import NSObject -from IPython.frontend.cocoa.cocoa_frontend import IPythonCocoaController \ No newline at end of file diff --git a/IPython/frontend/cocoa/plugin/Makefile b/IPython/frontend/cocoa/plugin/Makefile index 66ddc3b..109d8bf 100644 --- a/IPython/frontend/cocoa/plugin/Makefile +++ b/IPython/frontend/cocoa/plugin/Makefile @@ -2,4 +2,5 @@ include ./plugins.mk all : dist/IPythonCocoaController.plugin -dist/IPythonCocoaController.plugin : ./FrontendLoader.py ./setup.py \ No newline at end of file +dist/IPythonCocoaController.plugin : ./IPythonCocoaFrontendLoader.py\ + ./setup.py \ No newline at end of file diff --git a/IPython/frontend/cocoa/plugin/plugins.mk b/IPython/frontend/cocoa/plugin/plugins.mk index af5d0e9..2df61f0 100644 --- a/IPython/frontend/cocoa/plugin/plugins.mk +++ b/IPython/frontend/cocoa/plugin/plugins.mk @@ -1,5 +1,4 @@ %.plugin:: - mkdir -p plugin rm -rf dist/$(notdir $@) rm -rf build dist && \ python setup.py py2app -s diff --git a/IPython/frontend/cocoa/plugin/setup.py b/IPython/frontend/cocoa/plugin/setup.py index 984e9cc..6811e11 100644 --- a/IPython/frontend/cocoa/plugin/setup.py +++ b/IPython/frontend/cocoa/plugin/setup.py @@ -25,10 +25,10 @@ infoPlist = dict( ) setup( - plugin=['FrontendLoader.py'], + plugin=['IPythonCocoaFrontendLoader.py'], setup_requires=['py2app'], options=dict(py2app=dict( plist=infoPlist, - excludes=['IPython'] + excludes=['IPython','twisted'] )), ) \ No newline at end of file diff --git a/IPython/frontend/frontendbase.py b/IPython/frontend/frontendbase.py index 644166a..45c5684 100644 --- a/IPython/frontend/frontendbase.py +++ b/IPython/frontend/frontendbase.py @@ -66,23 +66,20 @@ class IFrontEnd(zi.Interface): zi.Attribute("continuation_prompt_template", "string.Template instance\ substituteable with execute result.") - def update_cell_prompt(self, result): + def update_cell_prompt(result, blockID=None): """Subclass may override to update the input prompt for a block. Since this method will be called as a - twisted.internet.defer.Deferred's callback, + twisted.internet.defer.Deferred's callback/errback, implementations should return result when finished. - NB: result is a failure if the execute returned a failre. - To get the blockID, you should do something like:: - if(isinstance(result, twisted.python.failure.Failure)): - blockID = result.blockID - else: - blockID = result['blockID'] + Result is a result dict in case of success, and a + twisted.python.util.failure.Failure in case of an error """ pass - def render_result(self, result): + + def render_result(result): """Render the result of an execute call. Implementors may choose the method of rendering. For example, a notebook-style frontend might render a Chaco plot @@ -90,6 +87,7 @@ class IFrontEnd(zi.Interface): Parameters: result : dict (result of IEngineBase.execute ) + blockID = result['blockID'] Result: Output of frontend rendering @@ -97,22 +95,24 @@ class IFrontEnd(zi.Interface): pass - def render_error(self, failure): + def render_error(failure): """Subclasses must override to render the failure. Since this method - ill be called as a twisted.internet.defer.Deferred's callback, + will be called as a twisted.internet.defer.Deferred's callback, implementations should return result when finished. + + blockID = failure.blockID """ pass - def input_prompt(result={}): + def input_prompt(number=None): """Returns the input prompt by subsituting into self.input_prompt_template """ pass - def output_prompt(result): + def output_prompt(number=None): """Returns the output prompt by subsituting into self.output_prompt_template """ @@ -180,15 +180,12 @@ class FrontEndBase(object): self.history = history - def input_prompt(self, result={}): + def input_prompt(self, number=None): """Returns the current input prompt It would be great to use ipython1.core.prompts.Prompt1 here """ - - result.setdefault('number','') - - return self.input_prompt_template.safe_substitute(result) + return self.input_prompt_template.safe_substitute({'number':number}) def continuation_prompt(self): @@ -196,10 +193,10 @@ class FrontEndBase(object): return self.continuation_prompt_template.safe_substitute() - def output_prompt(self, result): + def output_prompt(self, number=None): """Returns the output prompt for result""" - return self.output_prompt_template.safe_substitute(result) + return self.output_prompt_template.safe_substitute({'number':number}) def is_complete(self, block): @@ -259,25 +256,33 @@ class FrontEndBase(object): d = self.engine.execute(block) d.addCallback(self._add_history, block=block) - d.addBoth(self._add_block_id, blockID) - d.addBoth(self.update_cell_prompt) - d.addCallbacks(self.render_result, errback=self.render_error) + d.addCallbacks(self._add_block_id_for_result, + errback=self._add_block_id_for_failure, + callbackArgs=(blockID,), + errbackArgs=(blockID,)) + d.addBoth(self.update_cell_prompt, blockID=blockID) + d.addCallbacks(self.render_result, + errback=self.render_error) return d - def _add_block_id(self, result, blockID): + def _add_block_id_for_result(self, result, blockID): """Add the blockID to result or failure. Unfortunatley, we have to treat failures differently than result dicts. """ - if(isinstance(result, Failure)): - result.blockID = blockID - else: - result['blockID'] = blockID + result['blockID'] = blockID return result + def _add_block_id_for_failure(self, failure, blockID): + """_add_block_id_for_failure""" + + failure.blockID = blockID + return failure + + def _add_history(self, result, block=None): """Add block to the history""" @@ -313,20 +318,11 @@ class FrontEndBase(object): # Subclasses probably want to override these methods... ### - def update_cell_prompt(self, result): + def update_cell_prompt(self, result, blockID=None): """Subclass may override to update the input prompt for a block. Since this method will be called as a twisted.internet.defer.Deferred's callback, implementations should return result when finished. - - NB: result is a failure if the execute returned a failre. - To get the blockID, you should do something like:: - if(isinstance(result, twisted.python.failure.Failure)): - blockID = result.blockID - else: - blockID = result['blockID'] - - """ return result @@ -344,7 +340,8 @@ class FrontEndBase(object): def render_error(self, failure): """Subclasses must override to render the failure. Since this method will be called as a twisted.internet.defer.Deferred's callback, - implementations should return result when finished.""" + implementations should return result when finished. + """ return failure diff --git a/IPython/frontend/tests/test_frontendbase.py b/IPython/frontend/tests/test_frontendbase.py index 451e302..ff68b6c 100644 --- a/IPython/frontend/tests/test_frontendbase.py +++ b/IPython/frontend/tests/test_frontendbase.py @@ -28,7 +28,7 @@ class FrontEndCallbackChecker(frontendbase.FrontEndBase): self.renderResultCalled = False self.renderErrorCalled = False - def update_cell_prompt(self, result): + def update_cell_prompt(self, result, blockID=None): self.updateCalled = True return result