Dialogs are always displayed in the default run loop mode
[MacVim.git] / src / MacVim / MMVimController.m
blob6689a9207c5480ced91158189544613aa401d832
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"
35 static NSString *MMDefaultToolbarImageName = @"Attention";
36 static int MMAlertTextFieldHeight = 22;
38 // NOTE: By default a message sent to the backend will be dropped if it cannot
39 // be delivered instantly; otherwise there is a possibility that MacVim will
40 // 'beachball' while waiting to deliver DO messages to an unresponsive Vim
41 // process.  This means that you cannot rely on any message sent with
42 // sendMessage: to actually reach Vim.
43 static NSTimeInterval MMBackendProxyRequestTimeout = 0;
45 // Timeout used for setDialogReturn:.
46 static NSTimeInterval MMSetDialogReturnTimeout = 1.0;
48 // Maximum number of items in the receiveQueue.  (It is hard to predict what
49 // consequences changing this number will have.)
50 static int MMReceiveQueueCap = 100;
52 static BOOL isUnsafeMessage(int msgid);
55 @interface MMAlert : NSAlert {
56     NSTextField *textField;
58 - (void)setTextFieldString:(NSString *)textFieldString;
59 - (NSTextField *)textField;
60 @end
63 @interface MMVimController (Private)
64 - (void)doProcessCommandQueue:(NSArray *)queue;
65 - (void)handleMessage:(int)msgid data:(NSData *)data;
66 - (void)savePanelDidEnd:(NSSavePanel *)panel code:(int)code
67                 context:(void *)context;
68 - (void)alertDidEnd:(MMAlert *)alert code:(int)code context:(void *)context;
69 - (NSMenuItem *)menuItemForDescriptor:(NSArray *)desc;
70 - (NSMenu *)parentMenuForDescriptor:(NSArray *)desc;
71 - (NSMenu *)topLevelMenuForTitle:(NSString *)title;
72 - (void)addMenuWithDescriptor:(NSArray *)desc atIndex:(int)index;
73 - (void)addMenuItemWithDescriptor:(NSArray *)desc
74                           atIndex:(int)index
75                               tip:(NSString *)tip
76                              icon:(NSString *)icon
77                     keyEquivalent:(NSString *)keyEquivalent
78                      modifierMask:(int)modifierMask
79                            action:(NSString *)action
80                       isAlternate:(BOOL)isAlternate;
81 - (void)removeMenuItemWithDescriptor:(NSArray *)desc;
82 - (void)enableMenuItemWithDescriptor:(NSArray *)desc state:(BOOL)on;
83 - (void)addToolbarItemToDictionaryWithLabel:(NSString *)title
84         toolTip:(NSString *)tip icon:(NSString *)icon;
85 - (void)addToolbarItemWithLabel:(NSString *)label
86                           tip:(NSString *)tip icon:(NSString *)icon
87                       atIndex:(int)idx;
88 - (void)popupMenuWithDescriptor:(NSArray *)desc
89                           atRow:(NSNumber *)row
90                          column:(NSNumber *)col;
91 - (void)popupMenuWithAttributes:(NSDictionary *)attrs;
92 - (void)connectionDidDie:(NSNotification *)notification;
93 @end
98 @implementation MMVimController
100 - (id)initWithBackend:(id)backend pid:(int)processIdentifier
102     if ((self = [super init])) {
103         windowController =
104             [[MMWindowController alloc] initWithVimController:self];
105         backendProxy = [backend retain];
106         sendQueue = [NSMutableArray new];
107         receiveQueue = [NSMutableArray new];
108         popupMenuItems = [[NSMutableArray alloc] init];
109         toolbarItemDict = [[NSMutableDictionary alloc] init];
110         pid = processIdentifier;
112         NSConnection *connection = [backendProxy connectionForProxy];
114         // TODO: Check that this will not set the timeout for the root proxy
115         // (in MMAppController).
116         [connection setRequestTimeout:MMBackendProxyRequestTimeout];
118         [[NSNotificationCenter defaultCenter] addObserver:self
119                 selector:@selector(connectionDidDie:)
120                     name:NSConnectionDidDieNotification object:connection];
122         // Set up a main menu with only a "MacVim" menu (copied from a template
123         // which itself is set up in MainMenu.nib).  The main menu is populated
124         // by Vim later on.
125         mainMenu = [[NSMenu alloc] initWithTitle:@"MainMenu"];
126         NSMenuItem *appMenuItem = [[MMAppController sharedInstance]
127                                             appMenuItemTemplate];
128         appMenuItem = [[appMenuItem copy] autorelease];
130         // Note: If the title of the application menu is anything but what
131         // CFBundleName says then the application menu will not be typeset in
132         // boldface for some reason.  (It should already be set when we copy
133         // from the default main menu, but this is not the case for some
134         // reason.)
135         NSString *appName = [[NSBundle mainBundle]
136                 objectForInfoDictionaryKey:@"CFBundleName"];
137         [appMenuItem setTitle:appName];
139         [mainMenu addItem:appMenuItem];
141         isInitialized = YES;
142     }
144     return self;
147 - (void)dealloc
149     //NSLog(@"%@ %s", [self className], _cmd);
150     isInitialized = NO;
152     [serverName release];  serverName = nil;
153     [backendProxy release];  backendProxy = nil;
154     [sendQueue release];  sendQueue = nil;
155     [receiveQueue release];  receiveQueue = nil;
157     [toolbarItemDict release];  toolbarItemDict = nil;
158     [toolbar release];  toolbar = nil;
159     [popupMenuItems release];  popupMenuItems = nil;
160     [windowController release];  windowController = nil;
162     [vimState release];  vimState = nil;
163     [mainMenu release];  mainMenu = nil;
165     [super dealloc];
168 - (MMWindowController *)windowController
170     return windowController;
173 - (NSDictionary *)vimState
175     return vimState;
178 - (NSMenu *)mainMenu
180     return mainMenu;
183 - (void)setServerName:(NSString *)name
185     if (name != serverName) {
186         [serverName release];
187         serverName = [name copy];
188     }
191 - (NSString *)serverName
193     return serverName;
196 - (int)pid
198     return pid;
201 - (void)dropFiles:(NSArray *)filenames forceOpen:(BOOL)force
203     unsigned i, numberOfFiles = [filenames count];
204     NSMutableData *data = [NSMutableData data];
206     [data appendBytes:&force length:sizeof(BOOL)];
207     [data appendBytes:&numberOfFiles length:sizeof(int)];
209     for (i = 0; i < numberOfFiles; ++i) {
210         NSString *file = [filenames objectAtIndex:i];
211         int len = [file lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
213         if (len > 0) {
214             ++len;  // include NUL as well
215             [data appendBytes:&len length:sizeof(int)];
216             [data appendBytes:[file UTF8String] length:len];
217         }
218     }
220     [self sendMessage:DropFilesMsgID data:data];
223 - (void)dropString:(NSString *)string
225     int len = [string lengthOfBytesUsingEncoding:NSUTF8StringEncoding] + 1;
226     if (len > 0) {
227         NSMutableData *data = [NSMutableData data];
229         [data appendBytes:&len length:sizeof(int)];
230         [data appendBytes:[string UTF8String] length:len];
232         [self sendMessage:DropStringMsgID data:data];
233     }
236 - (void)odbEdit:(NSArray *)filenames server:(OSType)theID path:(NSString *)path
237           token:(NSAppleEventDescriptor *)token
239     int len;
240     unsigned i, numberOfFiles = [filenames count];
241     NSMutableData *data = [NSMutableData data];
243     if (0 == numberOfFiles || 0 == theID)
244         return;
246     [data appendBytes:&theID length:sizeof(theID)];
248     if (path && [path length] > 0) {
249         len = [path lengthOfBytesUsingEncoding:NSUTF8StringEncoding] + 1;
250         [data appendBytes:&len length:sizeof(int)];
251         [data appendBytes:[path UTF8String] length:len];
252     } else {
253         len = 0;
254         [data appendBytes:&len length:sizeof(int)];
255     }
257     if (token) {
258         DescType tokenType = [token descriptorType];
259         NSData *tokenData = [token data];
260         len = [tokenData length];
262         [data appendBytes:&tokenType length:sizeof(tokenType)];
263         [data appendBytes:&len length:sizeof(int)];
264         if (len > 0)
265             [data appendBytes:[tokenData bytes] length:len];
266     } else {
267         DescType tokenType = 0;
268         len = 0;
269         [data appendBytes:&tokenType length:sizeof(tokenType)];
270         [data appendBytes:&len length:sizeof(int)];
271     }
273     [data appendBytes:&numberOfFiles length:sizeof(int)];
275     for (i = 0; i < numberOfFiles; ++i) {
276         NSString *file = [filenames objectAtIndex:i];
277         len = [file lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
279         if (len > 0) {
280             ++len;  // include NUL as well
281             [data appendBytes:&len length:sizeof(unsigned)];
282             [data appendBytes:[file UTF8String] length:len];
283         }
284     }
286     [self sendMessage:ODBEditMsgID data:data];
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)backendProxy
372     return backendProxy;
375 - (void)cleanup
377     //NSLog(@"%@ %s", [self className], _cmd);
378     if (!isInitialized) return;
380     isInitialized = NO;
381     [toolbar setDelegate:nil];
382     [[NSNotificationCenter defaultCenter] removeObserver:self];
383     [windowController cleanup];
386 - (oneway void)showSavePanelWithAttributes:(in bycopy NSDictionary *)attr
388     if (!isInitialized) return;
390     BOOL inDefaultMode = [[[NSRunLoop currentRunLoop] currentMode]
391                                         isEqual:NSDefaultRunLoopMode];
392     if (!inDefaultMode) {
393         // Delay call until run loop is in default mode.
394         [self performSelectorOnMainThread:
395                                         @selector(showSavePanelWithAttributes:)
396                                withObject:attr
397                             waitUntilDone:NO
398                                     modes:[NSArray arrayWithObject:
399                                            NSDefaultRunLoopMode]];
400         return;
401     }
403     NSString *dir = [attr objectForKey:@"dir"];
404     BOOL saving = [[attr objectForKey:@"saving"] boolValue];
406     if (!dir) {
407         // 'dir == nil' means: set dir to the pwd of the Vim process, or let
408         // open dialog decide (depending on the below user default).
409         BOOL trackPwd = [[NSUserDefaults standardUserDefaults]
410                 boolForKey:MMDialogsTrackPwdKey];
411         if (trackPwd)
412             dir = [vimState objectForKey:@"pwd"];
413     }
415     if (saving) {
416         [[NSSavePanel savePanel] beginSheetForDirectory:dir file:nil
417                 modalForWindow:[windowController window]
418                  modalDelegate:self
419                 didEndSelector:@selector(savePanelDidEnd:code:context:)
420                    contextInfo:NULL];
421     } else {
422         NSOpenPanel *panel = [NSOpenPanel openPanel];
423         [panel setAllowsMultipleSelection:NO];
424         [panel setAccessoryView:openPanelAccessoryView()];
426         [panel beginSheetForDirectory:dir file:nil types:nil
427                 modalForWindow:[windowController window]
428                  modalDelegate:self
429                 didEndSelector:@selector(savePanelDidEnd:code:context:)
430                    contextInfo:NULL];
431     }
434 - (oneway void)presentDialogWithAttributes:(in bycopy NSDictionary *)attr
436     if (!isInitialized) return;
438     BOOL inDefaultMode = [[[NSRunLoop currentRunLoop] currentMode]
439                                         isEqual:NSDefaultRunLoopMode];
440     if (!inDefaultMode) {
441         // Delay call until run loop is in default mode.
442         [self performSelectorOnMainThread:@selector(presentDialogWithStyle:)
443                                withObject:attr
444                             waitUntilDone:NO
445                                     modes:[NSArray arrayWithObject:
446                                            NSDefaultRunLoopMode]];
447         return;
448     }
450     NSArray *buttonTitles = [attr objectForKey:@"buttonTitles"];
451     if (!(buttonTitles && [buttonTitles count])) return;
453     int style = [[attr objectForKey:@"alertStyle"] intValue];
454     NSString *message = [attr objectForKey:@"messageText"];
455     NSString *text = [attr objectForKey:@"informativeText"];
456     NSString *textFieldString = [attr objectForKey:@"textFieldString"];
457     MMAlert *alert = [[MMAlert alloc] init];
459     // NOTE! This has to be done before setting the informative text.
460     if (textFieldString)
461         [alert setTextFieldString:textFieldString];
463     [alert setAlertStyle:style];
465     if (message) {
466         [alert setMessageText:message];
467     } else {
468         // If no message text is specified 'Alert' is used, which we don't
469         // want, so set an empty string as message text.
470         [alert setMessageText:@""];
471     }
473     if (text) {
474         [alert setInformativeText:text];
475     } else if (textFieldString) {
476         // Make sure there is always room for the input text field.
477         [alert setInformativeText:@""];
478     }
480     unsigned i, count = [buttonTitles count];
481     for (i = 0; i < count; ++i) {
482         NSString *title = [buttonTitles objectAtIndex:i];
483         // NOTE: The title of the button may contain the character '&' to
484         // indicate that the following letter should be the key equivalent
485         // associated with the button.  Extract this letter and lowercase it.
486         NSString *keyEquivalent = nil;
487         NSRange hotkeyRange = [title rangeOfString:@"&"];
488         if (NSNotFound != hotkeyRange.location) {
489             if ([title length] > NSMaxRange(hotkeyRange)) {
490                 NSRange keyEquivRange = NSMakeRange(hotkeyRange.location+1, 1);
491                 keyEquivalent = [[title substringWithRange:keyEquivRange]
492                     lowercaseString];
493             }
495             NSMutableString *string = [NSMutableString stringWithString:title];
496             [string deleteCharactersInRange:hotkeyRange];
497             title = string;
498         }
500         [alert addButtonWithTitle:title];
502         // Set key equivalent for the button, but only if NSAlert hasn't
503         // already done so.  (Check the documentation for
504         // - [NSAlert addButtonWithTitle:] to see what key equivalents are
505         // automatically assigned.)
506         NSButton *btn = [[alert buttons] lastObject];
507         if ([[btn keyEquivalent] length] == 0 && keyEquivalent) {
508             [btn setKeyEquivalent:keyEquivalent];
509         }
510     }
512     [alert beginSheetModalForWindow:[windowController window]
513                       modalDelegate:self
514                      didEndSelector:@selector(alertDidEnd:code:context:)
515                         contextInfo:NULL];
517     [alert release];
520 - (oneway void)processCommandQueue:(in bycopy NSArray *)queue
522     if (!isInitialized) return;
524     if (inProcessCommandQueue) {
525         // NOTE!  If a synchronous DO call is made during
526         // doProcessCommandQueue: below it may happen that this method is
527         // called a second time while the synchronous message is waiting for a
528         // reply (could also happen if doProcessCommandQueue: enters a modal
529         // loop, see comment below).  Since this method cannot be considered
530         // reentrant, we queue the input and return immediately.
531         //
532         // If doProcessCommandQueue: enters a modal loop (happens e.g. on
533         // ShowPopupMenuMsgID) then the receiveQueue could grow to become
534         // arbitrarily large because DO calls still get processed.  To avoid
535         // this we set a cap on the size of the queue and simply clear it if it
536         // becomes too large.  (That is messages will be dropped and hence Vim
537         // and MacVim will at least temporarily be out of sync.)
538         if ([receiveQueue count] >= MMReceiveQueueCap)
539             [receiveQueue removeAllObjects];
541         [receiveQueue addObject:queue];
542         return;
543     }
545     inProcessCommandQueue = YES;
546     [self doProcessCommandQueue:queue];
548     int i;
549     for (i = 0; i < [receiveQueue count]; ++i) {
550         // Note that doProcessCommandQueue: may cause the receiveQueue to grow
551         // or get cleared (due to cap being hit).  Make sure to retain the item
552         // to process or it may get released from under us.
553         NSArray *q = [[receiveQueue objectAtIndex:i] retain];
554         [self doProcessCommandQueue:q];
555         [q release];
556     }
558     // We assume that the remaining calls make no synchronous DO calls.  If
559     // that did happen anyway, the command queue could get processed out of
560     // order.
562     if ([sendQueue count] > 0) {
563         @try {
564             [backendProxy processInputAndData:sendQueue];
565         }
566         @catch (NSException *e) {
567             // Connection timed out, just ignore this.
568             //NSLog(@"WARNING! Connection timed out in %s", _cmd);
569         }
571         [sendQueue removeAllObjects];
572     }
574     [windowController processCommandQueueDidFinish];
575     [receiveQueue removeAllObjects];
576     inProcessCommandQueue = NO;
579 - (NSToolbarItem *)toolbar:(NSToolbar *)theToolbar
580     itemForItemIdentifier:(NSString *)itemId
581     willBeInsertedIntoToolbar:(BOOL)flag
583     NSToolbarItem *item = [toolbarItemDict objectForKey:itemId];
584     if (!item) {
585         NSLog(@"WARNING:  No toolbar item with id '%@'", itemId);
586     }
588     return item;
591 - (NSArray *)toolbarAllowedItemIdentifiers:(NSToolbar *)theToolbar
593     return nil;
596 - (NSArray *)toolbarDefaultItemIdentifiers:(NSToolbar *)theToolbar
598     return nil;
601 @end // MMVimController
605 @implementation MMVimController (Private)
607 - (void)doProcessCommandQueue:(NSArray *)queue
609     NSMutableArray *delayQueue = nil;
611     @try {
612         unsigned i, count = [queue count];
613         if (count % 2) {
614             NSLog(@"WARNING: Uneven number of components (%d) in command "
615                     "queue.  Skipping...", count);
616             return;
617         }
619         //NSLog(@"======== %s BEGIN ========", _cmd);
620         for (i = 0; i < count; i += 2) {
621             NSData *value = [queue objectAtIndex:i];
622             NSData *data = [queue objectAtIndex:i+1];
624             int msgid = *((int*)[value bytes]);
625             //NSLog(@"%s%s", _cmd, MessageStrings[msgid]);
627             BOOL inDefaultMode = [[[NSRunLoop currentRunLoop] currentMode]
628                                                 isEqual:NSDefaultRunLoopMode];
629             if (!inDefaultMode && isUnsafeMessage(msgid)) {
630                 // NOTE: Because we listen to DO messages in 'event tracking'
631                 // mode we have to take extra care when doing things like
632                 // releasing view items (and other Cocoa objects).  Messages
633                 // that may be potentially "unsafe" are delayed until the run
634                 // loop is back to default mode at which time they are safe to
635                 // call again.
636                 //   A problem with this approach is that it is hard to
637                 // classify which messages are unsafe.  As a rule of thumb, if
638                 // a message may release an object used by the Cocoa framework
639                 // (e.g. views) then the message should be considered unsafe.
640                 //   Delaying messages may have undesired side-effects since it
641                 // means that messages may not be processed in the order Vim
642                 // sent them, so beware.
643                 if (!delayQueue)
644                     delayQueue = [NSMutableArray array];
646                 //NSLog(@"Adding unsafe message '%s' to delay queue (mode=%@)",
647                 //        MessageStrings[msgid],
648                 //        [[NSRunLoop currentRunLoop] currentMode]);
649                 [delayQueue addObject:value];
650                 [delayQueue addObject:data];
651             } else {
652                 [self handleMessage:msgid data:data];
653             }
654         }
655         //NSLog(@"======== %s  END  ========", _cmd);
656     }
657     @catch (NSException *e) {
658         NSLog(@"Exception caught whilst processing command queue: %@", e);
659     }
661     if (delayQueue) {
662         //NSLog(@"    Flushing delay queue (%d items)", [delayQueue count]/2);
663         [self performSelectorOnMainThread:@selector(processCommandQueue:)
664                                withObject:delayQueue
665                             waitUntilDone:NO
666                                     modes:[NSArray arrayWithObject:
667                                            NSDefaultRunLoopMode]];
668     }
671 - (void)handleMessage:(int)msgid data:(NSData *)data
673     //if (msgid != AddMenuMsgID && msgid != AddMenuItemMsgID &&
674     //        msgid != EnableMenuItemMsgID)
675     //    NSLog(@"%@ %s%s", [self className], _cmd, MessageStrings[msgid]);
677     if (OpenVimWindowMsgID == msgid) {
678         [windowController openWindow];
679     } else if (BatchDrawMsgID == msgid) {
680         [[[windowController vimView] textView] performBatchDrawWithData:data];
681     } else if (SelectTabMsgID == msgid) {
682 #if 0   // NOTE: Tab selection is done inside updateTabsWithData:.
683         const void *bytes = [data bytes];
684         int idx = *((int*)bytes);
685         //NSLog(@"Selecting tab with index %d", idx);
686         [windowController selectTabWithIndex:idx];
687 #endif
688     } else if (UpdateTabBarMsgID == msgid) {
689         [windowController updateTabsWithData:data];
690     } else if (ShowTabBarMsgID == msgid) {
691         [windowController showTabBar:YES];
692     } else if (HideTabBarMsgID == msgid) {
693         [windowController showTabBar:NO];
694     } else if (SetTextDimensionsMsgID == msgid || LiveResizeMsgID == msgid) {
695         const void *bytes = [data bytes];
696         int rows = *((int*)bytes);  bytes += sizeof(int);
697         int cols = *((int*)bytes);  bytes += sizeof(int);
699         [windowController setTextDimensionsWithRows:rows columns:cols
700                                                live:(LiveResizeMsgID==msgid)];
701     } else if (SetWindowTitleMsgID == msgid) {
702         const void *bytes = [data bytes];
703         int len = *((int*)bytes);  bytes += sizeof(int);
705         NSString *string = [[NSString alloc] initWithBytes:(void*)bytes
706                 length:len encoding:NSUTF8StringEncoding];
708         // While in live resize the window title displays the dimensions of the
709         // window so don't clobber this with a spurious "set title" message
710         // from Vim.
711         if (![[windowController vimView] inLiveResize])
712             [windowController setTitle:string];
714         [string release];
715     } else if (SetDocumentFilenameMsgID == msgid) {
716         const void *bytes = [data bytes];
717         int len = *((int*)bytes);  bytes += sizeof(int);
719         if (len > 0) {
720             NSString *filename = [[NSString alloc] initWithBytes:(void*)bytes
721                     length:len encoding:NSUTF8StringEncoding];
723             [windowController setDocumentFilename:filename];
725             [filename release];
726         } else {
727             [windowController setDocumentFilename:@""];
728         }
729     } else if (AddMenuMsgID == msgid) {
730         NSDictionary *attrs = [NSDictionary dictionaryWithData:data];
731         [self addMenuWithDescriptor:[attrs objectForKey:@"descriptor"]
732                 atIndex:[[attrs objectForKey:@"index"] intValue]];
733     } else if (AddMenuItemMsgID == msgid) {
734         NSDictionary *attrs = [NSDictionary dictionaryWithData:data];
735         [self addMenuItemWithDescriptor:[attrs objectForKey:@"descriptor"]
736                       atIndex:[[attrs objectForKey:@"index"] intValue]
737                           tip:[attrs objectForKey:@"tip"]
738                          icon:[attrs objectForKey:@"icon"]
739                 keyEquivalent:[attrs objectForKey:@"keyEquivalent"]
740                  modifierMask:[[attrs objectForKey:@"modifierMask"] intValue]
741                        action:[attrs objectForKey:@"action"]
742                   isAlternate:[[attrs objectForKey:@"isAlternate"] boolValue]];
743     } else if (RemoveMenuItemMsgID == msgid) {
744         NSDictionary *attrs = [NSDictionary dictionaryWithData:data];
745         [self removeMenuItemWithDescriptor:[attrs objectForKey:@"descriptor"]];
746     } else if (EnableMenuItemMsgID == msgid) {
747         NSDictionary *attrs = [NSDictionary dictionaryWithData:data];
748         [self enableMenuItemWithDescriptor:[attrs objectForKey:@"descriptor"]
749                 state:[[attrs objectForKey:@"enable"] boolValue]];
750     } else if (ShowToolbarMsgID == msgid) {
751         const void *bytes = [data bytes];
752         int enable = *((int*)bytes);  bytes += sizeof(int);
753         int flags = *((int*)bytes);  bytes += sizeof(int);
755         int mode = NSToolbarDisplayModeDefault;
756         if (flags & ToolbarLabelFlag) {
757             mode = flags & ToolbarIconFlag ? NSToolbarDisplayModeIconAndLabel
758                     : NSToolbarDisplayModeLabelOnly;
759         } else if (flags & ToolbarIconFlag) {
760             mode = NSToolbarDisplayModeIconOnly;
761         }
763         int size = flags & ToolbarSizeRegularFlag ? NSToolbarSizeModeRegular
764                 : NSToolbarSizeModeSmall;
766         [windowController showToolbar:enable size:size mode:mode];
767     } else if (CreateScrollbarMsgID == msgid) {
768         const void *bytes = [data bytes];
769         long ident = *((long*)bytes);  bytes += sizeof(long);
770         int type = *((int*)bytes);  bytes += sizeof(int);
772         [windowController createScrollbarWithIdentifier:ident type:type];
773     } else if (DestroyScrollbarMsgID == msgid) {
774         const void *bytes = [data bytes];
775         long ident = *((long*)bytes);  bytes += sizeof(long);
777         [windowController destroyScrollbarWithIdentifier:ident];
778     } else if (ShowScrollbarMsgID == msgid) {
779         const void *bytes = [data bytes];
780         long ident = *((long*)bytes);  bytes += sizeof(long);
781         int visible = *((int*)bytes);  bytes += sizeof(int);
783         [windowController showScrollbarWithIdentifier:ident state:visible];
784     } else if (SetScrollbarPositionMsgID == msgid) {
785         const void *bytes = [data bytes];
786         long ident = *((long*)bytes);  bytes += sizeof(long);
787         int pos = *((int*)bytes);  bytes += sizeof(int);
788         int len = *((int*)bytes);  bytes += sizeof(int);
790         [windowController setScrollbarPosition:pos length:len
791                                     identifier:ident];
792     } else if (SetScrollbarThumbMsgID == msgid) {
793         const void *bytes = [data bytes];
794         long ident = *((long*)bytes);  bytes += sizeof(long);
795         float val = *((float*)bytes);  bytes += sizeof(float);
796         float prop = *((float*)bytes);  bytes += sizeof(float);
798         [windowController setScrollbarThumbValue:val proportion:prop
799                                       identifier:ident];
800     } else if (SetFontMsgID == msgid) {
801         const void *bytes = [data bytes];
802         float size = *((float*)bytes);  bytes += sizeof(float);
803         int len = *((int*)bytes);  bytes += sizeof(int);
804         NSString *name = [[NSString alloc]
805                 initWithBytes:(void*)bytes length:len
806                      encoding:NSUTF8StringEncoding];
807         NSFont *font = [NSFont fontWithName:name size:size];
809         if (font)
810             [windowController setFont:font];
812         [name release];
813     } else if (SetWideFontMsgID == msgid) {
814         const void *bytes = [data bytes];
815         float size = *((float*)bytes);  bytes += sizeof(float);
816         int len = *((int*)bytes);  bytes += sizeof(int);
817         if (len > 0) {
818             NSString *name = [[NSString alloc]
819                     initWithBytes:(void*)bytes length:len
820                          encoding:NSUTF8StringEncoding];
821             NSFont *font = [NSFont fontWithName:name size:size];
822             [windowController setWideFont:font];
824             [name release];
825         } else {
826             [windowController setWideFont:nil];
827         }
828     } else if (SetDefaultColorsMsgID == msgid) {
829         const void *bytes = [data bytes];
830         unsigned bg = *((unsigned*)bytes);  bytes += sizeof(unsigned);
831         unsigned fg = *((unsigned*)bytes);  bytes += sizeof(unsigned);
832         NSColor *back = [NSColor colorWithArgbInt:bg];
833         NSColor *fore = [NSColor colorWithRgbInt:fg];
835         [windowController setDefaultColorsBackground:back foreground:fore];
836     } else if (ExecuteActionMsgID == msgid) {
837         const void *bytes = [data bytes];
838         int len = *((int*)bytes);  bytes += sizeof(int);
839         NSString *actionName = [[NSString alloc]
840                 initWithBytes:(void*)bytes length:len
841                      encoding:NSUTF8StringEncoding];
843         SEL sel = NSSelectorFromString(actionName);
844         [NSApp sendAction:sel to:nil from:self];
846         [actionName release];
847     } else if (ShowPopupMenuMsgID == msgid) {
848         NSDictionary *attrs = [NSDictionary dictionaryWithData:data];
850         // The popup menu enters a modal loop so delay this call so that we
851         // don't block inside processCommandQueue:.
852         [self performSelectorOnMainThread:@selector(popupMenuWithAttributes:)
853                              withObject:attrs
854                           waitUntilDone:NO
855                                   modes:[NSArray arrayWithObject:
856                                          NSDefaultRunLoopMode]];
857     } else if (SetMouseShapeMsgID == msgid) {
858         const void *bytes = [data bytes];
859         int shape = *((int*)bytes);  bytes += sizeof(int);
861         [windowController setMouseShape:shape];
862     } else if (AdjustLinespaceMsgID == msgid) {
863         const void *bytes = [data bytes];
864         int linespace = *((int*)bytes);  bytes += sizeof(int);
866         [windowController adjustLinespace:linespace];
867     } else if (ActivateMsgID == msgid) {
868         //NSLog(@"ActivateMsgID");
869         [NSApp activateIgnoringOtherApps:YES];
870         [[windowController window] makeKeyAndOrderFront:self];
871     } else if (SetServerNameMsgID == msgid) {
872         NSString *name = [[NSString alloc] initWithData:data
873                                                encoding:NSUTF8StringEncoding];
874         [self setServerName:name];
875         [name release];
876     } else if (EnterFullscreenMsgID == msgid) {
877         const void *bytes = [data bytes];
878         int fuoptions = *((int*)bytes); bytes += sizeof(int);
879         int bg = *((int*)bytes);
880         NSColor *back = [NSColor colorWithArgbInt:bg];
882         [windowController enterFullscreen:fuoptions backgroundColor:back];
883     } else if (LeaveFullscreenMsgID == msgid) {
884         [windowController leaveFullscreen];
885     } else if (BuffersNotModifiedMsgID == msgid) {
886         [windowController setBuffersModified:NO];
887     } else if (BuffersModifiedMsgID == msgid) {
888         [windowController setBuffersModified:YES];
889     } else if (SetPreEditPositionMsgID == msgid) {
890         const int *dim = (const int*)[data bytes];
891         [[[windowController vimView] textView] setPreEditRow:dim[0]
892                                                       column:dim[1]];
893     } else if (EnableAntialiasMsgID == msgid) {
894         [[[windowController vimView] textView] setAntialias:YES];
895     } else if (DisableAntialiasMsgID == msgid) {
896         [[[windowController vimView] textView] setAntialias:NO];
897     } else if (SetVimStateMsgID == msgid) {
898         NSDictionary *dict = [NSDictionary dictionaryWithData:data];
899         if (dict) {
900             [vimState release];
901             vimState = [dict retain];
902         }
903     // IMPORTANT: When adding a new message, make sure to update
904     // isUnsafeMessage() if necessary!
905     } else {
906         NSLog(@"WARNING: Unknown message received (msgid=%d)", msgid);
907     }
910 - (void)savePanelDidEnd:(NSSavePanel *)panel code:(int)code
911                 context:(void *)context
913     NSString *path = (code == NSOKButton) ? [panel filename] : nil;
915     // NOTE! setDialogReturn: is a synchronous call so set a proper timeout to
916     // avoid waiting forever for it to finish.  We make this a synchronous call
917     // so that we can be fairly certain that Vim doesn't think the dialog box
918     // is still showing when MacVim has in fact already dismissed it.
919     NSConnection *conn = [backendProxy connectionForProxy];
920     NSTimeInterval oldTimeout = [conn requestTimeout];
921     [conn setRequestTimeout:MMSetDialogReturnTimeout];
923     @try {
924         [backendProxy setDialogReturn:path];
926         // Add file to the "Recent Files" menu (this ensures that files that
927         // are opened/saved from a :browse command are added to this menu).
928         if (path)
929             [[NSDocumentController sharedDocumentController]
930                     noteNewRecentFilePath:path];
931     }
932     @catch (NSException *e) {
933         NSLog(@"Exception caught in %s %@", _cmd, e);
934     }
935     @finally {
936         [conn setRequestTimeout:oldTimeout];
937     }
940 - (void)alertDidEnd:(MMAlert *)alert code:(int)code context:(void *)context
942     NSArray *ret = nil;
944     code = code - NSAlertFirstButtonReturn + 1;
946     if ([alert isKindOfClass:[MMAlert class]] && [alert textField]) {
947         ret = [NSArray arrayWithObjects:[NSNumber numberWithInt:code],
948             [[alert textField] stringValue], nil];
949     } else {
950         ret = [NSArray arrayWithObject:[NSNumber numberWithInt:code]];
951     }
953     @try {
954         [backendProxy setDialogReturn:ret];
955     }
956     @catch (NSException *e) {
957         NSLog(@"Exception caught in %s %@", _cmd, e);
958     }
961 - (NSMenuItem *)menuItemForDescriptor:(NSArray *)desc
963     if (!(desc && [desc count] > 0)) return nil;
965     NSString *rootName = [desc objectAtIndex:0];
966     NSArray *rootItems = [rootName hasPrefix:@"PopUp"] ? popupMenuItems
967                                                        : [mainMenu itemArray];
969     NSMenuItem *item = nil;
970     int i, count = [rootItems count];
971     for (i = 0; i < count; ++i) {
972         item = [rootItems objectAtIndex:i];
973         if ([[item title] isEqual:rootName])
974             break;
975     }
977     if (i == count) return nil;
979     count = [desc count];
980     for (i = 1; i < count; ++i) {
981         item = [[item submenu] itemWithTitle:[desc objectAtIndex:i]];
982         if (!item) return nil;
983     }
985     return item;
988 - (NSMenu *)parentMenuForDescriptor:(NSArray *)desc
990     if (!(desc && [desc count] > 0)) return nil;
992     NSString *rootName = [desc objectAtIndex:0];
993     NSArray *rootItems = [rootName hasPrefix:@"PopUp"] ? popupMenuItems
994                                                        : [mainMenu itemArray];
996     NSMenu *menu = nil;
997     int i, count = [rootItems count];
998     for (i = 0; i < count; ++i) {
999         NSMenuItem *item = [rootItems objectAtIndex:i];
1000         if ([[item title] isEqual:rootName]) {
1001             menu = [item submenu];
1002             break;
1003         }
1004     }
1006     if (!menu) return nil;
1008     count = [desc count] - 1;
1009     for (i = 1; i < count; ++i) {
1010         NSMenuItem *item = [menu itemWithTitle:[desc objectAtIndex:i]];
1011         menu = [item submenu];
1012         if (!menu) return nil;
1013     }
1015     return menu;
1018 - (NSMenu *)topLevelMenuForTitle:(NSString *)title
1020     // Search only the top-level menus.
1022     unsigned i, count = [popupMenuItems count];
1023     for (i = 0; i < count; ++i) {
1024         NSMenuItem *item = [popupMenuItems objectAtIndex:i];
1025         if ([title isEqual:[item title]])
1026             return [item submenu];
1027     }
1029     count = [mainMenu numberOfItems];
1030     for (i = 0; i < count; ++i) {
1031         NSMenuItem *item = [mainMenu itemAtIndex:i];
1032         if ([title isEqual:[item title]])
1033             return [item submenu];
1034     }
1036     return nil;
1039 - (void)addMenuWithDescriptor:(NSArray *)desc atIndex:(int)idx
1041     if (!(desc && [desc count] > 0 && idx >= 0)) return;
1043     NSString *rootName = [desc objectAtIndex:0];
1044     if ([rootName isEqual:@"ToolBar"]) {
1045         // The toolbar only has one menu, we take this as a hint to create a
1046         // toolbar, then we return.
1047         if (!toolbar) {
1048             // NOTE! Each toolbar must have a unique identifier, else each
1049             // window will have the same toolbar.
1050             NSString *ident = [NSString stringWithFormat:@"%d", (int)self];
1051             toolbar = [[NSToolbar alloc] initWithIdentifier:ident];
1053             [toolbar setShowsBaselineSeparator:NO];
1054             [toolbar setDelegate:self];
1055             [toolbar setDisplayMode:NSToolbarDisplayModeIconOnly];
1056             [toolbar setSizeMode:NSToolbarSizeModeSmall];
1058             [windowController setToolbar:toolbar];
1059         }
1061         return;
1062     }
1064     // This is either a main menu item or a popup menu item.
1065     NSString *title = [desc lastObject];
1066     NSMenuItem *item = [[NSMenuItem alloc] init];
1067     NSMenu *menu = [[NSMenu alloc] initWithTitle:title];
1069     [item setTitle:title];
1070     [item setSubmenu:menu];
1072     NSMenu *parent = [self parentMenuForDescriptor:desc];
1073     if (!parent && [rootName hasPrefix:@"PopUp"]) {
1074         if ([popupMenuItems count] <= idx) {
1075             [popupMenuItems addObject:item];
1076         } else {
1077             [popupMenuItems insertObject:item atIndex:idx];
1078         }
1079     } else {
1080         // If descriptor has no parent and its not a popup (or toolbar) menu,
1081         // then it must belong to main menu.
1082         if (!parent) parent = mainMenu;
1084         if ([parent numberOfItems] <= idx) {
1085             [parent addItem:item];
1086         } else {
1087             [parent insertItem:item atIndex:idx];
1088         }
1089     }
1091     [item release];
1092     [menu release];
1095 - (void)addMenuItemWithDescriptor:(NSArray *)desc
1096                           atIndex:(int)idx
1097                               tip:(NSString *)tip
1098                              icon:(NSString *)icon
1099                     keyEquivalent:(NSString *)keyEquivalent
1100                      modifierMask:(int)modifierMask
1101                            action:(NSString *)action
1102                       isAlternate:(BOOL)isAlternate
1104     if (!(desc && [desc count] > 1 && idx >= 0)) return;
1106     NSString *title = [desc lastObject];
1107     NSString *rootName = [desc objectAtIndex:0];
1109     if ([rootName isEqual:@"ToolBar"]) {
1110         if (toolbar && [desc count] == 2)
1111             [self addToolbarItemWithLabel:title tip:tip icon:icon atIndex:idx];
1112         return;
1113     }
1115     NSMenu *parent = [self parentMenuForDescriptor:desc];
1116     if (!parent) {
1117         NSLog(@"WARNING: Menu item '%@' has no parent",
1118                 [desc componentsJoinedByString:@"->"]);
1119         return;
1120     }
1122     NSMenuItem *item = nil;
1123     if (0 == [title length]
1124             || ([title hasPrefix:@"-"] && [title hasSuffix:@"-"])) {
1125         item = [NSMenuItem separatorItem];
1126         [item setTitle:title];
1127     } else {
1128         item = [[[NSMenuItem alloc] init] autorelease];
1129         [item setTitle:title];
1131         // Note: It is possible to set the action to a message that "doesn't
1132         // exist" without problems.  We take advantage of this when adding
1133         // "dummy items" e.g. when dealing with the "Recent Files" menu (in
1134         // which case a recentFilesDummy: action is set, although it is never
1135         // used).
1136         if ([action length] > 0)
1137             [item setAction:NSSelectorFromString(action)];
1138         else
1139             [item setAction:@selector(vimMenuItemAction:)];
1140         if ([tip length] > 0) [item setToolTip:tip];
1141         if ([keyEquivalent length] > 0) {
1142             [item setKeyEquivalent:keyEquivalent];
1143             [item setKeyEquivalentModifierMask:modifierMask];
1144         }
1145         [item setAlternate:isAlternate];
1147         // The tag is used to indicate whether Vim thinks a menu item should be
1148         // enabled or disabled.  By default Vim thinks menu items are enabled.
1149         [item setTag:1];
1150     }
1152     if ([parent numberOfItems] <= idx) {
1153         [parent addItem:item];
1154     } else {
1155         [parent insertItem:item atIndex:idx];
1156     }
1159 - (void)removeMenuItemWithDescriptor:(NSArray *)desc
1161     if (!(desc && [desc count] > 0)) return;
1163     NSString *title = [desc lastObject];
1164     NSString *rootName = [desc objectAtIndex:0];
1165     if ([rootName isEqual:@"ToolBar"]) {
1166         if (toolbar) {
1167             // Only remove toolbar items, never actually remove the toolbar
1168             // itself or strange things may happen.
1169             if ([desc count] == 2) {
1170                 int idx = [toolbar indexOfItemWithItemIdentifier:title];
1171                 if (idx != NSNotFound)
1172                     [toolbar removeItemAtIndex:idx];
1173             }
1174         }
1175         return;
1176     }
1178     NSMenuItem *item = [self menuItemForDescriptor:desc];
1179     if (!item) {
1180         NSLog(@"Failed to remove menu item, descriptor not found: %@",
1181                 [desc componentsJoinedByString:@"->"]);
1182         return;
1183     }
1185     [item retain];
1187     if ([item menu] == [NSApp mainMenu] || ![item menu]) {
1188         // NOTE: To be on the safe side we try to remove the item from
1189         // both arrays (it is ok to call removeObject: even if an array
1190         // does not contain the object to remove).
1191         [popupMenuItems removeObject:item];
1192     }
1194     if ([item menu])
1195         [[item menu] removeItem:item];
1197     [item release];
1200 - (void)enableMenuItemWithDescriptor:(NSArray *)desc state:(BOOL)on
1202     if (!(desc && [desc count] > 0)) return;
1204     /*NSLog(@"%sable item %@", on ? "En" : "Dis",
1205             [desc componentsJoinedByString:@"->"]);*/
1207     NSString *rootName = [desc objectAtIndex:0];
1208     if ([rootName isEqual:@"ToolBar"]) {
1209         if (toolbar && [desc count] == 2) {
1210             NSString *title = [desc lastObject];
1211             [[toolbar itemWithItemIdentifier:title] setEnabled:on];
1212         }
1213     } else {
1214         // Use tag to set whether item is enabled or disabled instead of
1215         // calling setEnabled:.  This way the menus can autoenable themselves
1216         // but at the same time Vim can set if a menu is enabled whenever it
1217         // wants to.
1218         [[self menuItemForDescriptor:desc] setTag:on];
1219     }
1222 - (void)addToolbarItemToDictionaryWithLabel:(NSString *)title
1223                                     toolTip:(NSString *)tip
1224                                        icon:(NSString *)icon
1226     // If the item corresponds to a separator then do nothing, since it is
1227     // already defined by Cocoa.
1228     if (!title || [title isEqual:NSToolbarSeparatorItemIdentifier]
1229                || [title isEqual:NSToolbarSpaceItemIdentifier]
1230                || [title isEqual:NSToolbarFlexibleSpaceItemIdentifier])
1231         return;
1233     NSToolbarItem *item = [[NSToolbarItem alloc] initWithItemIdentifier:title];
1234     [item setLabel:title];
1235     [item setToolTip:tip];
1236     [item setAction:@selector(vimToolbarItemAction:)];
1237     [item setAutovalidates:NO];
1239     NSImage *img = [NSImage imageNamed:icon];
1240     if (!img)
1241         img = [[[NSImage alloc] initByReferencingFile:icon] autorelease];
1242     if (!img) {
1243         NSLog(@"WARNING: Could not find image with name '%@' to use as toolbar"
1244                " image for identifier '%@';"
1245                " using default toolbar icon '%@' instead.",
1246                icon, title, MMDefaultToolbarImageName);
1248         img = [NSImage imageNamed:MMDefaultToolbarImageName];
1249     }
1251     [item setImage:img];
1253     [toolbarItemDict setObject:item forKey:title];
1255     [item release];
1258 - (void)addToolbarItemWithLabel:(NSString *)label
1259                             tip:(NSString *)tip
1260                            icon:(NSString *)icon
1261                         atIndex:(int)idx
1263     if (!toolbar) return;
1265     // Check for separator items.
1266     if (!label) {
1267         label = NSToolbarSeparatorItemIdentifier;
1268     } else if ([label length] >= 2 && [label hasPrefix:@"-"]
1269                                    && [label hasSuffix:@"-"]) {
1270         // The label begins and ends with '-'; decided which kind of separator
1271         // item it is by looking at the prefix.
1272         if ([label hasPrefix:@"-space"]) {
1273             label = NSToolbarSpaceItemIdentifier;
1274         } else if ([label hasPrefix:@"-flexspace"]) {
1275             label = NSToolbarFlexibleSpaceItemIdentifier;
1276         } else {
1277             label = NSToolbarSeparatorItemIdentifier;
1278         }
1279     }
1281     [self addToolbarItemToDictionaryWithLabel:label toolTip:tip icon:icon];
1283     int maxIdx = [[toolbar items] count];
1284     if (maxIdx < idx) idx = maxIdx;
1286     [toolbar insertItemWithItemIdentifier:label atIndex:idx];
1289 - (void)popupMenuWithDescriptor:(NSArray *)desc
1290                           atRow:(NSNumber *)row
1291                          column:(NSNumber *)col
1293     NSMenu *menu = [[self menuItemForDescriptor:desc] submenu];
1294     if (!menu) return;
1296     id textView = [[windowController vimView] textView];
1297     NSPoint pt;
1298     if (row && col) {
1299         // TODO: Let textView convert (row,col) to NSPoint.
1300         int r = [row intValue];
1301         int c = [col intValue];
1302         NSSize cellSize = [textView cellSize];
1303         pt = NSMakePoint((c+1)*cellSize.width, (r+1)*cellSize.height);
1304         pt = [textView convertPoint:pt toView:nil];
1305     } else {
1306         pt = [[windowController window] mouseLocationOutsideOfEventStream];
1307     }
1309     NSEvent *event = [NSEvent mouseEventWithType:NSRightMouseDown
1310                            location:pt
1311                       modifierFlags:0
1312                           timestamp:0
1313                        windowNumber:[[windowController window] windowNumber]
1314                             context:nil
1315                         eventNumber:0
1316                          clickCount:0
1317                            pressure:1.0];
1319     [NSMenu popUpContextMenu:menu withEvent:event forView:textView];
1322 - (void)popupMenuWithAttributes:(NSDictionary *)attrs
1324     if (!attrs) return;
1326     [self popupMenuWithDescriptor:[attrs objectForKey:@"descriptor"]
1327                             atRow:[attrs objectForKey:@"row"]
1328                            column:[attrs objectForKey:@"column"]];
1331 - (void)connectionDidDie:(NSNotification *)notification
1333     //NSLog(@"%@ %s%@", [self className], _cmd, notification);
1335     // NOTE!  This notification can arrive at pretty much anytime, e.g. while
1336     // the run loop is the 'event tracking' mode.  This means that Cocoa may
1337     // well be in the middle of processing some message while this message is
1338     // received.  If we were to remove the vim controller straight away we may
1339     // free objects that Cocoa is currently using (e.g. view objects).  The
1340     // following call ensures that the vim controller is not released until the
1341     // run loop is back in the 'default' mode.
1342     [[MMAppController sharedInstance]
1343             performSelectorOnMainThread:@selector(removeVimController:)
1344                              withObject:self
1345                           waitUntilDone:NO
1346                                   modes:[NSArray arrayWithObject:
1347                                          NSDefaultRunLoopMode]];
1350 - (NSString *)description
1352     return [NSString stringWithFormat:@"%@ : isInitialized=%d inProcessCommandQueue=%d mainMenu=%@ popupMenuItems=%@ toolbar=%@", [self className], isInitialized, inProcessCommandQueue, mainMenu, popupMenuItems, toolbar];
1355 @end // MMVimController (Private)
1360 @implementation MMAlert
1361 - (void)dealloc
1363     [textField release];  textField = nil;
1364     [super dealloc];
1367 - (void)setTextFieldString:(NSString *)textFieldString
1369     [textField release];
1370     textField = [[NSTextField alloc] init];
1371     [textField setStringValue:textFieldString];
1374 - (NSTextField *)textField
1376     return textField;
1379 - (void)setInformativeText:(NSString *)text
1381     if (textField) {
1382         // HACK! Add some space for the text field.
1383         [super setInformativeText:[text stringByAppendingString:@"\n\n\n"]];
1384     } else {
1385         [super setInformativeText:text];
1386     }
1389 - (void)beginSheetModalForWindow:(NSWindow *)window
1390                    modalDelegate:(id)delegate
1391                   didEndSelector:(SEL)didEndSelector
1392                      contextInfo:(void *)contextInfo
1394     [super beginSheetModalForWindow:window
1395                       modalDelegate:delegate
1396                      didEndSelector:didEndSelector
1397                         contextInfo:contextInfo];
1399     // HACK! Place the input text field at the bottom of the informative text
1400     // (which has been made a bit larger by adding newline characters).
1401     NSView *contentView = [[self window] contentView];
1402     NSRect rect = [contentView frame];
1403     rect.origin.y = rect.size.height;
1405     NSArray *subviews = [contentView subviews];
1406     unsigned i, count = [subviews count];
1407     for (i = 0; i < count; ++i) {
1408         NSView *view = [subviews objectAtIndex:i];
1409         if ([view isKindOfClass:[NSTextField class]]
1410                 && [view frame].origin.y < rect.origin.y) {
1411             // NOTE: The informative text field is the lowest NSTextField in
1412             // the alert dialog.
1413             rect = [view frame];
1414         }
1415     }
1417     rect.size.height = MMAlertTextFieldHeight;
1418     [textField setFrame:rect];
1419     [contentView addSubview:textField];
1420     [textField becomeFirstResponder];
1423 @end // MMAlert
1428     static BOOL
1429 isUnsafeMessage(int msgid)
1431     // Messages that may release Cocoa objects must be added to this list.  For
1432     // example, UpdateTabBarMsgID may delete NSTabViewItem objects so it goes
1433     // on this list.
1434     static int unsafeMessages[] = { // REASON MESSAGE IS ON THIS LIST:
1435         OpenVimWindowMsgID,         //   Changes lots of state
1436         UpdateTabBarMsgID,          //   May delete NSTabViewItem
1437         RemoveMenuItemMsgID,        //   Deletes NSMenuItem
1438         DestroyScrollbarMsgID,      //   Deletes NSScroller
1439         ExecuteActionMsgID,         //   Impossible to predict
1440         ShowPopupMenuMsgID,         //   Enters modal loop
1441         ActivateMsgID,              //   ?
1442         EnterFullscreenMsgID,       //   Modifies delegate of window controller
1443         LeaveFullscreenMsgID,       //   Modifies delegate of window controller
1444     };
1446     int i, count = sizeof(unsafeMessages)/sizeof(unsafeMessages[0]);
1447     for (i = 0; i < count; ++i)
1448         if (msgid == unsafeMessages[i])
1449             return YES;
1451     return NO;