From 28a67bb1c8ad25d2a6e3881371cf16a1cde92c52 Mon Sep 17 00:00:00 2001
From: Matt Tolton <matt@tolton.com>
Date: Mon, 23 Jun 2008 23:30:12 -0700
Subject: [PATCH] Basic plugin architecture.  Also includes routines to convert from vimscript
 values to cocoa values.

- Plugins can add GUI panels which appear in a drawer (for now)
- Plugins can add global menu items
- Plugins can execute arbitrary vimscript in the corresponding vim instance
---
 src/MacVim/MMAppController.h                |    2 +
 src/MacVim/MMAppController.m                |   36 ++--
 src/MacVim/MMBackend.m                      |  146 +++++++++++++
 src/MacVim/MMPlugInManager.h                |   31 +++
 src/MacVim/MMPlugInManager.m                |  128 +++++++++++
 src/MacVim/MMVimController.h                |    4 +
 src/MacVim/MMVimController.m                |   25 +++
 src/MacVim/MacVim.h                         |    3 +-
 src/MacVim/MacVim.xcodeproj/project.pbxproj |   44 ++++
 src/MacVim/PlugInGUI.h                      |   57 +++++
 src/MacVim/PlugInGUI.m                      |  307 +++++++++++++++++++++++++++
 src/MacVim/PlugInImpl.h                     |   37 ++++
 src/MacVim/PlugInImpl.m                     |  166 +++++++++++++++
 src/MacVim/PlugInInterface.h                |   48 ++++
 src/MacVim/PlugInView.nib/classes.nib       |   98 +++++++++
 src/MacVim/PlugInView.nib/info.nib          |   20 ++
 src/MacVim/PlugInView.nib/keyedobjects.nib  |  Bin 0 -> 3529 bytes
 src/eval.c                                  |    2 +-
 18 files changed, 1136 insertions(+), 18 deletions(-)
 create mode 100644 src/MacVim/MMPlugInManager.h
 create mode 100644 src/MacVim/MMPlugInManager.m
 create mode 100644 src/MacVim/PlugInGUI.h
 create mode 100644 src/MacVim/PlugInGUI.m
 create mode 100644 src/MacVim/PlugInImpl.h
 create mode 100644 src/MacVim/PlugInImpl.m
 create mode 100644 src/MacVim/PlugInInterface.h
 create mode 100644 src/MacVim/PlugInView.nib/classes.nib
 create mode 100644 src/MacVim/PlugInView.nib/info.nib
 create mode 100644 src/MacVim/PlugInView.nib/keyedobjects.nib

diff --git a/src/MacVim/MMAppController.h b/src/MacVim/MMAppController.h
index 366e5a5..2bf7eea 100644
--- a/src/MacVim/MMAppController.h
+++ b/src/MacVim/MMAppController.h
@@ -13,6 +13,7 @@
 
 
 @class MMWindowController;
