From 06dc07c6483418abc473259bf297bc9f3ab09311 2008-07-01 07:10:44 From: Barry Wark Date: 2008-07-01 07:10:44 Subject: [PATCH] Merged changes from ipython-frontend1. Better error rendering in Cocoa frontend. Refactored frontendbase.FrontEndBase so that FrontEndBase is synchronous and AsynchronousFronEndBase(FrontEndBase) wraps FrontEndBase in Twisted goodness. --- diff --git a/IPython/frontend/cocoa/cocoa_frontend.py b/IPython/frontend/cocoa/cocoa_frontend.py index b366905..a713a2e 100644 --- a/IPython/frontend/cocoa/cocoa_frontend.py +++ b/IPython/frontend/cocoa/cocoa_frontend.py @@ -40,7 +40,7 @@ from pprint import saferepr import IPython from IPython.kernel.engineservice import ThreadedEngineService -from IPython.frontend.frontendbase import FrontEndBase +from IPython.frontend.frontendbase import AsynchronousFrontEndBase from twisted.internet.threads import blockingCallFromThread from twisted.python.failure import Failure @@ -96,14 +96,14 @@ class AutoreleasePoolWrappedThreadedEngineService(ThreadedEngineService): return d -class IPythonCocoaController(NSObject, FrontEndBase): +class IPythonCocoaController(NSObject, AsynchronousFrontEndBase): userNS = objc.ivar() #mirror of engine.user_ns (key=>str(value)) waitingForEngine = objc.ivar().bool() textView = objc.IBOutlet() def init(self): self = super(IPythonCocoaController, self).init() - FrontEndBase.__init__(self, + AsynchronousFrontEndBase.__init__(self, engine=AutoreleasePoolWrappedThreadedEngineService()) if(self != None): self._common_init() diff --git a/IPython/frontend/cocoa/plugin/CocoaFrontendPlugin.xcodeproj/project.pbxproj b/IPython/frontend/cocoa/plugin/CocoaFrontendPlugin.xcodeproj/project.pbxproj new file mode 100644 index 0000000..d544d70 --- /dev/null +++ b/IPython/frontend/cocoa/plugin/CocoaFrontendPlugin.xcodeproj/project.pbxproj @@ -0,0 +1,256 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 42; + objects = { + +/* Begin PBXContainerItemProxy section */ + 4C5B7ADB0E1A0BCD006CB905 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 4C96F4FE0E199AB500B03430 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 4C96F50C0E199AF100B03430 /* Cocoa Frontend Plugin */; + remoteInfo = "Cocoa Frontend Plugin"; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXFileReference section */ + 4C5B7A8D0E1A0B4C006CB905 /* Plugin-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "Plugin-Info.plist"; sourceTree = ""; }; + 4C5B7AD30E1A0BC8006CB905 /* Placeholder (Do Not Use).bundle */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "Placeholder (Do Not Use).bundle"; sourceTree = BUILT_PRODUCTS_DIR; }; + 4C5B7AD40E1A0BC8006CB905 /* Placeholder (Do Not Use)-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "Placeholder (Do Not Use)-Info.plist"; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 4C5B7AD10E1A0BC8006CB905 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 4C5B7A8C0E1A0B4C006CB905 /* Products */ = { + isa = PBXGroup; + children = ( + 4C5B7AD30E1A0BC8006CB905 /* Placeholder (Do Not Use).bundle */, + ); + name = Products; + sourceTree = ""; + }; + 4C96F4FC0E199AB500B03430 = { + isa = PBXGroup; + children = ( + 4C5B7A8C0E1A0B4C006CB905 /* Products */, + 4C5B7A8D0E1A0B4C006CB905 /* Plugin-Info.plist */, + 4C5B7AD40E1A0BC8006CB905 /* Placeholder (Do Not Use)-Info.plist */, + ); + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXLegacyTarget section */ + 4C96F50C0E199AF100B03430 /* Cocoa Frontend Plugin */ = { + isa = PBXLegacyTarget; + buildArgumentsString = "$(ACTION)"; + buildConfigurationList = 4C96F5110E199B3300B03430 /* Build configuration list for PBXLegacyTarget "Cocoa Frontend Plugin" */; + buildPhases = ( + ); + buildToolPath = /usr/bin/make; + buildWorkingDirectory = ""; + dependencies = ( + ); + name = "Cocoa Frontend Plugin"; + passBuildSettingsInEnvironment = 1; + productName = "Cocoa Frontend Plugin"; + }; +/* End PBXLegacyTarget section */ + +/* Begin PBXNativeTarget section */ + 4C5B7AD20E1A0BC8006CB905 /* Placeholder (Do Not Use) */ = { + isa = PBXNativeTarget; + buildConfigurationList = 4C5B7ADA0E1A0BC9006CB905 /* Build configuration list for PBXNativeTarget "Placeholder (Do Not Use)" */; + buildPhases = ( + 4C5B7ACF0E1A0BC8006CB905 /* Resources */, + 4C5B7AD00E1A0BC8006CB905 /* Sources */, + 4C5B7AD10E1A0BC8006CB905 /* Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + 4C5B7ADC0E1A0BCD006CB905 /* PBXTargetDependency */, + ); + name = "Placeholder (Do Not Use)"; + productName = "Placeholder (Do Not Use)"; + productReference = 4C5B7AD30E1A0BC8006CB905 /* Placeholder (Do Not Use).bundle */; + productType = "com.apple.product-type.bundle"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 4C96F4FE0E199AB500B03430 /* Project object */ = { + isa = PBXProject; + buildConfigurationList = 4C96F5010E199AB500B03430 /* Build configuration list for PBXProject "CocoaFrontendPlugin" */; + compatibilityVersion = "Xcode 2.4"; + hasScannedForEncodings = 0; + mainGroup = 4C96F4FC0E199AB500B03430; + productRefGroup = 4C5B7A8C0E1A0B4C006CB905 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 4C96F50C0E199AF100B03430 /* Cocoa Frontend Plugin */, + 4C5B7AD20E1A0BC8006CB905 /* Placeholder (Do Not Use) */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 4C5B7ACF0E1A0BC8006CB905 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 4C5B7AD00E1A0BC8006CB905 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 4C5B7ADC0E1A0BCD006CB905 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 4C96F50C0E199AF100B03430 /* Cocoa Frontend Plugin */; + targetProxy = 4C5B7ADB0E1A0BCD006CB905 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin XCBuildConfiguration section */ + 4C5B7AD50E1A0BC9006CB905 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + COPY_PHASE_STRIP = NO; + GCC_DYNAMIC_NO_PIC = NO; + GCC_ENABLE_FIX_AND_CONTINUE = YES; + GCC_MODEL_TUNING = G5; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PRECOMPILE_PREFIX_HEADER = YES; + GCC_PREFIX_HEADER = "$(SYSTEM_LIBRARY_DIR)/Frameworks/AppKit.framework/Headers/AppKit.h"; + INFOPLIST_FILE = "Placeholder (Do Not Use)-Info.plist"; + INSTALL_PATH = "$(HOME)/Library/Bundles"; + OTHER_LDFLAGS = ( + "-framework", + Foundation, + "-framework", + AppKit, + ); + PREBINDING = NO; + PRODUCT_NAME = "Placeholder (Do Not Use)"; + WRAPPER_EXTENSION = bundle; + ZERO_LINK = YES; + }; + name = Debug; + }; + 4C5B7AD60E1A0BC9006CB905 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + COPY_PHASE_STRIP = YES; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + GCC_ENABLE_FIX_AND_CONTINUE = NO; + GCC_MODEL_TUNING = G5; + GCC_PRECOMPILE_PREFIX_HEADER = YES; + GCC_PREFIX_HEADER = "$(SYSTEM_LIBRARY_DIR)/Frameworks/AppKit.framework/Headers/AppKit.h"; + INFOPLIST_FILE = "Placeholder (Do Not Use)-Info.plist"; + INSTALL_PATH = "$(HOME)/Library/Bundles"; + OTHER_LDFLAGS = ( + "-framework", + Foundation, + "-framework", + AppKit, + ); + PREBINDING = NO; + PRODUCT_NAME = "Placeholder (Do Not Use)"; + WRAPPER_EXTENSION = bundle; + ZERO_LINK = NO; + }; + name = Release; + }; + 4C96F4FF0E199AB500B03430 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + COPY_PHASE_STRIP = NO; + }; + name = Debug; + }; + 4C96F5000E199AB500B03430 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + COPY_PHASE_STRIP = YES; + }; + name = Release; + }; + 4C96F50D0E199AF100B03430 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + COPY_PHASE_STRIP = NO; + GCC_DYNAMIC_NO_PIC = NO; + GCC_OPTIMIZATION_LEVEL = 0; + PRODUCT_NAME = "Cocoa Frontend Plugin"; + }; + name = Debug; + }; + 4C96F50E0E199AF100B03430 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + COPY_PHASE_STRIP = YES; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + GCC_ENABLE_FIX_AND_CONTINUE = NO; + PRODUCT_NAME = "Cocoa Frontend Plugin"; + ZERO_LINK = NO; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 4C5B7ADA0E1A0BC9006CB905 /* Build configuration list for PBXNativeTarget "Placeholder (Do Not Use)" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 4C5B7AD50E1A0BC9006CB905 /* Debug */, + 4C5B7AD60E1A0BC9006CB905 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 4C96F5010E199AB500B03430 /* Build configuration list for PBXProject "CocoaFrontendPlugin" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 4C96F4FF0E199AB500B03430 /* Debug */, + 4C96F5000E199AB500B03430 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 4C96F5110E199B3300B03430 /* Build configuration list for PBXLegacyTarget "Cocoa Frontend Plugin" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 4C96F50D0E199AF100B03430 /* Debug */, + 4C96F50E0E199AF100B03430 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 4C96F4FE0E199AB500B03430 /* Project object */; +} diff --git a/IPython/frontend/cocoa/plugin/IPythonCocoaFrontendLoader.py b/IPython/frontend/cocoa/plugin/IPythonCocoaFrontendLoader.py new file mode 100644 index 0000000..a3b5478 --- /dev/null +++ b/IPython/frontend/cocoa/plugin/IPythonCocoaFrontendLoader.py @@ -0,0 +1,25 @@ +# 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 PyObjCTools import AppHelper +from twisted.internet import _threadedselect + +#make sure _threadedselect is installed first +reactor = _threadedselect.install() + +# load the Cocoa frontend controller +from IPython.frontend.cocoa.cocoa_frontend import IPythonCocoaController +reactor.interleave(AppHelper.callAfter) +assert(reactor.running) diff --git a/IPython/frontend/cocoa/plugin/Placeholder (Do Not Use)-Info.plist b/IPython/frontend/cocoa/plugin/Placeholder (Do Not Use)-Info.plist new file mode 100644 index 0000000..01fd88b --- /dev/null +++ b/IPython/frontend/cocoa/plugin/Placeholder (Do Not Use)-Info.plist @@ -0,0 +1,20 @@ + + + + + CFBundleDevelopmentRegion + English + CFBundleExecutable + ${EXECUTABLE_NAME} + CFBundleIdentifier + com.yourcompany.Placeholder (Do Not Use) + CFBundleInfoDictionaryVersion + 6.0 + CFBundlePackageType + BNDL + CFBundleSignature + ???? + CFBundleVersion + 1.0 + + diff --git a/IPython/frontend/cocoa/plugin/setup.py b/IPython/frontend/cocoa/plugin/setup.py index 6811e11..8393085 100644 --- a/IPython/frontend/cocoa/plugin/setup.py +++ b/IPython/frontend/cocoa/plugin/setup.py @@ -29,6 +29,7 @@ setup( setup_requires=['py2app'], options=dict(py2app=dict( plist=infoPlist, - excludes=['IPython','twisted'] + site_packages=True, + excludes=['IPython','twisted','PyObjCTools'] )), ) \ No newline at end of file diff --git a/IPython/frontend/frontendbase.py b/IPython/frontend/frontendbase.py index 9a3b10e..f74d6b6 100644 --- a/IPython/frontend/frontendbase.py +++ b/IPython/frontend/frontendbase.py @@ -159,9 +159,6 @@ class FrontEndBase(object): - How do we handle completions? """ - zi.implements(IFrontEnd) - zi.classProvides(IFrontEndFactory) - history_cursor = 0 current_indent_level = 0 @@ -171,9 +168,8 @@ class FrontEndBase(object): output_prompt_template = string.Template(rc.prompt_out) continuation_prompt_template = string.Template(rc.prompt_in2) - def __init__(self, engine=None, history=None): - assert(engine==None or IEngineCore.providedBy(engine)) - self.engine = IEngineCore(engine) + def __init__(self, shell=None, history=None): + self.shell = shell if history is None: self.history = FrontEndHistory(input_cache=['']) else: @@ -236,7 +232,7 @@ class FrontEndBase(object): def execute(self, block, blockID=None): - """Execute the block and return result. + """Execute the block and return the result. Parameters: block : {str, AST} @@ -249,22 +245,23 @@ class FrontEndBase(object): """ if(not self.is_complete(block)): - return Failure(Exception("Block is not compilable")) + raise Exception("Block is not compilable") if(blockID == None): blockID = uuid.uuid4() #random UUID - d = self.engine.execute(block) - d.addCallback(self._add_history, block=block) - 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) + try: + result = self.shell.execute(block) + except Exception,e: + e = self._add_block_id_for_failure(e, blockID=blockID) + e = self.update_cell_prompt(e, blockID=blockID) + e = self.render_error(e) + else: + result = self._add_block_id_for_result(result, blockID=blockID) + result = self.update_cell_prompt(result, blockID=blockID) + result = self.render_result(result) - return d + return result def _add_block_id_for_result(self, result, blockID): @@ -347,3 +344,53 @@ class FrontEndBase(object): +class AsynchronousFrontEndBase(FrontEndBase): + """ + Overrides FrontEndBase to wrap execute in a deferred result. + All callbacks are made as callbacks on the deferred result. + """ + + zi.implements(IFrontEnd) + zi.classProvides(IFrontEndFactory) + + def __init__(self, engine=None, history=None): + assert(engine==None or IEngineCore.providedBy(engine)) + self.engine = IEngineCore(engine) + if history is None: + self.history = FrontEndHistory(input_cache=['']) + else: + self.history = history + + + def execute(self, block, blockID=None): + """Execute the block and return the deferred result. + + Parameters: + block : {str, AST} + blockID : any + Caller may provide an ID to identify this block. + result['blockID'] := blockID + + Result: + Deferred result of self.interpreter.execute + """ + + if(not self.is_complete(block)): + return Failure(Exception("Block is not compilable")) + + if(blockID == None): + blockID = uuid.uuid4() #random UUID + + d = self.engine.execute(block) + d.addCallback(self._add_history, block=block) + 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 + + diff --git a/IPython/frontend/tests/test_frontendbase.py b/IPython/frontend/tests/test_frontendbase.py index ff68b6c..1498036 100644 --- a/IPython/frontend/tests/test_frontendbase.py +++ b/IPython/frontend/tests/test_frontendbase.py @@ -19,7 +19,7 @@ import unittest from IPython.frontend import frontendbase from IPython.kernel.engineservice import EngineService -class FrontEndCallbackChecker(frontendbase.FrontEndBase): +class FrontEndCallbackChecker(frontendbase.AsynchronousFrontEndBase): """FrontEndBase subclass for checking callbacks""" def __init__(self, engine=None, history=None): super(FrontEndCallbackChecker, self).__init__(engine=engine, @@ -44,7 +44,7 @@ class FrontEndCallbackChecker(frontendbase.FrontEndBase): -class TestFrontendBase(unittest.TestCase): +class TestAsynchronousFrontendBase(unittest.TestCase): def setUp(self): """Setup the EngineService and FrontEndBase""" @@ -53,7 +53,7 @@ class TestFrontendBase(unittest.TestCase): def test_implements_IFrontEnd(self): assert(frontendbase.IFrontEnd.implementedBy( - frontendbase.FrontEndBase)) + frontendbase.AsynchronousFrontEndBase)) def test_is_complete_returns_False_for_incomplete_block(self):