Correct bug in previous commit
[MacVim.git] / src / MacVim / MMVimController.m
blob1a198e90f152c9d289cfbde051938efd96b197b8
1 /* vi:set ts=8 sts=4 sw=4 ft=objc:
2  *
3  * VIM - Vi IMproved            by Bram Moolenaar
4  *                              MacVim GUI port by Bjorn Winckler
5  *
6  * Do ":help uganda"  in Vim to read copying and usage conditions.
7  * Do ":help credits" in Vim to see a list of people who contributed.
8  * See README.txt for an overview of the Vim source code.
9  */
11  * MMVimController
12  *
13  * Coordinates input/output to/from backend.  Each MMBackend communicates
14  * directly with a MMVimController.
15  *
16  * MMVimController does not deal with visual presentation.  Essentially it
17  * should be able to run with no window present.
18  *
19  * Output from the backend is received in processCommandQueue:.  Input is sent
20  * to the backend via sendMessage:data: or addVimInput:.  The latter allows
21  * execution of arbitrary stings in the Vim process, much like the Vim script
22  * function remote_send() does.  The messages that may be passed between
23  * frontend and backend are defined in an enum in MacVim.h.
24  */
26 #import "MMAppController.h"
27 #import "MMAtsuiTextView.h"
28 #import "MMTextView.h"
29 #import "MMVimController.h"
30 #import "MMVimView.h"
31 #import "MMWindowController.h"
32 #import "Miscellaneous.h"
34 #ifdef MM_ENABLE_PLUGINS
35 #import "MMPlugInManager.h"
36 #endif
38 static NSString *MMDefaultToolbarImageName = @"Attention";
39 static int MMAlertTextFieldHeight = 22;
41 // NOTE: By default a message sent to the backend will be dropped if it cannot
42 // be delivered instantly; otherwise there is a possibility that MacVim will
43 // 'beachball' while waiting to deliver DO messages to an unresponsive Vim
44 // process.  This means that you cannot rely on any message sent with
45 // sendMessage: to actually reach Vim.
46 static NSTimeInterval MMBackendProxyRequestTimeout = 0;
48 // Timeout used for setDialogReturn:.
49 static NSTimeInterval MMSetDialogReturnTimeout = 1.0;
51 // Maximum number of items in the receiveQueue.  (It is hard to predict what
52 // consequences changing this number will have.)
53 static int MMReceiveQueueCap = 100;
55 static BOOL isUnsafeMessage(int msgid);
58 @interface MMAlert : NSAlert {
59     NSTextField *textField;
61 - (void)setTextFieldString:(NSString *)textFieldString;
62 - (NSTextField *)textField;
63 @end
66 @interface MMVimController (Private)
67 - (void)doProcessCommandQueue:(NSArray *)queue;
68 - (void)handleMessage:(int)msgid data:(NSData *)data;
69 - (void)savePanelDidEnd:(NSSavePanel *)panel code:(int)code
70                 context:(void *)context;
71 - (void)alertDidEnd:(MMAlert *)alert code:(int)code context:(void *)context;
72 - (NSMenuItem *)menuItemForDescriptor:(NSArray *)desc;
73 - (NSMenu *)parentMenuForDescriptor:(NSArray *)desc;
74 - (NSMenu *)topLevelMenuForTitle:(NSString *)title;
75 - (void)addMenuWithDescriptor:(NSArray *)desc atIndex:(int)index;
76 - (void)addMenuItemWithDescriptor:(NSArray *)desc
77                           atIndex:(int)index
78                               tip:(NSString *)tip
79                              icon:(NSString *)icon
80                     keyEquivalent:(NSString *)keyEquivalent
81                      modifierMask:(int)modifierMask
82                            action:(NSString *)action
83                       isAlternate:(BOOL)isAlternate;
84 - (void)removeMenuItemWithDescriptor:(NSArray *)desc;
85 - (void)enableMenuItemWithDescriptor:(NSArray *)desc state:(BOOL)on;
86 - (void)addToolbarItemToDictionaryWithLabel:(NSString *)title
87         toolTip:(NSString *)tip icon:(NSString *)icon;
88 - (void)addToolbarItemWithLabel:(NSString *)label
89                           tip:(NSString *)tip icon:(NSString *)icon
90                       atIndex:(int)idx;
91 - (void)popupMenuWithDescriptor:(NSArray *)desc
92                           atRow:(NSNumber *)row
93                          column:(NSNumber *)col;
94 - (void)popupMenuWithAttributes:(NSDictionary *)attrs;
95 - (void)connectionDidDie:(NSNotification *)notification;
96 - (void)scheduleClose;
97 @end
102 @implementation MMVimController
104 - (id)initWithBackend:(id)backend pid:(int)processIdentifier
106     if (!(self = [super init]))
107         return nil;
109     windowController =
110         [[MMWindowController alloc] initWithVimController:self];
111     backendProxy = [backend retain];
112     sendQueue = [NSMutableArray new];
113     receiveQueue = [NSMutableArray new];
114     popupMenuItems = [[NSMutableArray alloc] init];
115     toolbarItemDict = [[NSMutableDictionary alloc] init];
116     pid = processIdentifier;
117     creationDate = [[NSDate alloc] init];
119     NSConnection *connection = [backendProxy connectionForProxy];
121     // TODO: Check that this will not set the timeout for the root proxy
122     // (in MMAppController).
123     [connection setRequestTimeout:MMBackendProxyRequestTimeout];
125     [[NSNotificationCenter defaultCenter] addObserver:self
126             selector:@selector(connectionDidDie:)
127                 name:NSConnectionDidDieNotification object:connection];
129     // Set up a main menu with only a "MacVim" menu (copied from a template
130     // which itself is set up in MainMenu.nib).  The main menu is populated
131     // by Vim later on.
132     mainMenu = [[NSMenu alloc] initWithTitle:@"MainMenu"];
133     NSMenuItem *appMenuItem = [[MMAppController sharedInstance]
134                                         appMenuItemTemplate];
135     appMenuItem = [[appMenuItem copy] autorelease];
137     // Note: If the title of the application menu is anything but what
138     // CFBundleName says then the application menu will not be typeset in
139     // boldface for some reason.  (It should already be set when we copy
140     // from the default main menu, but this is not the case for some
141     // reason.)
142     NSString *appName = [[NSBundle mainBundle]
143             objectForInfoDictionaryKey:@"CFBundleName"];
144     [appMenuItem setTitle:appName];
146     [mainMenu addItem:appMenuItem];
148 #ifdef MM_ENABLE_PLUGINS
149     instanceMediator = [[MMPlugInInstanceMediator alloc]
150             initWithVimController:self];
151 #endif
153     isInitialized = YES;
155     return self;
158 - (void)dealloc
160     LOG_DEALLOC
162     isInitialized = NO;
164 #ifdef MM_ENABLE_PLUGINS
165     [instanceMediator release]; instanceMediator = nil;
166 #endif
168     [serverName release];  serverName = nil;
169     [backendProxy release];  backendProxy = nil;
170     [sendQueue release];  sendQueue = nil;
171     [receiveQueue release];  receiveQueue = nil;
173     [toolbarItemDict release];  toolbarItemDict = nil;
174     [toolbar release];  toolbar = nil;
175     [popupMenuItems release];  popupMenuItems = nil;
176     [windowController release];  windowController = nil;
178     [vimState release];  vimState = nil;
179     [mainMenu release];  mainMenu = nil;
180     [creationDate release];  creationDate = nil;
182     [super dealloc];
185 - (MMWindowController *)windowController
187     return windowController;
190 #ifdef MM_ENABLE_PLUGINS
191 - (MMPlugInInstanceMediator *)instanceMediator
193     return instanceMediator;
195 #endif
197 - (NSDictionary *)vimState
199     return vimState;
202 - (NSMenu *)mainMenu
204     return mainMenu;
207 - (BOOL)isPreloading
209     return isPreloading;
212 - (void)setIsPreloading:(BOOL)yn
214     isPreloading = yn;
217 - (NSDate *)creationDate
219     return creationDate;
222 - (void)setServerName:(NSString *)name
224     if (name != serverName) {
225         [serverName release];
226         serverName = [name copy];
227     }
230 - (NSString *)serverName
232     return serverName;
235 - (int)pid
237     return pid;
240 - (void)dropFiles:(NSArray *)filenames forceOpen:(BOOL)force
242     NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
244     // Default to opening in tabs if layout is invalid or set to "windows".
245     int layout = [ud integerForKey:MMOpenLayoutKey];
246     if (layout < 0 || layout > MMLayoutTabs)
247         layout = MMLayoutTabs;
249     NSDictionary *args = [NSDictionary dictionaryWithObjectsAndKeys:
250             [NSNumber numberWithInt:layout],    @"layout",
251             filenames,                          @"filenames",
252             [NSNumber numberWithBool:force],    @"forceOpen",
253             nil];
255     [self sendMessage:DropFilesMsgID data:[args dictionaryAsData]];
258 - (void)dropString:(NSString *)string
260     int len = [string lengthOfBytesUsingEncoding:NSUTF8StringEncoding] + 1;
261     if (len > 0) {
262         NSMutableData *data = [NSMutableData data];
264         [data appendBytes:&len length:sizeof(int)];
265         [data appendBytes:[string UTF8String] length:len];
267         [self sendMessage:DropStringMsgID data:data];
268     }
271 - (void)passArguments:(NSDictionary *)args
273     if (!args) return;
275     [self sendMessage:OpenWithArgumentsMsgID data:[args dictionaryAsData]];
277     // HACK! Fool findUnusedEditor into thinking that this controller is not
278     // unused anymore, in case it is called before the arguments have reached
279     // the Vim process.  This should be a "safe" hack since the next time the
280     // Vim process flushes its output queue the state will be updated again (at
281     // which time the "unusedEditor" state will have been properly set).
282     NSMutableDictionary *dict = [NSMutableDictionary dictionaryWithDictionary:
283             vimState];
284     [dict setObject:[NSNumber numberWithBool:NO] forKey:@"unusedEditor"];
285     [vimState release];
286     vimState = [dict copy];
289 - (void)sendMessage:(int)msgid data:(NSData *)data
291     //NSLog(@"sendMessage:%s (isInitialized=%d inProcessCommandQueue=%d)",
292     //        MessageStrings[msgid], isInitialized, inProcessCommandQueue);
294     if (!isInitialized) return;
296     if (inProcessCommandQueue) {
297         //NSLog(@"In process command queue; delaying message send.");
298         [sendQueue addObject:[NSNumber numberWithInt:msgid]];
299         if (data)
300             [sendQueue addObject:data];
301         else
302             [sendQueue addObject:[NSNull null]];
303         return;
304     }
306     @try {
307         [backendProxy processInput:msgid data:data];
308     }
309     @catch (NSException *e) {
310         //NSLog(@"%@ %s Exception caught during DO call: %@",
311         //        [self className], _cmd, e);
312     }
315 - (BOOL)sendMessageNow:(int)msgid data:(NSData *)data
316                timeout:(NSTimeInterval)timeout
318     // Send a message with a timeout.  USE WITH EXTREME CAUTION!  Sending
319     // messages in rapid succession with a timeout may cause MacVim to beach
320     // ball forever.  In almost all circumstances sendMessage:data: should be
321     // used instead.
323     if (!isInitialized || inProcessCommandQueue)
324         return NO;
326     if (timeout < 0) timeout = 0;
328     BOOL sendOk = YES;
329     NSConnection *conn = [backendProxy connectionForProxy];
330     NSTimeInterval oldTimeout = [conn requestTimeout];
332     [conn setRequestTimeout:timeout];
334     @try {
335         [backendProxy processInput:msgid data:data];
336     }
337     @catch (NSException *e) {
338         sendOk = NO;
339     }
340     @finally {
341         [conn setRequestTimeout:oldTimeout];
342     }
344     return sendOk;
347 - (void)addVimInput:(NSString *)string
349     // This is a very general method of adding input to the Vim process.  It is
350     // basically the same as calling remote_send() on the process (see
351     // ':h remote_send').
352     if (string) {
353         NSData *data = [string dataUsingEncoding:NSUTF8StringEncoding];
354         [self sendMessage:AddInputMsgID data:data];
355     }
358 - (NSString *)evaluateVimExpression:(NSString *)expr
360     NSString *eval = nil;
362     @try {
363         eval = [backendProxy evaluateExpression:expr];
364     }
365     @catch (NSException *ex) { /* do nothing */ }
367     return eval;
370 - (id)evaluateVimExpressionCocoa:(NSString *)expr errorString:(NSString **)errstr
372     id eval = nil;
374     @try {
375         eval = [backendProxy evaluateExpressionCocoa:expr
376                                          errorString:errstr];
377     } @catch (NSException *ex) {
378         *errstr = [ex reason];
379     }
381     return eval;
384 - (id)backendProxy
386     return backendProxy;
389 - (void)cleanup
391     if (!isInitialized) return;
393     isInitialized = NO;
394     [toolbar setDelegate:nil];
395     [[NSNotificationCenter defaultCenter] removeObserver:self];
396     //[[backendProxy connectionForProxy] invalidate];
397     //[windowController close];
398     [windowController cleanup];
401 - (oneway void)showSavePanelWithAttributes:(in bycopy NSDictionary *)attr
403     if (!isInitialized) return;
405     BOOL inDefaultMode = [[[NSRunLoop currentRunLoop] currentMode]
406                                         isEqual:NSDefaultRunLoopMode];
407     if (!inDefaultMode) {
408         // Delay call until run loop is in default mode.
409         [self performSelectorOnMainThread:
410                                         @selector(showSavePanelWithAttributes:)
411                                withObject:attr
412                             waitUntilDone:NO
413                                     modes:[NSArray arrayWithObject:
414                                            NSDefaultRunLoopMode]];
415         return;
416     }
418     NSString *dir = [attr objectForKey:@"dir"];
419     BOOL saving = [[attr objectForKey:@"saving"] boolValue];
421     if (!dir) {
422         // 'dir == nil' means: set dir to the pwd of the Vim process, or let
423         // open dialog decide (depending on the below user default).
424         BOOL trackPwd = [[NSUserDefaults standardUserDefaults]
425                 boolForKey:MMDialogsTrackPwdKey];
426         if (trackPwd)
427             dir = [vimState objectForKey:@"pwd"];
428     }
430     if (saving) {
431         [[NSSavePanel savePanel] beginSheetForDirectory:dir file:nil
432                 modalForWindow:[windowController window]
433                  modalDelegate:self
434                 didEndSelector:@selector(savePanelDidEnd:code:context:)
435                    contextInfo:NULL];
436     } else {
437         NSOpenPanel *panel = [NSOpenPanel openPanel];
438         [panel setAllowsMultipleSelection:NO];
439         [panel setAccessoryView:openPanelAccessoryView()];
441         [panel beginSheetForDirectory:dir file:nil types:nil
442                 modalForWindow:[windowController window]
443                  modalDelegate:self
444                 didEndSelector:@selector(savePanelDidEnd:code:context:)
445                    contextInfo:NULL];
446     }
449 - (oneway void)presentDialogWithAttributes:(in bycopy NSDictionary *)attr
451     if (!isInitialized) return;
453     BOOL inDefaultMode = [[[NSRunLoop currentRunLoop] currentMode]
454                                         isEqual:NSDefaultRunLoopMode];
455     if (!inDefaultMode) {
456         // Delay call until run loop is in default mode.
457         [self performSelectorOnMainThread:
458                                         @selector(presentDialogWithAttributes:)
459                                withObject:attr
460                             waitUntilDone:NO
461                                     modes:[NSArray arrayWithObject:
462                                            NSDefaultRunLoopMode]];
463         return;
464     }
466     NSArray *buttonTitles = [attr objectForKey:@"buttonTitles"];
467     if (!(buttonTitles && [buttonTitles count])) return;
469     int style = [[attr objectForKey:@"alertStyle"] intValue];
470     NSString *message = [attr objectForKey:@"messageText"];
471     NSString *text = [attr objectForKey:@"informativeText"];
472     NSString *textFieldString = [attr objectForKey:@"textFieldString"];
473     MMAlert *alert = [[MMAlert alloc] init];
475     // NOTE! This has to be done before setting the informative text.
476     if (textFieldString)
477         [alert setTextFieldString:textFieldString];
479     [alert setAlertStyle:style];
481     if (message) {
482         [alert setMessageText:message];
483     } else {
484         // If no message text is specified 'Alert' is used, which we don't
485         // want, so set an empty string as message text.
486         [alert setMessageText:@""];
487     }
489     if (text) {
490         [alert setInformativeText:text];
491     } else if (textFieldString) {
492         // Make sure there is always room for the input text field.
493         [alert setInformativeText:@""];
494     }
496     unsigned i, count = [buttonTitles count];
497     for (i = 0; i < count; ++i) {
498         NSString *title = [buttonTitles objectAtIndex:i];
499         // NOTE: The title of the button may contain the character '&' to
500         // indicate that the following letter should be the key equivalent
501         // associated with the button.  Extract this letter and lowercase it.
502         NSString *keyEquivalent = nil;
503         NSRange hotkeyRange = [title rangeOfString:@"&"];
504         if (NSNotFound != hotkeyRange.location) {
505             if ([title length] > NSMaxRange(hotkeyRange)) {
506                 NSRange keyEquivRange = NSMakeRange(hotkeyRange.location+1, 1);
507                 keyEquivalent = [[title substringWithRange:keyEquivRange]
508                     lowercaseString];
509             }
511             NSMutableString *string = [NSMutableString stringWithString:title];
512             [string deleteCharactersInRange:hotkeyRange];
513             title = string;
514         }
516         [alert addButtonWithTitle:title];
518         // Set key equivalent for the button, but only if NSAlert hasn't
519         // already done so.  (Check the documentation for
520         // - [NSAlert addButtonWithTitle:] to see what key equivalents are
521         // automatically assigned.)
522         NSButton *btn = [[alert buttons] lastObject];
523         if ([[btn keyEquivalent] length] == 0 && keyEquivalent) {
524             [btn setKeyEquivalent:keyEquivalent];
525         }
526     }
528     [alert beginSheetModalForWindow:[windowController window]
529                       modalDelegate:self
530                      didEndSelector:@selector(alertDidEnd:code:context:)
531                         contextInfo:NULL];
533     [alert release];
536 - (oneway void)processCommandQueue:(in bycopy NSArray *)queue
538     if (!isInitialized) return;
540     if (inProcessCommandQueue) {
541         // NOTE!  If a synchronous DO call is made during
542         // doProcessCommandQueue: below it may happen that this method is
543         // called a second time while the synchronous message is waiting for a
544         // reply (could also happen if doProcessCommandQueue: enters a modal
545         // loop, see comment below).  Since this method cannot be considered
546         // reentrant, we queue the input and return immediately.
547         //
548         // If doProcessCommandQueue: enters a modal loop (happens e.g. on
549         // ShowPopupMenuMsgID) then the receiveQueue could grow to become
550         // arbitrarily large because DO calls still get processed.  To avoid
551         // this we set a cap on the size of the queue and simply clear it if it
552         // becomes too large.  (That is messages will be dropped and hence Vim
553         // and MacVim will at least temporarily be out of sync.)
554         if ([receiveQueue count] >= MMReceiveQueueCap)
555             [receiveQueue removeAllObjects];
557         [receiveQueue addObject:queue];
558         return;
559     }
561     inProcessCommandQueue = YES;
562     [self doProcessCommandQueue:queue];
564     int i;
565     for (i = 0; i < [receiveQueue count]; ++i) {
566         // Note that doProcessCommandQueue: may cause the receiveQueue to grow
567         // or get cleared (due to cap being hit).  Make sure to retain the item
568         // to process or it may get released from under us.
569         NSArray *q = [[receiveQueue objectAtIndex:i] retain];
570         [self doProcessCommandQueue:q];
571         [q release];
572     }
574     // We assume that the remaining calls make no synchronous DO calls.  If
575     // that did happen anyway, the command queue could get processed out of
576     // order.
578     if ([sendQueue count] > 0) {
579         @try {
580             [backendProxy processInputAndData:sendQueue];
581         }
582         @catch (NSException *e) {
583             // Connection timed out, just ignore this.
584             //NSLog(@"WARNING! Connection timed out in %s", _cmd);
585         }
587         [sendQueue removeAllObjects];
588     }
590     [windowController processCommandQueueDidFinish];
591     [receiveQueue removeAllObjects];
592     inProcessCommandQueue = NO;
595 - (NSToolbarItem *)toolbar:(NSToolbar *)theToolbar
596     itemForItemIdentifier:(NSString *)itemId
597     willBeInsertedIntoToolbar:(BOOL)flag
599     NSToolbarItem *item = [toolbarItemDict objectForKey:itemId];
600     if (!item) {
601         NSLog(@"WARNING:  No toolbar item with id '%@'", itemId);
602     }
604     return item;
607 - (NSArray *)toolbarAllowedItemIdentifiers:(NSToolbar *)theToolbar
609     return nil;
612 - (NSArray *)toolbarDefaultItemIdentifiers:(NSToolbar *)theToolbar
614     return nil;
617 @end // MMVimController
621 @implementation MMVimController (Private)
623 - (void)doProcessCommandQueue:(NSArray *)queue
625     NSMutableArray *delayQueue = nil;
627     @try {
628         unsigned i, count = [queue count];
629         if (count % 2) {
630             NSLog(@"WARNING: Uneven number of components (%d) in command "
631                     "queue.  Skipping...", count);
632             return;
633         }
635         //NSLog(@"======== %s BEGIN ========", _cmd);
636         for (i = 0; i < count; i += 2) {
637             NSData *value = [queue objectAtIndex:i];
638             NSData *data = [queue objectAtIndex:i+1];
640             int msgid = *((int*)[value bytes]);
641             //NSLog(@"%s%s", _cmd, MessageStrings[msgid]);
643             BOOL inDefaultMode = [[[NSRunLoop currentRunLoop] currentMode]
644                                                 isEqual:NSDefaultRunLoopMode];
645             if (!inDefaultMode && isUnsafeMessage(msgid)) {
646                 // NOTE: Because we listen to DO messages in 'event tracking'
647                 // mode we have to take extra care when doing things like
648                 // releasing view items (and other Cocoa objects).  Messages
649                 // that may be potentially "unsafe" are delayed until the run
650                 // loop is back to default mode at which time they are safe to
651                 // call again.
652                 //   A problem with this approach is that it is hard to
653                 // classify which messages are unsafe.  As a rule of thumb, if
654                 // a message may release an object used by the Cocoa framework
655                 // (e.g. views) then the message should be considered unsafe.
656                 //   Delaying messages may have undesired side-effects since it
657                 // means that messages may not be processed in the order Vim
658                 // sent them, so beware.
659                 if (!delayQueue)
660                     delayQueue = [NSMutableArray array];
662                 //NSLog(@"Adding unsafe message '%s' to delay queue (mode=%@)",
663                 //        MessageStrings[msgid],
664                 //        [[NSRunLoop currentRunLoop] currentMode]);
665                 [delayQueue addObject:value];
666                 [delayQueue addObject:data];
667             } else {
668                 [self handleMessage:msgid data:data];
669             }
670         }
671         //NSLog(@"======== %s  END  ========", _cmd);
672     }
673     @catch (NSException *e) {
674         NSLog(@"Exception caught whilst processing command queue: %@", e);
675     }
677     if (delayQueue) {
678         //NSLog(@"    Flushing delay queue (%d items)", [delayQueue count]/2);
679         [self performSelectorOnMainThread:@selector(processCommandQueue:)
680                                withObject:delayQueue
681                             waitUntilDone:NO
682                                     modes:[NSArray arrayWithObject:
683                                            NSDefaultRunLoopMode]];
684     }
687 - (void)handleMessage:(int)msgid data:(NSData *)data
689     //if (msgid != AddMenuMsgID && msgid != AddMenuItemMsgID &&
690     //        msgid != EnableMenuItemMsgID)
691     //    NSLog(@"%@ %s%s", [self className], _cmd, MessageStrings[msgid]);
693     if (OpenWindowMsgID == msgid) {
694         [windowController openWindow];
696         // If the vim controller is preloading then the window will be
697         // displayed when it is taken off the preload cache.
698         if (!isPreloading)
699             [windowController showWindow];
700     } else if (BatchDrawMsgID == msgid) {
701         [[[windowController vimView] textView] performBatchDrawWithData:data];
702     } else if (SelectTabMsgID == msgid) {
703 #if 0   // NOTE: Tab selection is done inside updateTabsWithData:.
704         const void *bytes = [data bytes];
705         int idx = *((int*)bytes);
706         //NSLog(@"Selecting tab with index %d", idx);
707         [windowController selectTabWithIndex:idx];
708 #endif
709     } else if (UpdateTabBarMsgID == msgid) {
710         [windowController updateTabsWithData:data];
711     } else if (ShowTabBarMsgID == msgid) {
712         [windowController showTabBar:YES];
713     } else if (HideTabBarMsgID == msgid) {
714         [windowController showTabBar:NO];
715     } else if (SetTextDimensionsMsgID == msgid || LiveResizeMsgID == msgid) {
716         const void *bytes = [data bytes];
717         int rows = *((int*)bytes);  bytes += sizeof(int);
718         int cols = *((int*)bytes);  bytes += sizeof(int);
720         [windowController setTextDimensionsWithRows:rows columns:cols
721                                                live:(LiveResizeMsgID==msgid)];
722     } else if (SetWindowTitleMsgID == msgid) {
723         const void *bytes = [data bytes];
724         int len = *((int*)bytes);  bytes += sizeof(int);
726         NSString *string = [[NSString alloc] initWithBytes:(void*)bytes
727                 length:len encoding:NSUTF8StringEncoding];
729         // While in live resize the window title displays the dimensions of the
730         // window so don't clobber this with a spurious "set title" message
731         // from Vim.
732         if (![[windowController vimView] inLiveResize])
733             [windowController setTitle:string];
735         [string release];
736     } else if (SetDocumentFilenameMsgID == msgid) {
737         const void *bytes = [data bytes];
738         int len = *((int*)bytes);  bytes += sizeof(int);
740         if (len > 0) {
741             NSString *filename = [[NSString alloc] initWithBytes:(void*)bytes
742                     length:len encoding:NSUTF8StringEncoding];
744             [windowController setDocumentFilename:filename];
746             [filename release];
747         } else {
748             [windowController setDocumentFilename:@""];
749         }
750     } else if (AddMenuMsgID == msgid) {
751         NSDictionary *attrs = [NSDictionary dictionaryWithData:data];
752         [self addMenuWithDescriptor:[attrs objectForKey:@"descriptor"]
753                 atIndex:[[attrs objectForKey:@"index"] intValue]];
754     } else if (AddMenuItemMsgID == msgid) {
755         NSDictionary *attrs = [NSDictionary dictionaryWithData:data];
756         [self addMenuItemWithDescriptor:[attrs objectForKey:@"descriptor"]
757                       atIndex:[[attrs objectForKey:@"index"] intValue]
758                           tip:[attrs objectForKey:@"tip"]
759                          icon:[attrs objectForKey:@"icon"]
760                 keyEquivalent:[attrs objectForKey:@"keyEquivalent"]
761                  modifierMask:[[attrs objectForKey:@"modifierMask"] intValue]
762                        action:[attrs objectForKey:@"action"]
763                   isAlternate:[[attrs objectForKey:@"isAlternate"] boolValue]];
764     } else if (RemoveMenuItemMsgID == msgid) {
765         NSDictionary *attrs = [NSDictionary dictionaryWithData:data];
766         [self removeMenuItemWithDescriptor:[attrs objectForKey:@"descriptor"]];
767     } else if (EnableMenuItemMsgID == msgid) {
768         NSDictionary *attrs = [NSDictionary dictionaryWithData:data];
769         [self enableMenuItemWithDescriptor:[attrs objectForKey:@"descriptor"]
770                 state:[[attrs objectForKey:@"enable"] boolValue]];
771     } else if (ShowToolbarMsgID == msgid) {
772         const void *bytes = [data bytes];
773         int enable = *((int*)bytes);  bytes += sizeof(int);
774         int flags = *((int*)bytes);  bytes += sizeof(int);
776         int mode = NSToolbarDisplayModeDefault;
777         if (flags & ToolbarLabelFlag) {
778             mode = flags & ToolbarIconFlag ? NSToolbarDisplayModeIconAndLabel
779                     : NSToolbarDisplayModeLabelOnly;
780         } else if (flags & ToolbarIconFlag) {
781             mode = NSToolbarDisplayModeIconOnly;
782         }
784         int size = flags & ToolbarSizeRegularFlag ? NSToolbarSizeModeRegular
785                 : NSToolbarSizeModeSmall;
787         [windowController showToolbar:enable size:size mode:mode];
788     } else if (CreateScrollbarMsgID == msgid) {
789         const void *bytes = [data bytes];
790         long ident = *((long*)bytes);  bytes += sizeof(long);
791         int type = *((int*)bytes);  bytes += sizeof(int);
793         [windowController createScrollbarWithIdentifier:ident type:type];
794     } else if (DestroyScrollbarMsgID == msgid) {
795         const void *bytes = [data bytes];
796         long ident = *((long*)bytes);  bytes += sizeof(long);
798         [windowController destroyScrollbarWithIdentifier:ident];
799     } else if (ShowScrollbarMsgID == msgid) {
800         const void *bytes = [data bytes];
801         long ident = *((long*)bytes);  bytes += sizeof(long);
802         int visible = *((int*)bytes);  bytes += sizeof(int);
804         [windowController showScrollbarWithIdentifier:ident state:visible];
805     } else if (SetScrollbarPositionMsgID == msgid) {
806         const void *bytes = [data bytes];
807         long ident = *((long*)bytes);  bytes += sizeof(long);
808         int pos = *((int*)bytes);  bytes += sizeof(int);
809         int len = *((int*)bytes);  bytes += sizeof(int);
811         [windowController setScrollbarPosition:pos length:len
812                                     identifier:ident];
813     } else if (SetScrollbarThumbMsgID == msgid) {
814         const void *bytes = [data bytes];
815         long ident = *((long*)bytes);  bytes += sizeof(long);
816         float val = *((float*)bytes);  bytes += sizeof(float);
817         float prop = *((float*)bytes);  bytes += sizeof(float);
819         [windowController setScrollbarThumbValue:val proportion:prop
820                                       identifier:ident];
821     } else if (SetFontMsgID == msgid) {
822         const void *bytes = [data bytes];
823         float size = *((float*)bytes);  bytes += sizeof(float);
824         int len = *((int*)bytes);  bytes += sizeof(int);
825         NSString *name = [[NSString alloc]
826                 initWithBytes:(void*)bytes length:len
827                      encoding:NSUTF8StringEncoding];
828         NSFont *font = [NSFont fontWithName:name size:size];
830         if (font)
831             [windowController setFont:font];
833         [name release];
834     } else if (SetWideFontMsgID == msgid) {
835         const void *bytes = [data bytes];
836         float size = *((float*)bytes);  bytes += sizeof(float);
837         int len = *((int*)bytes);  bytes += sizeof(int);
838         if (len > 0) {
839             NSString *name = [[NSString alloc]
840                     initWithBytes:(void*)bytes length:len
841                          encoding:NSUTF8StringEncoding];
842             NSFont *font = [NSFont fontWithName:name size:size];
843             [windowController setWideFont:font];
845             [name release];
846         } else {
847             [windowController setWideFont:nil];
848         }
849     } else if (SetDefaultColorsMsgID == msgid) {
850         const void *bytes = [data bytes];
851         unsigned bg = *((unsigned*)bytes);  bytes += sizeof(unsigned);
852         unsigned fg = *((unsigned*)bytes);  bytes += sizeof(unsigned);
853         NSColor *back = [NSColor colorWithArgbInt:bg];
854         NSColor *fore = [NSColor colorWithRgbInt:fg];
856         [windowController setDefaultColorsBackground:back foreground:fore];
857     } else if (ExecuteActionMsgID == msgid) {
858         const void *bytes = [data bytes];
859         int len = *((int*)bytes);  bytes += sizeof(int);
860         NSString *actionName = [[NSString alloc]
861                 initWithBytes:(void*)bytes length:len
862                      encoding:NSUTF8StringEncoding];
864         SEL sel = NSSelectorFromString(actionName);
865         [NSApp sendAction:sel to:nil from:self];
867         [actionName release];
868     } else if (ShowPopupMenuMsgID == msgid) {
869         NSDictionary *attrs = [NSDictionary dictionaryWithData:data];
871         // The popup menu enters a modal loop so delay this call so that we
872         // don't block inside processCommandQueue:.
873         [self performSelectorOnMainThread:@selector(popupMenuWithAttributes:)
874                              withObject:attrs
875                           waitUntilDone:NO
876                                   modes:[NSArray arrayWithObject:
877                                          NSDefaultRunLoopMode]];
878     } else if (SetMouseShapeMsgID == msgid) {
879         const void *bytes = [data bytes];
880         int shape = *((int*)bytes);  bytes += sizeof(int);
882         [windowController setMouseShape:shape];
883     } else if (AdjustLinespaceMsgID == msgid) {
884         const void *bytes = [data bytes];
885         int linespace = *((int*)bytes);  bytes += sizeof(int);
887         [windowController adjustLinespace:linespace];
888     } else if (ActivateMsgID == msgid) {
889         //NSLog(@"ActivateMsgID");
890         [NSApp activateIgnoringOtherApps:YES];
891         [[windowController window] makeKeyAndOrderFront:self];
892     } else if (SetServerNameMsgID == msgid) {
893         NSString *name = [[NSString alloc] initWithData:data
894                                                encoding:NSUTF8StringEncoding];
895         [self setServerName:name];
896         [name release];
897     } else if (EnterFullscreenMsgID == msgid) {
898         const void *bytes = [data bytes];
899         int fuoptions = *((int*)bytes); bytes += sizeof(int);
900         int bg = *((int*)bytes);
901         NSColor *back = [NSColor colorWithArgbInt:bg];
903         [windowController enterFullscreen:fuoptions backgroundColor:back];
904     } else if (LeaveFullscreenMsgID == msgid) {
905         [windowController leaveFullscreen];
906     } else if (BuffersNotModifiedMsgID == msgid) {
907         [windowController setBuffersModified:NO];
908     } else if (BuffersModifiedMsgID == msgid) {
909         [windowController setBuffersModified:YES];
910     } else if (SetPreEditPositionMsgID == msgid) {
911         const int *dim = (const int*)[data bytes];
912         [[[windowController vimView] textView] setPreEditRow:dim[0]
913                                                       column:dim[1]];
914     } else if (EnableAntialiasMsgID == msgid) {
915         [[[windowController vimView] textView] setAntialias:YES];
916     } else if (DisableAntialiasMsgID == msgid) {
917         [[[windowController vimView] textView] setAntialias:NO];
918     } else if (SetVimStateMsgID == msgid) {
919         NSDictionary *dict = [NSDictionary dictionaryWithData:data];
920         if (dict) {
921             [vimState release];
922             vimState = [dict retain];
923         }
924     } else if (CloseWindowMsgID == msgid) {
925         [self scheduleClose];
926     } else if (SetFullscreenColorMsgID == msgid) {
927         const int *bg = (const int*)[data bytes];
928         NSColor *color = [NSColor colorWithRgbInt:*bg];
930         [windowController setFullscreenBackgroundColor:color];
931     // IMPORTANT: When adding a new message, make sure to update
932     // isUnsafeMessage() if necessary!
933     } else {
934         NSLog(@"WARNING: Unknown message received (msgid=%d)", msgid);
935     }
938 - (void)savePanelDidEnd:(NSSavePanel *)panel code:(int)code
939                 context:(void *)context
941     NSString *path = (code == NSOKButton) ? [panel filename] : nil;
943     // NOTE! setDialogReturn: is a synchronous call so set a proper timeout to
944     // avoid waiting forever for it to finish.  We make this a synchronous call
945     // so that we can be fairly certain that Vim doesn't think the dialog box
946     // is still showing when MacVim has in fact already dismissed it.
947     NSConnection *conn = [backendProxy connectionForProxy];
948     NSTimeInterval oldTimeout = [conn requestTimeout];
949     [conn setRequestTimeout:MMSetDialogReturnTimeout];
951     @try {
952         [backendProxy setDialogReturn:path];
954         // Add file to the "Recent Files" menu (this ensures that files that
955         // are opened/saved from a :browse command are added to this menu).
956         if (path)
957             [[NSDocumentController sharedDocumentController]
958                     noteNewRecentFilePath:path];
959     }
960     @catch (NSException *e) {
961         NSLog(@"Exception caught in %s %@", _cmd, e);
962     }
963     @finally {
964         [conn setRequestTimeout:oldTimeout];
965     }
968 - (void)alertDidEnd:(MMAlert *)alert code:(int)code context:(void *)context
970     NSArray *ret = nil;
972     code = code - NSAlertFirstButtonReturn + 1;
974     if ([alert isKindOfClass:[MMAlert class]] && [alert textField]) {
975         ret = [NSArray arrayWithObjects:[NSNumber numberWithInt:code],
976             [[alert textField] stringValue], nil];
977     } else {
978         ret = [NSArray arrayWithObject:[NSNumber numberWithInt:code]];
979     }
981     @try {
982         [backendProxy setDialogReturn:ret];
983     }
984     @catch (NSException *e) {
985         NSLog(@"Exception caught in %s %@", _cmd, e);
986     }
989 - (NSMenuItem *)menuItemForDescriptor:(NSArray *)desc
991     if (!(desc && [desc count] > 0)) return nil;
993     NSString *rootName = [desc objectAtIndex:0];
994     NSArray *rootItems = [rootName hasPrefix:@"PopUp"] ? popupMenuItems
995                                                        : [mainMenu itemArray];
997     NSMenuItem *item = nil;
998     int i, count = [rootItems count];
999     for (i = 0; i < count; ++i) {
1000         item = [rootItems objectAtIndex:i];
1001         if ([[item title] isEqual:rootName])
1002             break;
1003     }
1005     if (i == count) return nil;
1007     count = [desc count];
1008     for (i = 1; i < count; ++i) {
1009         item = [[item submenu] itemWithTitle:[desc objectAtIndex:i]];
1010         if (!item) return nil;
1011     }
1013     return item;
1016 - (NSMenu *)parentMenuForDescriptor:(NSArray *)desc
1018     if (!(desc && [desc count] > 0)) return nil;
1020     NSString *rootName = [desc objectAtIndex:0];
1021     NSArray *rootItems = [rootName hasPrefix:@"PopUp"] ? popupMenuItems
1022                                                        : [mainMenu itemArray];
1024     NSMenu *menu = nil;
1025     int i, count = [rootItems count];
1026     for (i = 0; i < count; ++i) {
1027         NSMenuItem *item = [rootItems objectAtIndex:i];
1028         if ([[item title] isEqual:rootName]) {
1029             menu = [item submenu];
1030             break;
1031         }
1032     }
1034     if (!menu) return nil;
1036     count = [desc count] - 1;
1037     for (i = 1; i < count; ++i) {
1038         NSMenuItem *item = [menu itemWithTitle:[desc objectAtIndex:i]];
1039         menu = [item submenu];
1040         if (!menu) return nil;
1041     }
1043     return menu;
1046 - (NSMenu *)topLevelMenuForTitle:(NSString *)title
1048     // Search only the top-level menus.
1050     unsigned i, count = [popupMenuItems count];
1051     for (i = 0; i < count; ++i) {
1052         NSMenuItem *item = [popupMenuItems objectAtIndex:i];
1053         if ([title isEqual:[item title]])
1054             return [item submenu];
1055     }
1057     count = [mainMenu numberOfItems];
1058     for (i = 0; i < count; ++i) {
1059         NSMenuItem *item = [mainMenu itemAtIndex:i];
1060         if ([title isEqual:[item title]])
1061             return [item submenu];
1062     }
1064     return nil;
1067 - (void)addMenuWithDescriptor:(NSArray *)desc atIndex:(int)idx
1069     if (!(desc && [desc count] > 0 && idx >= 0)) return;
1071     NSString *rootName = [desc objectAtIndex:0];
1072     if ([rootName isEqual:@"ToolBar"]) {
1073         // The toolbar only has one menu, we take this as a hint to create a
1074         // toolbar, then we return.
1075         if (!toolbar) {
1076             // NOTE! Each toolbar must have a unique identifier, else each
1077             // window will have the same toolbar.
1078             NSString *ident = [NSString stringWithFormat:@"%d", (int)self];
1079             toolbar = [[NSToolbar alloc] initWithIdentifier:ident];
1081             [toolbar setShowsBaselineSeparator:NO];
1082             [toolbar setDelegate:self];
1083             [toolbar setDisplayMode:NSToolbarDisplayModeIconOnly];
1084             [toolbar setSizeMode:NSToolbarSizeModeSmall];
1086             [windowController setToolbar:toolbar];
1087         }
1089         return;
1090     }
1092     // This is either a main menu item or a popup menu item.
1093     NSString *title = [desc lastObject];
1094     NSMenuItem *item = [[NSMenuItem alloc] init];
1095     NSMenu *menu = [[NSMenu alloc] initWithTitle:title];
1097     [item setTitle:title];
1098     [item setSubmenu:menu];
1100     NSMenu *parent = [self parentMenuForDescriptor:desc];
1101     if (!parent && [rootName hasPrefix:@"PopUp"]) {
1102         if ([popupMenuItems count] <= idx) {
1103             [popupMenuItems addObject:item];
1104         } else {
1105             [popupMenuItems insertObject:item atIndex:idx];
1106         }
1107     } else {
1108         // If descriptor has no parent and its not a popup (or toolbar) menu,
1109         // then it must belong to main menu.
1110         if (!parent) parent = mainMenu;
1112         if ([parent numberOfItems] <= idx) {
1113             [parent addItem:item];
1114         } else {
1115             [parent insertItem:item atIndex:idx];
1116         }
1117     }
1119     [item release];
1120     [menu release];
1123 - (void)addMenuItemWithDescriptor:(NSArray *)desc
1124                           atIndex:(int)idx
1125                               tip:(NSString *)tip
1126                              icon:(NSString *)icon
1127                     keyEquivalent:(NSString *)keyEquivalent
1128                      modifierMask:(int)modifierMask
1129                            action:(NSString *)action
1130                       isAlternate:(BOOL)isAlternate
1132     if (!(desc && [desc count] > 1 && idx >= 0)) return;
1134     NSString *title = [desc lastObject];
1135     NSString *rootName = [desc objectAtIndex:0];
1137     if ([rootName isEqual:@"ToolBar"]) {
1138         if (toolbar && [desc count] == 2)
1139             [self addToolbarItemWithLabel:title tip:tip icon:icon atIndex:idx];
1140         return;
1141     }
1143     NSMenu *parent = [self parentMenuForDescriptor:desc];
1144     if (!parent) {
1145         NSLog(@"WARNING: Menu item '%@' has no parent",
1146                 [desc componentsJoinedByString:@"->"]);
1147         return;
1148     }
1150     NSMenuItem *item = nil;
1151     if (0 == [title length]
1152             || ([title hasPrefix:@"-"] && [title hasSuffix:@"-"])) {
1153         item = [NSMenuItem separatorItem];
1154         [item setTitle:title];
1155     } else {
1156         item = [[[NSMenuItem alloc] init] autorelease];
1157         [item setTitle:title];
1159         // Note: It is possible to set the action to a message that "doesn't
1160         // exist" without problems.  We take advantage of this when adding
1161         // "dummy items" e.g. when dealing with the "Recent Files" menu (in
1162         // which case a recentFilesDummy: action is set, although it is never
1163         // used).
1164         if ([action length] > 0)
1165             [item setAction:NSSelectorFromString(action)];
1166         else
1167             [item setAction:@selector(vimMenuItemAction:)];
1168         if ([tip length] > 0) [item setToolTip:tip];
1169         if ([keyEquivalent length] > 0) {
1170             [item setKeyEquivalent:keyEquivalent];
1171             [item setKeyEquivalentModifierMask:modifierMask];
1172         }
1173         [item setAlternate:isAlternate];
1175         // The tag is used to indicate whether Vim thinks a menu item should be
1176         // enabled or disabled.  By default Vim thinks menu items are enabled.
1177         [item setTag:1];
1178     }
1180     if ([parent numberOfItems] <= idx) {
1181         [parent addItem:item];
1182     } else {
1183         [parent insertItem:item atIndex:idx];
1184     }
1187 - (void)removeMenuItemWithDescriptor:(NSArray *)desc
1189     if (!(desc && [desc count] > 0)) return;
1191     NSString *title = [desc lastObject];
1192     NSString *rootName = [desc objectAtIndex:0];
1193     if ([rootName isEqual:@"ToolBar"]) {
1194         if (toolbar) {
1195             // Only remove toolbar items, never actually remove the toolbar
1196             // itself or strange things may happen.
1197             if ([desc count] == 2) {
1198                 int idx = [toolbar indexOfItemWithItemIdentifier:title];
1199                 if (idx != NSNotFound)
1200                     [toolbar removeItemAtIndex:idx];
1201             }
1202         }
1203         return;
1204     }
1206     NSMenuItem *item = [self menuItemForDescriptor:desc];
1207     if (!item) {
1208         NSLog(@"Failed to remove menu item, descriptor not found: %@",
1209                 [desc componentsJoinedByString:@"->"]);
1210         return;
1211     }
1213     [item retain];
1215     if ([item menu] == [NSApp mainMenu] || ![item menu]) {
1216         // NOTE: To be on the safe side we try to remove the item from
1217         // both arrays (it is ok to call removeObject: even if an array
1218         // does not contain the object to remove).
1219         [popupMenuItems removeObject:item];
1220     }
1222     if ([item menu])
1223         [[item menu] removeItem:item];
1225     [item release];
1228 - (void)enableMenuItemWithDescriptor:(NSArray *)desc state:(BOOL)on
1230     if (!(desc && [desc count] > 0)) return;
1232     /*NSLog(@"%sable item %@", on ? "En" : "Dis",
1233             [desc componentsJoinedByString:@"->"]);*/
1235     NSString *rootName = [desc objectAtIndex:0];
1236     if ([rootName isEqual:@"ToolBar"]) {
1237         if (toolbar && [desc count] == 2) {
1238             NSString *title = [desc lastObject];
1239             [[toolbar itemWithItemIdentifier:title] setEnabled:on];
1240         }
1241     } else {
1242         // Use tag to set whether item is enabled or disabled instead of
1243         // calling setEnabled:.  This way the menus can autoenable themselves
1244         // but at the same time Vim can set if a menu is enabled whenever it
1245         // wants to.
1246         [[self menuItemForDescriptor:desc] setTag:on];
1247     }
1250 - (void)addToolbarItemToDictionaryWithLabel:(NSString *)title
1251                                     toolTip:(NSString *)tip
1252                                        icon:(NSString *)icon
1254     // If the item corresponds to a separator then do nothing, since it is
1255     // already defined by Cocoa.
1256     if (!title || [title isEqual:NSToolbarSeparatorItemIdentifier]
1257                || [title isEqual:NSToolbarSpaceItemIdentifier]
1258                || [title isEqual:NSToolbarFlexibleSpaceItemIdentifier])
1259         return;
1261     NSToolbarItem *item = [[NSToolbarItem alloc] initWithItemIdentifier:title];
1262     [item setLabel:title];
1263     [item setToolTip:tip];
1264     [item setAction:@selector(vimToolbarItemAction:)];
1265     [item setAutovalidates:NO];
1267     NSImage *img = [NSImage imageNamed:icon];
1268     if (!img) {
1269         img = [[[NSImage alloc] initByReferencingFile:icon] autorelease];
1270         if (!(img && [img isValid]))
1271             img = nil;
1272     }
1273     if (!img) {
1274         NSLog(@"WARNING: Could not find image with name '%@' to use as toolbar"
1275                " image for identifier '%@';"
1276                " using default toolbar icon '%@' instead.",
1277                icon, title, MMDefaultToolbarImageName);
1279         img = [NSImage imageNamed:MMDefaultToolbarImageName];
1280     }
1282     [item setImage:img];
1284     [toolbarItemDict setObject:item forKey:title];
1286     [item release];
1289 - (void)addToolbarItemWithLabel:(NSString *)label
1290                             tip:(NSString *)tip
1291                            icon:(NSString *)icon
1292                         atIndex:(int)idx
1294     if (!toolbar) return;
1296     // Check for separator items.
1297     if (!label) {
1298         label = NSToolbarSeparatorItemIdentifier;
1299     } else if ([label length] >= 2 && [label hasPrefix:@"-"]
1300                                    && [label hasSuffix:@"-"]) {
1301         // The label begins and ends with '-'; decided which kind of separator
1302         // item it is by looking at the prefix.
1303         if ([label hasPrefix:@"-space"]) {
1304             label = NSToolbarSpaceItemIdentifier;
1305         } else if ([label hasPrefix:@"-flexspace"]) {
1306             label = NSToolbarFlexibleSpaceItemIdentifier;
1307         } else {
1308             label = NSToolbarSeparatorItemIdentifier;
1309         }
1310     }
1312     [self addToolbarItemToDictionaryWithLabel:label toolTip:tip icon:icon];
1314     int maxIdx = [[toolbar items] count];
1315     if (maxIdx < idx) idx = maxIdx;
1317     [toolbar insertItemWithItemIdentifier:label atIndex:idx];
1320 - (void)popupMenuWithDescriptor:(NSArray *)desc
1321                           atRow:(NSNumber *)row
1322                          column:(NSNumber *)col
1324     NSMenu *menu = [[self menuItemForDescriptor:desc] submenu];
1325     if (!menu) return;
1327     id textView = [[windowController vimView] textView];
1328     NSPoint pt;
1329     if (row && col) {
1330         // TODO: Let textView convert (row,col) to NSPoint.
1331         int r = [row intValue];
1332         int c = [col intValue];
1333         NSSize cellSize = [textView cellSize];
1334         pt = NSMakePoint((c+1)*cellSize.width, (r+1)*cellSize.height);
1335         pt = [textView convertPoint:pt toView:nil];
1336     } else {
1337         pt = [[windowController window] mouseLocationOutsideOfEventStream];
1338     }
1340     NSEvent *event = [NSEvent mouseEventWithType:NSRightMouseDown
1341                            location:pt
1342                       modifierFlags:0
1343                           timestamp:0
1344                        windowNumber:[[windowController window] windowNumber]
1345                             context:nil
1346                         eventNumber:0
1347                          clickCount:0
1348                            pressure:1.0];
1350     [NSMenu popUpContextMenu:menu withEvent:event forView:textView];
1353 - (void)popupMenuWithAttributes:(NSDictionary *)attrs
1355     if (!attrs) return;
1357     [self popupMenuWithDescriptor:[attrs objectForKey:@"descriptor"]
1358                             atRow:[attrs objectForKey:@"row"]
1359                            column:[attrs objectForKey:@"column"]];
1362 - (void)connectionDidDie:(NSNotification *)notification
1364     //NSLog(@"%@ %s%@", [self className], _cmd, notification);
1365     [self scheduleClose];
1368 - (void)scheduleClose
1370     // NOTE!  This message can arrive at pretty much anytime, e.g. while
1371     // the run loop is the 'event tracking' mode.  This means that Cocoa may
1372     // well be in the middle of processing some message while this message is
1373     // received.  If we were to remove the vim controller straight away we may
1374     // free objects that Cocoa is currently using (e.g. view objects).  The
1375     // following call ensures that the vim controller is not released until the
1376     // run loop is back in the 'default' mode.
1377     [[MMAppController sharedInstance]
1378             performSelectorOnMainThread:@selector(removeVimController:)
1379                              withObject:self
1380                           waitUntilDone:NO
1381                                   modes:[NSArray arrayWithObject:
1382                                          NSDefaultRunLoopMode]];
1385 @end // MMVimController (Private)
1390 @implementation MMAlert
1391 - (void)dealloc
1393     [textField release];  textField = nil;
1394     [super dealloc];
1397 - (void)setTextFieldString:(NSString *)textFieldString
1399     [textField release];
1400     textField = [[NSTextField alloc] init];
1401     [textField setStringValue:textFieldString];
1404 - (NSTextField *)textField
1406     return textField;
1409 - (void)setInformativeText:(NSString *)text
1411     if (textField) {
1412         // HACK! Add some space for the text field.
1413         [super setInformativeText:[text stringByAppendingString:@"\n\n\n"]];
1414     } else {
1415         [super setInformativeText:text];
1416     }
1419 - (void)beginSheetModalForWindow:(NSWindow *)window
1420                    modalDelegate:(id)delegate
1421                   didEndSelector:(SEL)didEndSelector
1422                      contextInfo:(void *)contextInfo
1424     [super beginSheetModalForWindow:window
1425                       modalDelegate:delegate
1426                      didEndSelector:didEndSelector
1427                         contextInfo:contextInfo];
1429     // HACK! Place the input text field at the bottom of the informative text
1430     // (which has been made a bit larger by adding newline characters).
1431     NSView *contentView = [[self window] contentView];
1432     NSRect rect = [contentView frame];
1433     rect.origin.y = rect.size.height;
1435     NSArray *subviews = [contentView subviews];
1436     unsigned i, count = [subviews count];
1437     for (i = 0; i < count; ++i) {
1438         NSView *view = [subviews objectAtIndex:i];
1439         if ([view isKindOfClass:[NSTextField class]]
1440                 && [view frame].origin.y < rect.origin.y) {
1441             // NOTE: The informative text field is the lowest NSTextField in
1442             // the alert dialog.
1443             rect = [view frame];
1444         }
1445     }
1447     rect.size.height = MMAlertTextFieldHeight;
1448     [textField setFrame:rect];
1449     [contentView addSubview:textField];
1450     [textField becomeFirstResponder];
1453 @end // MMAlert
1458     static BOOL
1459 isUnsafeMessage(int msgid)
1461     // Messages that may release Cocoa objects must be added to this list.  For
1462     // example, UpdateTabBarMsgID may delete NSTabViewItem objects so it goes
1463     // on this list.
1464     static int unsafeMessages[] = { // REASON MESSAGE IS ON THIS LIST:
1465         //OpenWindowMsgID,            // Changes lots of state
1466         UpdateTabBarMsgID,          // May delete NSTabViewItem
1467         RemoveMenuItemMsgID,        // Deletes NSMenuItem
1468         DestroyScrollbarMsgID,      // Deletes NSScroller
1469         ExecuteActionMsgID,         // Impossible to predict
1470         ShowPopupMenuMsgID,         // Enters modal loop
1471         ActivateMsgID,              // ?
1472         EnterFullscreenMsgID,       // Modifies delegate of window controller
1473         LeaveFullscreenMsgID,       // Modifies delegate of window controller
1474         CloseWindowMsgID,           // See note below
1475     };
1477     // NOTE about CloseWindowMsgID: If this arrives at the same time as say
1478     // ExecuteActionMsgID, then the "execute" message will be lost due to it
1479     // being queued and handled after the "close" message has caused the
1480     // controller to cleanup...UNLESS we add CloseWindowMsgID to the list of
1481     // unsafe messages.  This is the _only_ reason it is on this list (since
1482     // all that happens in response to it is that we schedule another message
1483     // for later handling).
1485     int i, count = sizeof(unsafeMessages)/sizeof(unsafeMessages[0]);
1486     for (i = 0; i < count; ++i)
1487         if (msgid == unsafeMessages[i])
1488             return YES;
1490     return NO;