+@class MMVimController;
 
 
 @interface MMAppController : NSObject <MMAppProtocol> {
@@ -30,6 +31,7 @@
 + (MMAppController *)sharedInstance;
 - (NSMenu *)defaultMainMenu;
 - (NSMenuItem *)appMenuItemTemplate;
+- (MMVimController *)keyVimController;
 - (void)removeVimController:(id)controller;
 - (void)windowControllerWillOpen:(MMWindowController *)windowController;
 - (void)setMainMenu:(NSMenu *)mainMenu;
diff --git a/src/MacVim/MMAppController.m b/src/MacVim/MMAppController.m
index ea8cd4f..8055749 100644
--- a/src/MacVim/MMAppController.m
+++ b/src/MacVim/MMAppController.m
@@ -42,6 +42,8 @@
 #import "MMVimController.h"
 #import "MMWindowController.h"
 #import "Miscellaneous.h"
+#import "MMPreferenceController.h"
+#import "MMPlugInManager.h"
 #import <unistd.h>
 
 
@@ -85,7 +87,6 @@ static int executeInLoginShell(NSString *path, NSArray *args);
 
 
 @interface MMAppController (Private)
-- (MMVimController *)keyVimController;
 - (MMVimController *)topmostVimController;
 - (int)launchVimProcessWithArguments:(NSArray *)args;
 - (NSArray *)filterFilesAndNotify:(NSArray *)files;
@@ -256,6 +257,7 @@ static int executeInLoginShell(NSString *path, NSArray *args);
 - (void)applicationDidFinishLaunching:(NSNotification *)notification
 {
     [NSApp setServicesProvider:self];
+    [[MMPlugInManager sharedManager] loadAllPlugIns];
 }
 
 - (BOOL)applicationShouldOpenUntitledFile:(NSApplication *)sender
@@ -513,6 +515,8 @@ static int executeInLoginShell(NSString *path, NSArray *args);
 
 - (void)applicationWillTerminate:(NSNotification *)notification
 {
+    [[MMPlugInManager sharedManager] unloadAllPlugIns];
+
 #if MM_HANDLE_XCODE_MOD_EVENT
     [[NSAppleEventManager sharedAppleEventManager]
             removeEventHandlerForEventClass:'KAHL'
@@ -857,6 +861,21 @@ static int executeInLoginShell(NSString *path, NSArray *args);
     return array;
 }
 
+- (MMVimController *)keyVimController
+{
+    NSWindow *keyWindow = [NSApp keyWindow];
+    if (keyWindow) {
+        unsigned i, count = [vimControllers count];
+        for (i = 0; i < count; ++i) {
+            MMVimController *vc = [vimControllers objectAtIndex:i];
+            if ([[[vc windowController] window] isEqual:keyWindow])
+                return vc;
+        }
+    }
+
+    return nil;
+}
+
 @end // MMAppController
 
 
@@ -927,21 +946,6 @@ static int executeInLoginShell(NSString *path, NSArray *args);
 
 @implementation MMAppController (Private)
 
-- (MMVimController *)keyVimController
-{
-    NSWindow *keyWindow = [NSApp keyWindow];
-    if (keyWindow) {
-        unsigned i, count = [vimControllers count];
-        for (i = 0; i < count; ++i) {
-            MMVimController *vc = [vimControllers objectAtIndex:i];
-            if ([[[vc windowController] window] isEqual:keyWindow])
-                return vc;
-        }
-    }
-
-    return nil;
-}
-
 - (MMVimController *)topmostVimController
 {
     // Find the topmost visible window which has an associated vim controller.
diff --git a/src/MacVim/MMBackend.m b/src/MacVim/MMBackend.m
index 251ae28..95aa774 100644
--- a/src/MacVim/MMBackend.m
+++ b/src/MacVim/MMBackend.m
@@ -57,6 +57,7 @@ static int eventButtonNumberToVimMouseButton(int buttonNumber);
 // In gui_macvim.m
 vimmenu_T *menu_for_descriptor(NSArray *desc);
 
+static id evalExprCocoa(NSString * expr, NSString ** errstr);
 
 enum {
     MMBlinkStateNone = 0,
@@ -1141,6 +1142,13 @@ static NSString *MMSymlinkWarningString =
     }
 }
 
+- (id)evaluateExpressionCocoa:(in bycopy NSString *)expr 
+                  errorString:(out bycopy NSString **)errstr
+{
+    return evalExprCocoa(expr, errstr);
+}
+
+
 - (NSString *)evaluateExpression:(in bycopy NSString *)expr
 {
     NSString *eval = nil;
@@ -2436,3 +2444,141 @@ static int eventButtonNumberToVimMouseButton(int buttonNumber)
     return (buttonNumber >= 0 && buttonNumber < 3)
             ? mouseButton[buttonNumber] : -1;
 }
+
+// This function is modeled after the VimToPython function found in if_python.c
+// NB This does a deep copy by value, it does not lookup references like the
+// VimToPython function does.  This is because I didn't want to deal with the
+// retain cycles that this would create, and we can cover 99% of the use cases
+// by ignoring it.  If we ever switch to using GC in MacVim then this
+// functionality can be implemented easily.
+static id vimToCocoa(typval_T * tv, int depth)
+{
+    id result = nil;
+    id newObj = nil;
+
+
+    // Avoid infinite recursion
+    if (depth > 100) {
+        return nil;
+    }
+
+    if (tv->v_type == VAR_STRING) {
+        char_u * val = tv->vval.v_string;
+        // val can be NULL if the string is empty
+        if (!val) {
+            result = [NSString string];
+        } else {
+#ifdef FEAT_MBYTE
+            val = CONVERT_TO_UTF8(val);
+#endif
+            result = [NSString stringWithUTF8String:(char*)val];
+#ifdef FEAT_MBYTE
+            CONVERT_TO_UTF8_FREE(val);
+#endif
+        }
+    } else if (tv->v_type == VAR_NUMBER) {
+        // looks like sizeof(varnumber_T) is always <= sizeof(long)
+        result = [NSNumber numberWithLong:(long)tv->vval.v_number];
+    } else if (tv->v_type == VAR_LIST) {
+        list_T * list = tv->vval.v_list;
+        listitem_T * curr;
+
+        NSMutableArray * arr = result = [NSMutableArray array];
+
+        if (list != NULL) {
+            for (curr = list->lv_first; curr != NULL; curr = curr->li_next) {
+                newObj = vimToCocoa(&curr->li_tv, depth + 1);
+                [arr addObject:newObj];
+            }
+        }
+    } else if (tv->v_type == VAR_DICT) {
+        NSMutableDictionary * dict = result = [NSMutableDictionary dictionary];
+
+        if (tv->vval.v_dict != NULL) {
+            hashtab_T * ht = &tv->vval.v_dict->dv_hashtab;
+            int todo = ht->ht_used;
+            hashitem_T * hi;
+            dictitem_T * di;
+
+            for (hi = ht->ht_array; todo > 0; ++hi) {
+                if (!HASHITEM_EMPTY(hi)) {
+                    --todo;
+
+                    di = dict_lookup(hi);
+                    newObj = vimToCocoa(&di->di_tv, depth + 1);
+
+                    char_u * keyval = hi->hi_key;
+#ifdef FEAT_MBYTE
+                    keyval = CONVERT_TO_UTF8(keyval);
+#endif
+                    NSString * key = [NSString stringWithUTF8String:(char*)keyval];
+#ifdef FEAT_MBYTE
+                    CONVERT_TO_UTF8_FREE(keyval);
+#endif
+                    [dict setObject:newObj forKey:key];
+                }
+            }
+        }
+    } else { // only func refs should fall into this category?
+        result = nil;
+    }
+
+    return result;
+}
+
+
+// This function is modeled after eval_client_expr_to_string found in main.c
+// Returns nil if there was an error evaluating the expression, and writes a
+// message to errorStr.
+// TODO Get the error that occurred while evaluating the expression in vim
+// somehow.
+static id evalExprCocoa(NSString * expr, NSString ** errstr)
+{
+
+    char_u *s = (char_u*)[expr UTF8String];
+
+#ifdef FEAT_MBYTE
+    s = CONVERT_FROM_UTF8(s);
+#endif
+
+    int save_dbl = debug_break_level;
+    int save_ro = redir_off;
+
+    debug_break_level = -1;
+    redir_off = 0;
+    ++emsg_skip;
+
+    typval_T * tvres = eval_expr(s, NULL);
+
+    debug_break_level = save_dbl;
+    redir_off = save_ro;
+    --emsg_skip;
+
+    setcursor();
+    out_flush();
+
+#ifdef FEAT_MBYTE
+    CONVERT_FROM_UTF8_FREE(s);
+#endif
+
+#ifdef FEAT_GUI
+    if (gui.in_use)
+        gui_update_cursor(FALSE, FALSE);
+#endif
+
+    if (tvres == NULL) {
+        free_tv(tvres);
+        *errstr = @"Expression evaluation failed.";
+    }
+
+    id res = vimToCocoa(tvres, 1);
+
+    free_tv(tvres);
+
+    if (res == nil) {
+        *errstr = @"Conversion to cocoa values failed.";
+    }
+
+    return res;
+    
+}
diff --git a/src/MacVim/MMPlugInManager.h b/src/MacVim/MMPlugInManager.h
new file mode 100644
index 0000000..0300855
--- /dev/null
+++ b/src/MacVim/MMPlugInManager.h
@@ -0,0 +1,31 @@
+/* vi:set ts=8 sts=4 sw=4 ft=objc:
+ *
+ * VIM - Vi IMproved		by Bram Moolenaar
+ *				MacVim GUI port by Bjorn Winckler
+ *
+ * Do ":help uganda"  in Vim to read copying and usage conditions.
+ * Do ":help credits" in Vim to see a list of people who contributed.
+ * See README.txt for an overview of the Vim source code.
+ */
+
+#import <Cocoa/Cocoa.h>
+#import "MMVimController.h"
+
+
+@interface MMPlugInManager : NSObject {
+
+    NSMutableArray *plugInClasses;
+}
+
+- (NSArray *)plugInClasses;
+
+// Find and load any plugins
+- (void)loadAllPlugIns;
+
+// release instances of loaded plugins
+- (void)unloadAllPlugIns;
+
+// Return singleton instance of MMPluginManager
++ (MMPlugInManager *)sharedManager;
+
+@end
diff --git a/src/MacVim/MMPlugInManager.m b/src/MacVim/MMPlugInManager.m
new file mode 100644
index 0000000..93a4b98
--- /dev/null
+++ b/src/MacVim/MMPlugInManager.m
@@ -0,0 +1,128 @@
+/* vi:set ts=8 sts=4 sw=4 ft=objc:
+ *
+ * VIM - Vi IMproved		by Bram Moolenaar
+ *				MacVim GUI port by Bjorn Winckler
+ *
+ * Do ":help uganda"  in Vim to read copying and usage conditions.
+ * Do ":help credits" in Vim to see a list of people who contributed.
+ * See README.txt for an overview of the Vim source code.
+ */
+
+#import "MMPlugInManager.h"
+#import "PlugInInterface.h"
+#import "PlugInImpl.h"
+
+@implementation MMPlugInManager
+
+static NSString *ext = @"mmplugin";
+
+static NSString *appSupportSubpath = @"Application Support/MacVim/PlugIns";
+ 
+static MMPlugInManager *plugInManager = nil;
+
++ (MMPlugInManager*)sharedManager
+{
+    if (!plugInManager)
+        plugInManager = [[MMPlugInManager alloc] init];
+
+    return plugInManager;
+}
+
+- (id)init
+{
+    if ((self = [super init]) == nil) return nil;
+    plugInClasses = [[NSMutableArray alloc] init];
+    return self;
+}
+
+- (NSArray *)plugInClasses;
+{
+    return plugInClasses;
+}
+
+- (NSMutableArray *)allBundles
+{
+    NSArray *librarySearchPaths;
+    NSEnumerator *searchPathEnum;
+    NSString *currPath;
+    NSMutableArray *bundleSearchPaths = [NSMutableArray array];
+    NSMutableArray *allBundles = [NSMutableArray array];
+ 
+    librarySearchPaths = NSSearchPathForDirectoriesInDomains(
+        NSLibraryDirectory, NSAllDomainsMask - NSSystemDomainMask, YES);
+ 
+    searchPathEnum = [librarySearchPaths objectEnumerator];
+    while(currPath = [searchPathEnum nextObject]) {
+        [bundleSearchPaths addObject:
+            [currPath stringByAppendingPathComponent:appSupportSubpath]];
+    }
+
+    [bundleSearchPaths addObject:
+        [[NSBundle mainBundle] builtInPlugInsPath]];
+ 
+    searchPathEnum = [bundleSearchPaths objectEnumerator];
+    while(currPath = [searchPathEnum nextObject]) {
+        NSDirectoryEnumerator *bundleEnum;
+        NSString *currBundlePath;
+        bundleEnum = [[NSFileManager defaultManager]
+            enumeratorAtPath:currPath];
+        if(bundleEnum) {
+            while(currBundlePath = [bundleEnum nextObject]) {
+                if([[currBundlePath pathExtension] isEqualToString:ext]) {
+                 [allBundles addObject:[currPath
+                           stringByAppendingPathComponent:currBundlePath]];
+                }
+            }
+        }
+    }
+ 
+    return allBundles;
+}
+
+- (BOOL)plugInClassIsValid:(Class)class
+{
+    return [class conformsToProtocol:@protocol(PlugInProtocol)];
+}
+
+- (void)loadAllPlugIns
+{
+    NSMutableArray *bundlePaths;
+    NSEnumerator *pathEnum;
+    NSString *currPath;
+    NSBundle *currBundle;
+    Class currPrincipalClass;
+ 
+    bundlePaths = [NSMutableArray array];
+ 
+    [bundlePaths addObjectsFromArray:[self allBundles]];
+ 
+    pathEnum = [bundlePaths objectEnumerator];
+    while(currPath = [pathEnum nextObject]) {
+        currBundle = [NSBundle bundleWithPath:currPath];
+        if(currBundle) {
+            currPrincipalClass = [currBundle principalClass];
+            if(currPrincipalClass && [self plugInClassIsValid:currPrincipalClass]) {
+                if ([currPrincipalClass initializePlugIn:
+                    [MMPlugInAppMediator sharedAppMediator]]) {
+                    NSLog(@"Plug-in initialized: %@", currPath);
+                    [plugInClasses addObject:currPrincipalClass];
+                } else {
+                    NSLog(@"Plug-in failed to initialize: %@", currPath);
+                }
+            } else {
+                NSLog(@"Plug-in did not conform to protocol: %@", currPath);
+            }
+        }
+    }
+}
+ 
+- (void)unloadAllPlugIns
+{
+    int i, count = [plugInClasses count];
+    for (i = 0; i < count; i++)
+        [[plugInClasses objectAtIndex:i] terminatePlugIn];
+
+    [plugInClasses removeAllObjects];
+}
+
+@end
diff --git a/src/MacVim/MMVimController.h b/src/MacVim/MMVimController.h
index 87e3845..23814be 100644
--- a/src/MacVim/MMVimController.h
+++ b/src/MacVim/MMVimController.h
@@ -10,6 +10,7 @@
 
 #import <Cocoa/Cocoa.h>
 #import "MacVim.h"
+#import "PlugInImpl.h"
 
 
 @class MMWindowController;
@@ -31,6 +32,7 @@
     int                 pid;
     NSString            *serverName;
     NSDictionary        *vimState;
+    MMPlugInInstanceMediator *instanceMediator;
 }
 
 - (id)initWithBackend:(id)backend pid:(int)processIdentifier;
@@ -51,4 +53,6 @@
                timeout:(NSTimeInterval)timeout;
 - (void)addVimInput:(NSString *)string;
 - (NSString *)evaluateVimExpression:(NSString *)expr;
+- (id)evaluateVimExpressionCocoa:(NSString *)expr errorString:(NSString **)errstr;
+- (MMPlugInInstanceMediator *)instanceMediator;
 @end
diff --git a/src/MacVim/MMVimController.m b/src/MacVim/MMVimController.m
index 6689a92..e3a1485 100644
--- a/src/MacVim/MMVimController.m
+++ b/src/MacVim/MMVimController.m
@@ -31,6 +31,7 @@
 #import "MMWindowController.h"
 #import "Miscellaneous.h"
 
+#import "MMPlugInManager.h"
 
 static NSString *MMDefaultToolbarImageName = @"Attention";
 static int MMAlertTextFieldHeight = 22;
@@ -139,6 +140,8 @@ static BOOL isUnsafeMessage(int msgid);
         [mainMenu addItem:appMenuItem];
 
         isInitialized = YES;
+        
+        instanceMediator = [[MMPlugInInstanceMediator alloc] initWithVimController:self];
     }
 
     return self;
@@ -147,6 +150,9 @@ static BOOL isUnsafeMessage(int msgid);
 - (void)dealloc
 {
     //NSLog(@"%@ %s", [self className], _cmd);
+
+    [instanceMediator release]; instanceMediator = nil;
+
     isInitialized = NO;
 
     [serverName release];  serverName = nil;
@@ -170,6 +176,11 @@ static BOOL isUnsafeMessage(int msgid);
     return windowController;
 }
 
+- (MMPlugInInstanceMediator *)instanceMediator
+{
+    return instanceMediator;
+}
+
 - (NSDictionary *)vimState
 {
     return vimState;
@@ -367,6 +378,20 @@ static BOOL isUnsafeMessage(int msgid);
     return eval;
 }
 
+- (id)evaluateVimExpressionCocoa:(NSString *)expr errorString:(NSString **)errstr
+{
+    id eval = nil;
+
+    @try {
+        eval = [backendProxy evaluateExpressionCocoa:expr
+                                         errorString:errstr];
+    } @catch (NSException *ex) { 
+        *errstr = [ex reason];
+    }
+
+    return eval;
+}
+
 - (id)backendProxy
 {
     return backendProxy;
diff --git a/src/MacVim/MacVim.h b/src/MacVim/MacVim.h
index 5e7bc8b..6532919 100644
--- a/src/MacVim/MacVim.h
+++ b/src/MacVim/MacVim.h
@@ -31,10 +31,11 @@
 - (oneway void)processInputAndData:(in bycopy NSArray *)messages;
 - (oneway void)setDialogReturn:(in bycopy id)obj;
 - (NSString *)evaluateExpression:(in bycopy NSString *)expr;
+- (id)evaluateExpressionCocoa:(in bycopy NSString *)expr 
+                  errorString:(out bycopy NSString **)errstr;
 - (BOOL)starRegisterToPasteboard:(byref NSPasteboard *)pboard;
 @end
 
-
 //
 // This is the protocol MMVimController implements.
 //
diff --git a/src/MacVim/MacVim.xcodeproj/project.pbxproj b/src/MacVim/MacVim.xcodeproj/project.pbxproj
index 9dfc264..9b9e3da 100644
--- a/src/MacVim/MacVim.xcodeproj/project.pbxproj
+++ b/src/MacVim/MacVim.xcodeproj/project.pbxproj
@@ -85,6 +85,13 @@
 		8D11072B0486CEB800E47090 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 089C165CFE840E0CC02AAC07 /* InfoPlist.strings */; };
 		8D11072D0486CEB800E47090 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 29B97316FDCFA39411CA2CEA /* main.m */; settings = {ATTRIBUTES = (); }; };
 		8D11072F0486CEB800E47090 /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1058C7A1FEA54F0111CA2CBB /* Cocoa.framework */; };
+		BD476E300DAAD74400F08A5C /* RBSplitSubview.m in Sources */ = {isa = PBXBuildFile; fileRef = BD476E2C0DAAD74400F08A5C /* RBSplitSubview.m */; };
+		BD476E310DAAD74400F08A5C /* RBSplitView.m in Sources */ = {isa = PBXBuildFile; fileRef = BD476E2E0DAAD74400F08A5C /* RBSplitView.m */; };
+		BD943D530DA3050B00A02D9B /* PlugInImpl.m in Sources */ = {isa = PBXBuildFile; fileRef = BD943D520DA3050B00A02D9B /* PlugInImpl.m */; };
+		BD9DF0A00DB2BA020025C97C /* PlugInView.nib in Resources */ = {isa = PBXBuildFile; fileRef = BD9DF09F0DB2BA020025C97C /* PlugInView.nib */; };
+		BD9DF0B00DB41E780025C97C /* PlugInGUI.m in Sources */ = {isa = PBXBuildFile; fileRef = BD9DF0AF0DB41E780025C97C /* PlugInGUI.m */; };
+		BD9DF0FB0DB48C860025C97C /* CTGradient.m in Sources */ = {isa = PBXBuildFile; fileRef = BD9DF0FA0DB48C860025C97C /* CTGradient.m */; };
+		BDA8B1120D9F8A8500B3511A /* MMPlugInManager.m in Sources */ = {isa = PBXBuildFile; fileRef = BDA8B1110D9F8A8500B3511A /* MMPlugInManager.m */; };
 /* End PBXBuildFile section */
 
 /* Begin PBXContainerItemProxy section */
@@ -264,6 +271,21 @@
 		32CA4F630368D1EE00C91783 /* MacVim_Prefix.pch */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MacVim_Prefix.pch; sourceTree = "<group>"; };
 		8D1107310486CEB800E47090 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist; path = Info.plist; sourceTree = "<group>"; };
 		8D1107320486CEB800E47090 /* MacVim.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = MacVim.app; sourceTree = BUILT_PRODUCTS_DIR; };
+		BD476E2B0DAAD74400F08A5C /* RBSplitSubview.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RBSplitSubview.h; sourceTree = "<group>"; };
+		BD476E2C0DAAD74400F08A5C /* RBSplitSubview.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RBSplitSubview.m; sourceTree = "<group>"; };
+		BD476E2D0DAAD74400F08A5C /* RBSplitView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RBSplitView.h; sourceTree = "<group>"; };
+		BD476E2E0DAAD74400F08A5C /* RBSplitView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RBSplitView.m; sourceTree = "<group>"; };
+		BD476E2F0DAAD74400F08A5C /* RBSplitViewPrivateDefines.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RBSplitViewPrivateDefines.h; sourceTree = "<group>"; };
+		BD943D310DA2EA2500A02D9B /* PlugInInterface.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PlugInInterface.h; sourceTree = "<group>"; };
+		BD943D510DA3050B00A02D9B /* PlugInImpl.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PlugInImpl.h; sourceTree = "<group>"; };
+		BD943D520DA3050B00A02D9B /* PlugInImpl.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PlugInImpl.m; sourceTree = "<group>"; };
+		BD9DF09F0DB2BA020025C97C /* PlugInView.nib */ = {isa = PBXFileReference; lastKnownFileType = wrapper.nib; path = PlugInView.nib; sourceTree = "<group>"; };
+		BD9DF0AE0DB41E780025C97C /* PlugInGUI.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PlugInGUI.h; sourceTree = "<group>"; };
+		BD9DF0AF0DB41E780025C97C /* PlugInGUI.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PlugInGUI.m; sourceTree = "<group>"; };
+		BD9DF0F90DB48C860025C97C /* CTGradient.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CTGradient.h; sourceTree = "<group>"; };
+		BD9DF0FA0DB48C860025C97C /* CTGradient.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CTGradient.m; sourceTree = "<group>"; };
+		BDA8B1100D9F8A8500B3511A /* MMPlugInManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MMPlugInManager.h; sourceTree = "<group>"; };
+		BDA8B1110D9F8A8500B3511A /* MMPlugInManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MMPlugInManager.m; sourceTree = "<group>"; };
 /* End PBXFileReference section */
 
 /* Begin PBXFrameworksBuildPhase section */
@@ -304,6 +326,13 @@
 			children = (
 				1D8059220E118663001699D1 /* Miscellaneous.h */,
 				1D80591D0E1185EA001699D1 /* Miscellaneous.m */,
+				BD9DF0F90DB48C860025C97C /* CTGradient.h */,
+				BD9DF0FA0DB48C860025C97C /* CTGradient.m */,
+				BD476E2B0DAAD74400F08A5C /* RBSplitSubview.h */,
+				BD476E2C0DAAD74400F08A5C /* RBSplitSubview.m */,
+				BD476E2D0DAAD74400F08A5C /* RBSplitView.h */,
+				BD476E2E0DAAD74400F08A5C /* RBSplitView.m */,
+				BD476E2F0DAAD74400F08A5C /* RBSplitViewPrivateDefines.h */,
 				0395A9BD0D75D02400881434 /* AuthorizedShellCommand.h */,
 				0395A9BE0D75D02400881434 /* AuthorizedShellCommand.m */,
 				0395A8310D71ED7800881434 /* DBPrefsWindowController.h */,
@@ -336,6 +365,13 @@
 				1D1474960C56703C0038FA2B /* MacVim.m */,
 				32CA4F630368D1EE00C91783 /* MacVim_Prefix.pch */,
 				29B97316FDCFA39411CA2CEA /* main.m */,
+				BDA8B1100D9F8A8500B3511A /* MMPlugInManager.h */,
+				BDA8B1110D9F8A8500B3511A /* MMPlugInManager.m */,
+				BD943D310DA2EA2500A02D9B /* PlugInInterface.h */,
+				BD943D510DA3050B00A02D9B /* PlugInImpl.h */,
+				BD943D520DA3050B00A02D9B /* PlugInImpl.m */,
+				BD9DF0AE0DB41E780025C97C /* PlugInGUI.h */,
+				BD9DF0AF0DB41E780025C97C /* PlugInGUI.m */,
 			);
 			name = "MacVim Source";
 			sourceTree = "<group>";
@@ -470,6 +506,7 @@
 		29B97317FDCFA39411CA2CEA /* Resources */ = {
 			isa = PBXGroup;
 			children = (
+				BD9DF09F0DB2BA020025C97C /* PlugInView.nib */,
 				1DD3D51D0D82D4C9006E4320 /* ibeam.png */,
 				1D0F11480D58C77800D5DA09 /* Font */,
 				1DCE78490C460C6C006305A6 /* Icons */,
@@ -623,6 +660,7 @@
 				0395A8AA0D72D88B00881434 /* General.png in Resources */,
 				0395A95A0D74D47B00881434 /* Integration.png in Resources */,
 				1DD3D51E0D82D4C9006E4320 /* ibeam.png in Resources */,
+				BD9DF0A00DB2BA020025C97C /* PlugInView.nib in Resources */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 		};
@@ -650,6 +688,12 @@
 				0395A8330D71ED7800881434 /* DBPrefsWindowController.m in Sources */,
 				0395A9BF0D75D02400881434 /* AuthorizedShellCommand.m in Sources */,
 				1D80591F0E1185EA001699D1 /* Miscellaneous.m in Sources */,
+				BDA8B1120D9F8A8500B3511A /* MMPlugInManager.m in Sources */,
+				BD943D530DA3050B00A02D9B /* PlugInImpl.m in Sources */,
+				BD476E300DAAD74400F08A5C /* RBSplitSubview.m in Sources */,
+				BD476E310DAAD74400F08A5C /* RBSplitView.m in Sources */,
+				BD9DF0B00DB41E780025C97C /* PlugInGUI.m in Sources */,
+				BD9DF0FB0DB48C860025C97C /* CTGradient.m in Sources */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 		};
diff --git a/src/MacVim/PlugInGUI.h b/src/MacVim/PlugInGUI.h
new file mode 100644
index 0000000..445f814
--- /dev/null
+++ b/src/MacVim/PlugInGUI.h
@@ -0,0 +1,57 @@
+/* vi:set ts=8 sts=4 sw=4 ft=objc:
+ *
+ * VIM - Vi IMproved		by Bram Moolenaar
+ *				MacVim GUI port by Bjorn Winckler
+ *
+ * Do ":help uganda"  in Vim to read copying and usage conditions.
+ * Do ":help credits" in Vim to see a list of people who contributed.
+ * See README.txt for an overview of the Vim source code.
+ */
+
+#import <Cocoa/Cocoa.h>
+#import "RBSplitView.h"
+
+
+@class MMPlugInView;
+@class MMPlugInViewContainer;
+@class MMPlugInViewController;
+
+@interface MMPlugInViewHeader : NSView {
+    IBOutlet MMPlugInViewController *controller;
+}
+@end
+
+@interface MMPlugInView : RBSplitSubview {
+    IBOutlet MMPlugInViewController *controller;
+}
+
+- (MMPlugInViewController *)controller;
+
+@end
+
+@interface MMPlugInViewController : NSObject {
+    IBOutlet RBSplitSubview *plugInSubview;
+    IBOutlet MMPlugInViewHeader *headerView;
+    IBOutlet NSView *contentView;
+    IBOutlet NSTextField *titleField;
+}
+
+- (id)initWithView:(NSView *)view title:(NSString *)title;
+- (void)moveToContainer:(MMPlugInViewContainer *)container;
+- (RBSplitSubview *)plugInSubview;
+- (MMPlugInViewContainer *)container;
+
+// called when the dropView on the container holding this plugin view was
+// changed, and this was the current dropView or it is the new dropView
+- (void)dropViewChanged;
+@end
+
+@interface MMPlugInViewContainer : RBSplitView {
+    RBSplitSubview *fillerView;
+
+    // only used during drag and drop
+    MMPlugInView *dropView;
+}
+
+- (MMPlugInView *)dropView;
+@end
diff --git a/src/MacVim/PlugInGUI.m b/src/MacVim/PlugInGUI.m
new file mode 100644
index 0000000..d296fcd
--- /dev/null
+++ b/src/MacVim/PlugInGUI.m
@@ -0,0 +1,307 @@
+/* vi:set ts=8 sts=4 sw=4 ft=objc:
+ *
+ * VIM - Vi IMproved		by Bram Moolenaar
+ *				MacVim GUI port by Bjorn Winckler
+ *
+ * Do ":help uganda"  in Vim to read copying and usage conditions.
+ * Do ":help credits" in Vim to see a list of people who contributed.
+ * See README.txt for an overview of the Vim source code.
+ */
+
+#import "PlugInGUI.h"
+#import "CTGradient.h"
+
+NSString *MMPlugInViewPboardType = @"MMPlugInViewPboardType";
+
+@implementation MMPlugInViewHeader 
+
+- (void)mouseDown:(NSEvent *)theEvent
+{
+    // Make image from view
+    NSView *view = self;
+    [view lockFocus];
+    NSBitmapImageRep *bitmap = [[[NSBitmapImageRep alloc]
+        initWithFocusedViewRect: [view bounds]] autorelease];
+    [view unlockFocus];
+     
+    NSImage *image = [[[NSImage alloc] initWithSize: [view bounds].size] autorelease];
+    [image addRepresentation:bitmap];
+
+    NSPasteboard *pboard = [NSPasteboard pasteboardWithName:NSDragPboard];
+
+    [pboard declareTypes:[NSArray arrayWithObject:MMPlugInViewPboardType]  owner:self];
+ 
+    [[controller plugInSubview] dragImage:image 
+                                       at:[view convertPoint:[view bounds].origin 
+                                   toView:[controller plugInSubview]]
+                                   offset:NSMakeSize(0, 0) 
+                                    event:theEvent 
+                               pasteboard:pboard 
+                                   source:controller 
+                                slideBack:YES];
+}
+
+- (void)drawRect:(NSRect)rect
+{
+    NSColor *startColor;
+    startColor = [NSColor colorWithCalibratedRed:.600
+                                           green:.600
+                                            blue:.600
+                                           alpha:1.0];
+
+    NSColor *endColor = [NSColor colorWithCalibratedRed:.800
+                                                  green:.800
+                                                   blue:.800
+                                                  alpha:1.0];
+
+    CTGradient *grad = [CTGradient gradientWithBeginningColor:startColor
+                                                  endingColor:endColor];
+    [grad fillRect:[self bounds] angle:90];
+
+    MMPlugInView *dropView = [[controller container] dropView];
+
+    if (dropView == [controller plugInSubview]) {
+        NSRect insertionRect = NSMakeRect(0,[self bounds].size.height - 2,
+                [self bounds].size.width, 2);
+        [[NSColor redColor] set];
+        NSRectFill(insertionRect);
+    }
+}
+
+- (BOOL)isOpaque
+{
+    return YES;
+}
+
+- (NSRect)dragRect
+{
+    return NSMakeRect(0, [self bounds].size.height - 6, [self bounds].size.width, 6);
+}
+
+@end
+
+@implementation MMPlugInView
+
+- (MMPlugInViewController *)controller 
+{
+    return controller;
+}
+
+@end
+
+@implementation MMPlugInViewController
+
+- (id)initWithView:(NSView *)view title:(NSString *)title
+{
+    if ((self = [super init]) == nil) return nil;
+
+    if (![NSBundle loadNibNamed:@"PlugInView" owner:self])
+        NSLog(@"Error loading PlugIn nib");
+
+    [titleField setStringValue:title];
+
+    [plugInSubview setMinDimension:50
+                   andMaxDimension:0.0];
+
+    [view setFrame:[contentView bounds]];
+    [contentView addSubview:view];
+
+    return self;
+}
+
+- (RBSplitSubview *)plugInSubview
+{
+    return plugInSubview;
+}
+
+- (void)moveToContainer:(MMPlugInViewContainer *)container
+{
+    if ([plugInSubview splitView]) {
+        [plugInSubview removeFromSuperview];
+    }
+    [container addSubview:plugInSubview];
+}
+
+- (void)moveToContainer:(MMPlugInViewContainer *)container before:(MMPlugInView *)lowerView
+{
+    if ([plugInSubview splitView]) {
+        [plugInSubview removeFromSuperview];
+    }
+    [container addSubview:plugInSubview positioned:NSWindowBelow relativeTo:lowerView];
+}
+
+- (MMPlugInViewHeader *)headerView 
+{
+    return headerView;
+}
+
+- (NSDragOperation)draggingSourceOperationMaskForLocal:(BOOL)isLocal
+{
+    if (isLocal)
+        return NSDragOperationPrivate;
+    else
+        return NSDragOperationNone;
+}
+
+- (void)dropViewChanged {
+    [headerView setNeedsDisplay:YES];
+}
+
+- (MMPlugInViewContainer *)container {
+    return (MMPlugInViewContainer *)[plugInSubview splitView];
+}
+@end
+
+@implementation MMPlugInViewContainer
+
+- (id)initWithFrame:(NSRect)frame
+{
+    if ((self = [super initWithFrame:frame]) == nil) return nil;
+
+    [self registerForDraggedTypes:[NSArray arrayWithObjects:MMPlugInViewPboardType, nil]];
+ 
+    [self setVertical:NO];
+    [self setDelegate:self];
+
+    fillerView = [[RBSplitSubview alloc] initWithFrame:NSMakeRect(0,0,0,0)];
+    [fillerView setHidden:YES];
+
+    return self;
+}
+
+- (void)dealloc
+{
+    [fillerView release]; fillerView = nil;
+    [super dealloc];
+}
+
+- (unsigned int)splitView:(RBSplitView*)sender dividerForPoint:(NSPoint)point
+                inSubview:(RBSplitSubview*)subview
+{
+    MMPlugInViewController *controller = [(MMPlugInView *)subview controller]; 
+    MMPlugInViewHeader *header = [controller headerView];
+
+    if ([header mouse:[header convertPoint:point fromView:sender]
+               inRect:[header dragRect]])
+        return [subview position] - 1;
+
+    return NSNotFound;
+}
+
+- (NSRect)splitView:(RBSplitView*)sender cursorRect:(NSRect)rect 
+         forDivider:(unsigned int)theDivider
+{
+
+    if (theDivider != 0) return NSZeroRect;
+
+    int i;
+    for (i = 1;; i++) {
+        MMPlugInView *view = (MMPlugInView *)[sender subviewAtPosition:i];
+        if (!view) break;
+
+        MMPlugInViewHeader *header = [[view controller] headerView];
+        NSRect rect = [header dragRect];
+        rect = [sender convertRect:rect fromView:header];
+        [sender addCursorRect:rect
+                       cursor:[RBSplitView cursor:RBSVHorizontalCursor]];
+
+    }
+
+    return NSZeroRect;
+}
+
+- (void)clearDragInfo
+{
+    if (dropView) {
+        MMPlugInView *save = dropView;
+        dropView = nil;
+        [[save controller] dropViewChanged];
+    }
+}    
+
+// point should be in the window's coordinate system
+- (void)updateDragInfo:(id<NSDraggingInfo>)info
+{
+
+    [self clearDragInfo];
+
+    if (!([info draggingSourceOperationMask] & NSDragOperationPrivate)) return;
+
+    if (![[info draggingSource] isKindOfClass:[MMPlugInViewController class]]) return; 
+
+    // for now has to be THIS container.  in the future, it will be ok for any
+    // container associated with the same vim instance
+    if ([[info draggingSource] container] != self) return;
+
+    // XXX for now we just use the view that the mouse is currently over, and
+    // always insert "above" that view.  In the future, we might want to try to
+    // find the divider that the mouse is closest to and have the dropView be
+    // the view below that divider.
+    
+    NSPoint point = [info draggingLocation];
+
+    int i;
+    for (i = 0;; i++) {
+        MMPlugInView *subview = (MMPlugInView *)[self subviewAtPosition:i];
+        if (!subview) break;
+
+        if ([subview mouse:[subview convertPoint:point fromView:nil]
+                    inRect:[subview bounds]]) {
+            dropView = subview;
+            break;
+        }
+    }
+
+    if ([[info draggingSource] plugInSubview] == dropView)
+        dropView = nil;
+
+    if (dropView) [[dropView controller] dropViewChanged];
+}
+
+- (NSDragOperation)draggingEntered:(id<NSDraggingInfo>)sender
+{
+    [self updateDragInfo:sender];
+
+    if (dropView != nil)
+        return NSDragOperationPrivate;
+    else
+        return NSDragOperationNone;
+}
+
+- (NSDragOperation)draggingUpdated:(id<NSDraggingInfo>)sender
+{
+    [self updateDragInfo:sender];
+
+    if (dropView != nil)
+        return NSDragOperationPrivate;
+
+    return NSDragOperationNone;
+}
+
+- (BOOL)prepareForDragOperation:(id<NSDraggingInfo>)sender
+{
+    [self updateDragInfo:sender];
+    return dropView != nil;
+}
+
+- (BOOL)performDragOperation:(id<NSDraggingInfo>)sender
+{
+    MMPlugInViewController *source = [sender draggingSource];
+    [source moveToContainer:self before:dropView];
+    [self clearDragInfo];
+    return YES;
+}
+
+- (void)draggingExited:(id<NSDraggingInfo>)sender
+{
+    [self clearDragInfo];
+}
+
+
+- (MMPlugInView *)dropView {
+    return dropView;
+}
+
+@end
+
+
diff --git a/src/MacVim/PlugInImpl.h b/src/MacVim/PlugInImpl.h
new file mode 100644
index 0000000..6ab4dae
--- /dev/null
+++ b/src/MacVim/PlugInImpl.h
@@ -0,0 +1,37 @@
+/* vi:set ts=8 sts=4 sw=4 ft=objc:
+ *
+ * VIM - Vi IMproved		by Bram Moolenaar
+ *				MacVim GUI port by Bjorn Winckler
+ *
+ * Do ":help uganda"  in Vim to read copying and usage conditions.
+ * Do ":help credits" in Vim to see a list of people who contributed.
+ * See README.txt for an overview of the Vim source code.
+ */
+
+#import <Cocoa/Cocoa.h>
+#import "PlugInInterface.h"
+
+@interface MMPlugInAppMediator : NSObject <PlugInAppMediator> {
+    NSMenu *plugInMenu;
+}
+
++ (MMPlugInAppMediator *)sharedAppMediator;
+
+@end
+
+
+@class MMVimController;
+
+// One of these per vim controller object.  It manages all of the plugin
+// instances for a given controller.
+@interface MMPlugInInstanceMediator : NSObject <PlugInInstanceMediator> {
+    // NB: this is a weak reference to the vim controller
+    MMVimController *vimController;
+    NSMutableArray *instances;
+    NSDrawer *drawer;
+    NSMutableArray *plugInViews ;
+}
+
+- (id)initWithVimController:(MMVimController *)controller;
+
+@end
diff --git a/src/MacVim/PlugInImpl.m b/src/MacVim/PlugInImpl.m
new file mode 100644
index 0000000..2f3bee4
--- /dev/null
+++ b/src/MacVim/PlugInImpl.m
@@ -0,0 +1,166 @@
+/* vi:set ts=8 sts=4 sw=4 ft=objc:
+ *
+ * VIM - Vi IMproved		by Bram Moolenaar
+ *				MacVim GUI port by Bjorn Winckler
+ *
+ * Do ":help uganda"  in Vim to read copying and usage conditions.
+ * Do ":help credits" in Vim to see a list of people who contributed.
+ * See README.txt for an overview of the Vim source code.
+ */
+
+#import "PlugInImpl.h"
+#import "PlugInGUI.h"
+#import "MMPlugInManager.h"
+#import "RBSplitView.h"
+#import "MMAppController.h"
+#import "MMVimController.h"
+
+
+@implementation MMPlugInInstanceMediator
+
+- (void)setupDrawer
+{
+    // XXX The drawer does not work in full screen mode.  Eventually, the
+    // drawer will go away so I'm ignoring this issue for now.
+    drawer = [[NSDrawer alloc] initWithContentSize:NSMakeSize(200,100)
+                                     preferredEdge:NSMinXEdge];
+
+
+    NSSize contentSize = [drawer contentSize];
+
+    // XXX memory management for this
+    MMPlugInViewContainer * containerView = [[MMPlugInViewContainer alloc] 
+        initWithFrame:NSMakeRect(0, 0, contentSize.width, contentSize.height)];
+
+    [drawer setContentView:containerView];
+
+    [containerView release];
+
+    [drawer setParentWindow:[[vimController windowController] window]];
+
+}
+
+- (void)initializeInstances
+{
+    NSArray *plugInClasses = [[MMPlugInManager sharedManager] plugInClasses];
+    int i, count = [plugInClasses count];
+    for (i = 0; i < count; i++) {
+        Class plugInClass = [plugInClasses objectAtIndex:i];
+        if ([plugInClass instancesRespondToSelector:
+                         @selector(initWithMediator:)]) {
+            id instance = [[[plugInClass alloc] initWithMediator:self] autorelease];
+            [instances addObject:instance];
+        }
+    }
+}
+
+- (id)initWithVimController:(MMVimController *)controller
+{
+    if ((self = [super init]) == nil) return nil;
+    vimController = controller;
+    instances = [[NSMutableArray alloc] init];
+    plugInViews = [[NSMutableArray alloc] init];
+
+    [self setupDrawer];
+    [self initializeInstances];
+
+    return self;
+}
+
+- (void)dealloc
+{
+    //NSLog(@"%@ %s", [self className], _cmd);
+
+    [plugInViews release]; plugInViews = nil;
+    [instances release]; instances = nil;
+    [drawer release]; drawer = nil;
+    vimController = nil;
+
+    [super dealloc];
+}
+
+- (id)evaluateVimExpression:(NSString *)vimExpression
+{
+    NSString *errstr = nil;
+    id res = [vimController evaluateVimExpressionCocoa:vimExpression
+                                           errorString:&errstr];
+    if (!res)
+        [NSException raise:@"VimEvaluationException" format:errstr];
+
+    return res;
+}
+
+- (void)addVimInput:(NSString *)input
+{
+    [vimController addVimInput:input];
+}
+
+- (void)addPlugInView:(NSView *)view withTitle:(NSString *)title
+{
+    //NSLog(@"%@ %s", [self className], _cmd);
+
+    // For now, just always open the drawer when adding a plugin view
+    [drawer open];
+
+    MMPlugInViewController *newView = 
+        [[MMPlugInViewController alloc] initWithView:view title:title];
+
+    [plugInViews addObject:newView];
+
+    [newView moveToContainer:(MMPlugInViewContainer *)[drawer contentView]];
+
+    [newView release];
+}
+
+- (void)openFiles:(NSArray *)filenames
+{
+    [vimController dropFiles:filenames forceOpen:YES];
+}
+
+- (id)instanceWithClass:(Class)class
+{
+    int i, count = [instances count];
+    for (i = 0; i < count; i++) {
+        id instance = [instances objectAtIndex:i];
+        if ([instance isKindOfClass:class])
+            return instance;
+    }
+
+    return nil;
+}
+
+@end
+
+@implementation MMPlugInAppMediator
+
+MMPlugInAppMediator *sharedAppMediator = nil;
+
++ (MMPlugInAppMediator *)sharedAppMediator
+{
+    if (sharedAppMediator == nil)
+        sharedAppMediator = [[MMPlugInAppMediator alloc] init];
+
+    return sharedAppMediator;
+}
+
+- (void)addPlugInMenuItem:(NSMenuItem *)menuItem
+{
+    NSAssert(menuItem, @"menuItem cannot be nil");
+    [[MMAppController sharedInstance] addItemToPluginMenu:menuItem];
+}
+
+// It is a little bit ugly having to pass the class in here.  An alternative
+// would be to have a 1:1 relationship between app mediators and plugins, so
+// that we'd know exactly which plugin class to look for.
+- (id)keyPlugInInstanceWithClass:(Class)class
+{
+    MMVimController *keyVimController = [[NSApp delegate] keyVimController];
+    
+    if (keyVimController)
+        return [[keyVimController instanceMediator] instanceWithClass:class];
+
+    return nil;
+}
+
+@end
+
diff --git a/src/MacVim/PlugInInterface.h b/src/MacVim/PlugInInterface.h
new file mode 100644
index 0000000..c8de34d
--- /dev/null
+++ b/src/MacVim/PlugInInterface.h
@@ -0,0 +1,48 @@
+/* vi:set ts=8 sts=4 sw=4 ft=objc:
+ *
+ * VIM - Vi IMproved		by Bram Moolenaar
+ *				MacVim GUI port by Bjorn Winckler
+ *
+ * Do ":help uganda"  in Vim to read copying and usage conditions.
+ * Do ":help credits" in Vim to see a list of people who contributed.
+ * See README.txt for an overview of the Vim source code.
+ */
+
+// This header file should include everything that a plug-in will need so that
+// it is all we need to distribute for plug-in developers.
+
+
+@protocol PlugInAppMediator
+
+- (void)addPlugInMenuItem:(NSMenuItem *)menuItem;
+
+// Returns the plugin instance of the specified class associated with the key vim window.
+// If a vim window is not the key window, returns nil.
+// If there are no instances with the specified class, returns nil.
+- (id)keyPlugInInstanceWithClass:(Class)class;
+
+@end
+
+@protocol PlugInInstanceMediator
+
+// vim values are converted into NSNumber, NSString, NSArray, and NSDictionary
+- (id)evaluateVimExpression:(NSString *)vimExpression;
+- (void)addVimInput:(NSString *)input;
+- (void)openFiles:(NSArray *)fileNames;
+- (void)addPlugInView:(NSView *)view withTitle:(NSString *)title;
+
+@end
+
+@protocol PlugInProtocol
+// The mediator should not be retained.  It will exist until terminatePlugIn is
+// called.
++ (BOOL)initializePlugIn:(id<PlugInAppMediator>)mediator;
++ (void)terminatePlugIn;
+@end
+
+@interface NSObject (PlugInProtocol)
+// The mediator should not be retained.  It will exist until it releases this
+// plugin instance, and is not valid after that.
+- (id)initWithMediator:(id<PlugInInstanceMediator>)mediator;
+@end
+
diff --git a/src/MacVim/PlugInView.nib/classes.nib b/src/MacVim/PlugInView.nib/classes.nib
new file mode 100644
index 0000000..ae787f4
--- /dev/null
+++ b/src/MacVim/PlugInView.nib/classes.nib
@@ -0,0 +1,98 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+	<key>IBClasses</key>
+	<array>
+		<dict>
+			<key>CLASS</key>
+			<string>MMPlugInView</string>
+			<key>LANGUAGE</key>
+			<string>ObjC</string>
+			<key>OUTLETS</key>
+			<dict>
+				<key>controller</key>
+				<string>MMPlugInViewController</string>
+			</dict>
+			<key>SUPERCLASS</key>
+			<string>RBSplitSubview</string>
+		</dict>
+		<dict>
+			<key>CLASS</key>
+			<string>MMPlugInViewController</string>
+			<key>LANGUAGE</key>
+			<string>ObjC</string>
+			<key>OUTLETS</key>
+			<dict>
+				<key>contentView</key>
+				<string>NSView</string>
+				<key>headerView</key>
+				<string>MMPlugInViewHeader</string>
+				<key>plugInSubview</key>
+				<string>RBSplitSubview</string>
+				<key>titleField</key>
+				<string>NSTextField</string>
+			</dict>
+			<key>SUPERCLASS</key>
+			<string>NSObject</string>
+		</dict>
+		<dict>
+			<key>CLASS</key>
+			<string>RBSplitView</string>
+			<key>LANGUAGE</key>
+			<string>ObjC</string>
+			<key>OUTLETS</key>
+			<dict>
+				<key>delegate</key>
+				<string>id</string>
+			</dict>
+			<key>SUPERCLASS</key>
+			<string>RBSplitSubview</string>
+		</dict>
+		<dict>
+			<key>CLASS</key>
+			<string>RBSplitSubview</string>
+			<key>LANGUAGE</key>
+			<string>ObjC</string>
+			<key>SUPERCLASS</key>
+			<string>NSView</string>
+		</dict>
+		<dict>
+			<key>CLASS</key>
+			<string>FirstResponder</string>
+			<key>LANGUAGE</key>
+			<string>ObjC</string>
+			<key>SUPERCLASS</key>
+			<string>NSObject</string>
+		</dict>
+		<dict>
+			<key>ACTIONS</key>
+			<dict>
+				<key>didAdjustSubviews</key>
+				<string>RBSplitView</string>
+				<key>willAdjustSubviews</key>
+				<string>RBSplitView</string>
+			</dict>
+			<key>CLASS</key>
+			<string>NSObject</string>
+			<key>LANGUAGE</key>
+			<string>ObjC</string>
+		</dict>
+		<dict>
+			<key>CLASS</key>
+			<string>MMPlugInViewHeader</string>
+			<key>LANGUAGE</key>
+			<string>ObjC</string>
+			<key>OUTLETS</key>
+			<dict>
+				<key>controller</key>
+				<string>MMPlugInViewController</string>
+			</dict>
+			<key>SUPERCLASS</key>
+			<string>NSView</string>
+		</dict>
+	</array>
+	<key>IBVersion</key>
+	<string>1</string>
+</dict>
+</plist>
diff --git a/src/MacVim/PlugInView.nib/info.nib b/src/MacVim/PlugInView.nib/info.nib
new file mode 100644
index 0000000..668fd0b
--- /dev/null
+++ b/src/MacVim/PlugInView.nib/info.nib
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+	<key>IBFramework Version</key>
+	<string>629</string>
+	<key>IBLastKnownRelativeProjectPath</key>
+	<string>../MacVim.xcodeproj</string>
+	<key>IBOldestOS</key>
+	<integer>5</integer>
+	<key>IBOpenObjects</key>
+	<array>
+		<integer>6</integer>
+	</array>
+	<key>IBSystem Version</key>
+	<string>9D34</string>
+	<key>targetFramework</key>
+	<string>IBCocoaFramework</string>
+</dict>
+</plist>
diff --git a/src/MacVim/PlugInView.nib/keyedobjects.nib b/src/MacVim/PlugInView.nib/keyedobjects.nib
new file mode 100644
index 0000000000000000000000000000000000000000..28b0310c15e93ca234027c5c7ae5dc1b6b6f30bd
GIT binary patch
diff --git a/src/eval.c b/src/eval.c
index a35ef3b..becd293 100644
--- a/src/eval.c
+++ b/src/eval.c
@@ -5518,7 +5518,7 @@ list_equal(l1, l2, ic)
     return item1 == NULL && item2 == NULL;
 }
 
-#if defined(FEAT_PYTHON) || defined(PROTO)
+#if defined(FEAT_PYTHON) || defined(PROTO) || defined(FEAT_GUI_MACVIM)
 /*
  * Return the dictitem that an entry in a hashtable points to.
  */
-- 
1.5.5.1

