Inactive status line color changed
[MacVim/jjgod.git] / MMVimController.m
blob0abdbe41684ff3d54eaeb7e0f35a189bb6eb88b5
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 #import "MMVimController.h"
12 #import "MMWindowController.h"
13 #import "MMTextView.h"
14 #import "MMAppController.h"
15 #import "MMTextStorage.h"
18 // This is taken from gui.h
19 #define DRAW_CURSOR 0x20
21 static NSString *MMDefaultToolbarImageName = @"Attention";
22 static int MMAlertTextFieldHeight = 22;
24 // NOTE: By default a message sent to the backend will be dropped if it cannot
25 // be delivered instantly; otherwise there is a possibility that MacVim will
26 // 'beachball' while waiting to deliver DO messages to an unresponsive Vim
27 // process.  This means that you cannot rely on any message sent with
28 // sendMessage: to actually reach Vim.
29 static NSTimeInterval MMBackendProxyRequestTimeout = 0;
31 #if MM_RESEND_LAST_FAILURE
32 // If a message send fails, the message will be resent after this many seconds
33 // have passed.  (No queue is kept, only the very last message is resent.)
34 static NSTimeInterval MMResendInterval = 0.5;
35 #endif
38 @interface MMAlert : NSAlert {
39     NSTextField *textField;
41 - (void)setTextFieldString:(NSString *)textFieldString;
42 - (NSTextField *)textField;
43 @end
46 @interface MMVimController (Private)
47 - (void)handleMessage:(int)msgid data:(NSData *)data;
48 - (void)performBatchDrawWithData:(NSData *)data;
49 - (void)savePanelDidEnd:(NSSavePanel *)panel code:(int)code
50                 context:(void *)context;
51 - (void)alertDidEnd:(MMAlert *)alert code:(int)code context:(void *)context;
52 - (NSMenuItem *)menuItemForTag:(int)tag;
53 - (NSMenu *)menuForTag:(int)tag;
54 - (NSMenu *)topLevelMenuForTitle:(NSString *)title;
55 - (void)addMenuWithTag:(int)tag parent:(int)parentTag title:(NSString *)title
56                atIndex:(int)idx;
57 - (void)addMenuItemWithTag:(int)tag parent:(NSMenu *)parent
58                      title:(NSString *)title tip:(NSString *)tip
59              keyEquivalent:(int)key modifiers:(int)mask
60                     action:(NSString *)action atIndex:(int)idx;
61 - (void)updateMainMenu;
62 - (NSToolbarItem *)toolbarItemForTag:(int)tag index:(int *)index;
63 - (void)addToolbarItemToDictionaryWithTag:(int)tag label:(NSString *)title
64         toolTip:(NSString *)tip icon:(NSString *)icon;
65 - (void)addToolbarItemWithTag:(int)tag label:(NSString *)label
66                           tip:(NSString *)tip icon:(NSString *)icon
67                       atIndex:(int)idx;
68 - (void)connectionDidDie:(NSNotification *)notification;
69 #if MM_RESEND_LAST_FAILURE
70 - (void)resendTimerFired:(NSTimer *)timer;
71 #endif
72 @end
76 // TODO: Move to separate file
77 @interface NSColor (MMProtocol)
78 + (NSColor *)colorWithRgbInt:(int)rgb;
79 @end
83 static NSMenuItem *findMenuItemWithTagInMenu(NSMenu *root, int tag)
85     if (root) {
86         NSMenuItem *item = [root itemWithTag:tag];
87         if (item) return item;
89         NSArray *items = [root itemArray];
90         unsigned i, count = [items count];
91         for (i = 0; i < count; ++i) {
92             item = [items objectAtIndex:i];
93             if ([item hasSubmenu]) {
94                 item = findMenuItemWithTagInMenu([item submenu], tag);
95                 if (item) return item;
96             }
97         }
98     }
100     return nil;
105 @implementation MMVimController
107 - (id)initWithBackend:(id)backend pid:(int)processIdentifier
109     if ((self = [super init])) {
110         windowController =
111             [[MMWindowController alloc] initWithVimController:self];
112         backendProxy = [backend retain];
113         sendQueue = [NSMutableArray new];
114         mainMenuItems = [[NSMutableArray alloc] init];
115         popupMenuItems = [[NSMutableArray alloc] init];
116         toolbarItemDict = [[NSMutableDictionary alloc] init];
117         pid = processIdentifier;
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];
130         NSWindow *win = [windowController window];
132         [[NSNotificationCenter defaultCenter]
133                 addObserver:self
134                    selector:@selector(windowDidBecomeMain:)
135                        name:NSWindowDidBecomeMainNotification
136                      object:win];
138         isInitialized = YES;
139     }
141     return self;
144 - (void)dealloc
146     //NSLog(@"%@ %s", [self className], _cmd);
147     isInitialized = NO;
149 #if MM_RESEND_LAST_FAILURE
150     [resendData release];  resendData = nil;
151 #endif
153     [serverName release];  serverName = nil;
154     [backendProxy release];  backendProxy = nil;
155     [sendQueue release];  sendQueue = nil;
157     [toolbarItemDict release];  toolbarItemDict = nil;
158     [toolbar release];  toolbar = nil;
159     [popupMenuItems release];  popupMenuItems = nil;
160     [mainMenuItems release];  mainMenuItems = nil;
161     [windowController release];  windowController = nil;
163     [super dealloc];
166 - (MMWindowController *)windowController
168     return windowController;
171 - (void)setServerName:(NSString *)name
173     if (name != serverName) {
174         [serverName release];
175         serverName = [name copy];
176     }
179 - (NSString *)serverName
181     return serverName;
184 - (int)pid
186     return pid;
189 - (void)dropFiles:(NSArray *)filenames
191     int i, numberOfFiles = [filenames count];
192     NSMutableData *data = [NSMutableData data];
194     [data appendBytes:&numberOfFiles length:sizeof(int)];
196     for (i = 0; i < numberOfFiles; ++i) {
197         NSString *file = [filenames objectAtIndex:i];
198         int len = [file lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
200         if (len > 0) {
201             ++len;  // append NUL as well
202             [data appendBytes:&len length:sizeof(int)];
203             [data appendBytes:[file UTF8String] length:len];
204         }
205     }
207     [self sendMessage:DropFilesMsgID data:data];
210 - (void)dropString:(NSString *)string
212     int len = [string lengthOfBytesUsingEncoding:NSUTF8StringEncoding] + 1;
213     if (len > 0) {
214         NSMutableData *data = [NSMutableData data];
216         [data appendBytes:&len length:sizeof(int)];
217         [data appendBytes:[string UTF8String] length:len];
219         [self sendMessage:DropStringMsgID data:data];
220     }
223 - (void)sendMessage:(int)msgid data:(NSData *)data
225     //NSLog(@"sendMessage:%s (isInitialized=%d inProcessCommandQueue=%d)",
226     //        MessageStrings[msgid], isInitialized, inProcessCommandQueue);
228     if (!isInitialized) return;
230     if (inProcessCommandQueue) {
231         //NSLog(@"In process command queue; delaying message send.");
232         [sendQueue addObject:[NSNumber numberWithInt:msgid]];
233         if (data)
234             [sendQueue addObject:data];
235         else
236             [sendQueue addObject:[NSNull null]];
237         return;
238     }
240 #if MM_RESEND_LAST_FAILURE
241     if (resendTimer) {
242         //NSLog(@"cancelling scheduled resend of %s",
243         //        MessageStrings[resendMsgid]);
245         [resendTimer invalidate];
246         [resendTimer release];
247         resendTimer = nil;
248     }
250     if (resendData) {
251         [resendData release];
252         resendData = nil;
253     }
254 #endif
256     @try {
257         [backendProxy processInput:msgid data:data];
258     }
259     @catch (NSException *e) {
260         //NSLog(@"%@ %s Exception caught during DO call: %@",
261         //        [self className], _cmd, e);
262 #if MM_RESEND_LAST_FAILURE
263         //NSLog(@"%s failed, scheduling message %s for resend", _cmd,
264         //        MessageStrings[msgid]);
266         resendMsgid = msgid;
267         resendData = [data retain];
268         resendTimer = [NSTimer
269             scheduledTimerWithTimeInterval:MMResendInterval
270                                     target:self
271                                   selector:@selector(resendTimerFired:)
272                                   userInfo:nil
273                                    repeats:NO];
274         [resendTimer retain];
275 #endif
276     }
279 - (BOOL)sendMessageNow:(int)msgid data:(NSData *)data
280                timeout:(NSTimeInterval)timeout
282     if (!isInitialized || inProcessCommandQueue)
283         return NO;
285     if (timeout < 0) timeout = 0;
287     BOOL sendOk = YES;
288     NSConnection *conn = [backendProxy connectionForProxy];
289     NSTimeInterval oldTimeout = [conn requestTimeout];
291     [conn setRequestTimeout:timeout];
293     @try {
294         [backendProxy processInput:msgid data:data];
295     }
296     @catch (NSException *e) {
297         sendOk = NO;
298     }
299     @finally {
300         [conn setRequestTimeout:oldTimeout];
301     }
303     return sendOk;
306 - (id)backendProxy
308     return backendProxy;
311 - (void)cleanup
313     //NSLog(@"%@ %s", [self className], _cmd);
314     if (!isInitialized) return;
316     isInitialized = NO;
317     [toolbar setDelegate:nil];
318     [[NSNotificationCenter defaultCenter] removeObserver:self];
319     [windowController cleanup];
322 - (oneway void)showSavePanelForDirectory:(in bycopy NSString *)dir
323                                    title:(in bycopy NSString *)title
324                                   saving:(int)saving
326     if (!isInitialized) return;
328     if (saving) {
329         [[NSSavePanel savePanel] beginSheetForDirectory:dir file:nil
330                 modalForWindow:[windowController window]
331                  modalDelegate:self
332                 didEndSelector:@selector(savePanelDidEnd:code:context:)
333                    contextInfo:NULL];
334     } else {
335         NSOpenPanel *panel = [NSOpenPanel openPanel];
336         [panel setAllowsMultipleSelection:NO];
337         [panel beginSheetForDirectory:dir file:nil types:nil
338                 modalForWindow:[windowController window]
339                  modalDelegate:self
340                 didEndSelector:@selector(savePanelDidEnd:code:context:)
341                    contextInfo:NULL];
342     }
345 - (oneway void)presentDialogWithStyle:(int)style
346                               message:(in bycopy NSString *)message
347                       informativeText:(in bycopy NSString *)text
348                          buttonTitles:(in bycopy NSArray *)buttonTitles
349                       textFieldString:(in bycopy NSString *)textFieldString
351     if (!(windowController && buttonTitles && [buttonTitles count])) return;
353     MMAlert *alert = [[MMAlert alloc] init];
355     // NOTE! This has to be done before setting the informative text.
356     if (textFieldString)
357         [alert setTextFieldString:textFieldString];
359     [alert setAlertStyle:style];
361     if (message) {
362         [alert setMessageText:message];
363     } else {
364         // If no message text is specified 'Alert' is used, which we don't
365         // want, so set an empty string as message text.
366         [alert setMessageText:@""];
367     }
369     if (text) {
370         [alert setInformativeText:text];
371     } else if (textFieldString) {
372         // Make sure there is always room for the input text field.
373         [alert setInformativeText:@""];
374     }
376     unsigned i, count = [buttonTitles count];
377     for (i = 0; i < count; ++i) {
378         NSString *title = [buttonTitles objectAtIndex:i];
379         // NOTE: The title of the button may contain the character '&' to
380         // indicate that the following letter should be the key equivalent
381         // associated with the button.  Extract this letter and lowercase it.
382         NSString *keyEquivalent = nil;
383         NSRange hotkeyRange = [title rangeOfString:@"&"];
384         if (NSNotFound != hotkeyRange.location) {
385             if ([title length] > NSMaxRange(hotkeyRange)) {
386                 NSRange keyEquivRange = NSMakeRange(hotkeyRange.location+1, 1);
387                 keyEquivalent = [[title substringWithRange:keyEquivRange]
388                     lowercaseString];
389             }
391             NSMutableString *string = [NSMutableString stringWithString:title];
392             [string deleteCharactersInRange:hotkeyRange];
393             title = string;
394         }
396         [alert addButtonWithTitle:title];
398         // Set key equivalent for the button, but only if NSAlert hasn't
399         // already done so.  (Check the documentation for
400         // - [NSAlert addButtonWithTitle:] to see what key equivalents are
401         // automatically assigned.)
402         NSButton *btn = [[alert buttons] lastObject];
403         if ([[btn keyEquivalent] length] == 0 && keyEquivalent) {
404             [btn setKeyEquivalent:keyEquivalent];
405         }
406     }
408     [alert beginSheetModalForWindow:[windowController window]
409                       modalDelegate:self
410                      didEndSelector:@selector(alertDidEnd:code:context:)
411                         contextInfo:NULL];
413     [alert release];
416 - (oneway void)processCommandQueue:(in NSArray *)queue
418     if (!isInitialized) return;
420     unsigned i, count = [queue count];
421     if (count % 2) {
422         NSLog(@"WARNING: Uneven number of components (%d) in flush queue "
423                 "message; ignoring this message.", count);
424         return;
425     }
427     inProcessCommandQueue = YES;
429     //NSLog(@"======== %s BEGIN ========", _cmd);
430     for (i = 0; i < count; i += 2) {
431         NSData *value = [queue objectAtIndex:i];
432         NSData *data = [queue objectAtIndex:i+1];
434         int msgid = *((int*)[value bytes]);
435 #if 0
436         if (msgid != EnableMenuItemMsgID && msgid != AddMenuItemMsgID
437                 && msgid != AddMenuMsgID) {
438             NSLog(@"%s%s", _cmd, MessageStrings[msgid]);
439         }
440 #endif
442         [self handleMessage:msgid data:data];
443     }
444     //NSLog(@"======== %s  END  ========", _cmd);
446     if (shouldUpdateMainMenu) {
447         [self updateMainMenu];
448     }
450     [windowController processCommandQueueDidFinish];
452     inProcessCommandQueue = NO;
454     if ([sendQueue count] > 0) {
455         @try {
456             [backendProxy processInputAndData:sendQueue];
457         }
458         @catch (NSException *e) {
459             // Connection timed out, just ignore this.
460             //NSLog(@"WARNING! Connection timed out in %s", _cmd);
461         }
463         [sendQueue removeAllObjects];
464     }
467 - (void)windowDidBecomeMain:(NSNotification *)notification
469     if (isInitialized)
470         [self updateMainMenu];
473 - (NSToolbarItem *)toolbar:(NSToolbar *)theToolbar
474     itemForItemIdentifier:(NSString *)itemId
475     willBeInsertedIntoToolbar:(BOOL)flag
477     NSToolbarItem *item = [toolbarItemDict objectForKey:itemId];
478     if (!item) {
479         NSLog(@"WARNING:  No toolbar item with id '%@'", itemId);
480     }
482     return item;
485 - (NSArray *)toolbarAllowedItemIdentifiers:(NSToolbar *)theToolbar
487     return nil;
490 - (NSArray *)toolbarDefaultItemIdentifiers:(NSToolbar *)theToolbar
492     return nil;
495 @end // MMVimController
499 @implementation MMVimController (Private)
501 - (void)handleMessage:(int)msgid data:(NSData *)data
503     //NSLog(@"%@ %s", [self className], _cmd);
505     if (OpenVimWindowMsgID == msgid) {
506         [windowController openWindow];
507     } else if (BatchDrawMsgID == msgid) {
508         [self performBatchDrawWithData:data];
509     } else if (SelectTabMsgID == msgid) {
510 #if 0   // NOTE: Tab selection is done inside updateTabsWithData:.
511         const void *bytes = [data bytes];
512         int idx = *((int*)bytes);
513         //NSLog(@"Selecting tab with index %d", idx);
514         [windowController selectTabWithIndex:idx];
515 #endif
516     } else if (UpdateTabBarMsgID == msgid) {
517         [windowController updateTabsWithData:data];
518     } else if (ShowTabBarMsgID == msgid) {
519         [windowController showTabBar:YES];
520     } else if (HideTabBarMsgID == msgid) {
521         [windowController showTabBar:NO];
522     } else if (SetTextDimensionsMsgID == msgid) {
523         const void *bytes = [data bytes];
524         int rows = *((int*)bytes);  bytes += sizeof(int);
525         int cols = *((int*)bytes);  bytes += sizeof(int);
527         [windowController setTextDimensionsWithRows:rows columns:cols];
528     } else if (SetWindowTitleMsgID == msgid) {
529         const void *bytes = [data bytes];
530         int len = *((int*)bytes);  bytes += sizeof(int);
532         NSString *string = [[NSString alloc] initWithBytes:(void*)bytes
533                 length:len encoding:NSUTF8StringEncoding];
535         [[windowController window] setTitle:string];
537         [string release];
538     } else if (AddMenuMsgID == msgid) {
539         NSString *title = nil;
540         const void *bytes = [data bytes];
541         int tag = *((int*)bytes);  bytes += sizeof(int);
542         int parentTag = *((int*)bytes);  bytes += sizeof(int);
543         int len = *((int*)bytes);  bytes += sizeof(int);
544         if (len > 0) {
545             title = [[NSString alloc] initWithBytes:(void*)bytes length:len
546                                            encoding:NSUTF8StringEncoding];
547             bytes += len;
548         }
549         int idx = *((int*)bytes);  bytes += sizeof(int);
551         if (MenuToolbarType == parentTag) {
552             if (!toolbar) {
553                 // NOTE! Each toolbar must have a unique identifier, else each
554                 // window will have the same toolbar.
555                 NSString *ident = [NSString stringWithFormat:@"%d.%d",
556                          (int)self, tag];
557                 toolbar = [[NSToolbar alloc] initWithIdentifier:ident];
559                 [toolbar setShowsBaselineSeparator:NO];
560                 [toolbar setDelegate:self];
561                 [toolbar setDisplayMode:NSToolbarDisplayModeIconOnly];
562                 [toolbar setSizeMode:NSToolbarSizeModeSmall];
564                 NSWindow *win = [windowController window];
565                 [win setToolbar:toolbar];
567                 // HACK! Redirect the pill button so that we can ask Vim to
568                 // hide the toolbar.
569                 NSButton *pillButton = [win
570                     standardWindowButton:NSWindowToolbarButton];
571                 if (pillButton) {
572                     [pillButton setAction:@selector(toggleToolbar:)];
573                     [pillButton setTarget:windowController];
574                 }
575             }
576         } else if (title) {
577             [self addMenuWithTag:tag parent:parentTag title:title atIndex:idx];
578         }
580         [title release];
581     } else if (AddMenuItemMsgID == msgid) {
582         NSString *title = nil, *tip = nil, *icon = nil, *action = nil;
583         const void *bytes = [data bytes];
584         int tag = *((int*)bytes);  bytes += sizeof(int);
585         int parentTag = *((int*)bytes);  bytes += sizeof(int);
586         int namelen = *((int*)bytes);  bytes += sizeof(int);
587         if (namelen > 0) {
588             title = [[NSString alloc] initWithBytes:(void*)bytes length:namelen
589                                            encoding:NSUTF8StringEncoding];
590             bytes += namelen;
591         }
592         int tiplen = *((int*)bytes);  bytes += sizeof(int);
593         if (tiplen > 0) {
594             tip = [[NSString alloc] initWithBytes:(void*)bytes length:tiplen
595                                            encoding:NSUTF8StringEncoding];
596             bytes += tiplen;
597         }
598         int iconlen = *((int*)bytes);  bytes += sizeof(int);
599         if (iconlen > 0) {
600             icon = [[NSString alloc] initWithBytes:(void*)bytes length:iconlen
601                                            encoding:NSUTF8StringEncoding];
602             bytes += iconlen;
603         }
604         int actionlen = *((int*)bytes);  bytes += sizeof(int);
605         if (actionlen > 0) {
606             action = [[NSString alloc] initWithBytes:(void*)bytes
607                                               length:actionlen
608                                             encoding:NSUTF8StringEncoding];
609             bytes += actionlen;
610         }
611         int idx = *((int*)bytes);  bytes += sizeof(int);
612         if (idx < 0) idx = 0;
613         int key = *((int*)bytes);  bytes += sizeof(int);
614         int mask = *((int*)bytes);  bytes += sizeof(int);
616         NSString *ident = [NSString stringWithFormat:@"%d.%d",
617                 (int)self, parentTag];
618         if (toolbar && [[toolbar identifier] isEqual:ident]) {
619             [self addToolbarItemWithTag:tag label:title tip:tip icon:icon
620                                 atIndex:idx];
621         } else {
622             NSMenu *parent = [self menuForTag:parentTag];
623             [self addMenuItemWithTag:tag parent:parent title:title tip:tip
624                        keyEquivalent:key modifiers:mask action:action
625                              atIndex:idx];
626         }
628         [title release];
629         [tip release];
630         [icon release];
631         [action release];
632     } else if (RemoveMenuItemMsgID == msgid) {
633         const void *bytes = [data bytes];
634         int tag = *((int*)bytes);  bytes += sizeof(int);
636         id item;
637         int idx;
638         if ((item = [self toolbarItemForTag:tag index:&idx])) {
639             [toolbar removeItemAtIndex:idx];
640         } else if ((item = [self menuItemForTag:tag])) {
641             [item retain];
643             if ([item menu] == [NSApp mainMenu] || ![item menu]) {
644                 // NOTE: To be on the safe side we try to remove the item from
645                 // both arrays (it is ok to call removeObject: even if an array
646                 // does not contain the object to remove).
647                 [mainMenuItems removeObject:item];
648                 [popupMenuItems removeObject:item];
649             }
651             if ([item menu])
652                 [[item menu] removeItem:item];
654             [item release];
655         }
656     } else if (EnableMenuItemMsgID == msgid) {
657         const void *bytes = [data bytes];
658         int tag = *((int*)bytes);  bytes += sizeof(int);
659         int state = *((int*)bytes);  bytes += sizeof(int);
661         id item = [self toolbarItemForTag:tag index:NULL];
662         if (!item)
663             item = [self menuItemForTag:tag];
665         [item setEnabled:state];
666     } else if (ShowToolbarMsgID == msgid) {
667         const void *bytes = [data bytes];
668         int enable = *((int*)bytes);  bytes += sizeof(int);
669         int flags = *((int*)bytes);  bytes += sizeof(int);
671         int mode = NSToolbarDisplayModeDefault;
672         if (flags & ToolbarLabelFlag) {
673             mode = flags & ToolbarIconFlag ? NSToolbarDisplayModeIconAndLabel
674                     : NSToolbarDisplayModeLabelOnly;
675         } else if (flags & ToolbarIconFlag) {
676             mode = NSToolbarDisplayModeIconOnly;
677         }
679         int size = flags & ToolbarSizeRegularFlag ? NSToolbarSizeModeRegular
680                 : NSToolbarSizeModeSmall;
682         [windowController showToolbar:enable size:size mode:mode];
683     } else if (CreateScrollbarMsgID == msgid) {
684         const void *bytes = [data bytes];
685         long ident = *((long*)bytes);  bytes += sizeof(long);
686         int type = *((int*)bytes);  bytes += sizeof(int);
688         [windowController createScrollbarWithIdentifier:ident type:type];
689     } else if (DestroyScrollbarMsgID == msgid) {
690         const void *bytes = [data bytes];
691         long ident = *((long*)bytes);  bytes += sizeof(long);
693         [windowController destroyScrollbarWithIdentifier:ident];
694     } else if (ShowScrollbarMsgID == msgid) {
695         const void *bytes = [data bytes];
696         long ident = *((long*)bytes);  bytes += sizeof(long);
697         int visible = *((int*)bytes);  bytes += sizeof(int);
699         [windowController showScrollbarWithIdentifier:ident state:visible];
700     } else if (SetScrollbarPositionMsgID == msgid) {
701         const void *bytes = [data bytes];
702         long ident = *((long*)bytes);  bytes += sizeof(long);
703         int pos = *((int*)bytes);  bytes += sizeof(int);
704         int len = *((int*)bytes);  bytes += sizeof(int);
706         [windowController setScrollbarPosition:pos length:len
707                                     identifier:ident];
708     } else if (SetScrollbarThumbMsgID == msgid) {
709         const void *bytes = [data bytes];
710         long ident = *((long*)bytes);  bytes += sizeof(long);
711         float val = *((float*)bytes);  bytes += sizeof(float);
712         float prop = *((float*)bytes);  bytes += sizeof(float);
714         [windowController setScrollbarThumbValue:val proportion:prop
715                                       identifier:ident];
716     } else if (SetFontMsgID == msgid) {
717         const void *bytes = [data bytes];
718         float size = *((float*)bytes);  bytes += sizeof(float);
719         int len = *((int*)bytes);  bytes += sizeof(int);
720         NSString *name = [[NSString alloc]
721                 initWithBytes:(void*)bytes length:len
722                      encoding:NSUTF8StringEncoding];
723         NSFont *font = [NSFont fontWithName:name size:size];
725         if (font)
726             [windowController setFont:font];
728         [name release];
729     } else if (SetDefaultColorsMsgID == msgid) {
730         const void *bytes = [data bytes];
731         int bg = *((int*)bytes);  bytes += sizeof(int);
732         int fg = *((int*)bytes);  bytes += sizeof(int);
733         NSColor *back = [NSColor colorWithRgbInt:bg];
734         NSColor *fore = [NSColor colorWithRgbInt:fg];
736         [windowController setDefaultColorsBackground:back foreground:fore];
737     } else if (ExecuteActionMsgID == msgid) {
738         const void *bytes = [data bytes];
739         int len = *((int*)bytes);  bytes += sizeof(int);
740         NSString *actionName = [[NSString alloc]
741                 initWithBytesNoCopy:(void*)bytes
742                              length:len
743                            encoding:NSUTF8StringEncoding
744                        freeWhenDone:NO];
746         SEL sel = NSSelectorFromString(actionName);
747         [NSApp sendAction:sel to:nil from:self];
749         [actionName release];
750     } else if (ShowPopupMenuMsgID == msgid) {
751         const void *bytes = [data bytes];
752         int row = *((int*)bytes);  bytes += sizeof(int);
753         int col = *((int*)bytes);  bytes += sizeof(int);
754         int len = *((int*)bytes);  bytes += sizeof(int);
755         NSString *title = [[NSString alloc]
756                 initWithBytesNoCopy:(void*)bytes
757                              length:len
758                            encoding:NSUTF8StringEncoding
759                        freeWhenDone:NO];
761         NSMenu *menu = [self topLevelMenuForTitle:title];
762         if (menu) {
763             [windowController popupMenu:menu atRow:row column:col];
764         } else {
765             NSLog(@"WARNING: Cannot popup menu with title %@; no such menu.",
766                     title);
767         }
769         [title release];
770     } else if (SetMouseShapeMsgID == msgid) {
771         const void *bytes = [data bytes];
772         int shape = *((int*)bytes);  bytes += sizeof(int);
774         [windowController setMouseShape:shape];
775     } else if (AdjustLinespaceMsgID == msgid) {
776         const void *bytes = [data bytes];
777         int linespace = *((int*)bytes);  bytes += sizeof(int);
779         [windowController adjustLinespace:linespace];
780     } else if (ActivateMsgID == msgid) {
781         [NSApp activateIgnoringOtherApps:YES];
782         [[windowController window] makeKeyAndOrderFront:self];
783     } else if (SetServerNameMsgID == msgid) {
784         NSString *name = [[NSString alloc] initWithData:data
785                                                encoding:NSUTF8StringEncoding];
786         [self setServerName:name];
787         [name release];
788     } else {
789         NSLog(@"WARNING: Unknown message received (msgid=%d)", msgid);
790     }
794 #define MM_DEBUG_DRAWING 0
796 - (void)performBatchDrawWithData:(NSData *)data
798     // TODO!  Move to window controller.
799     MMTextStorage *textStorage = [windowController textStorage];
800     MMTextView *textView = [windowController textView];
801     if (!(textStorage && textView))
802         return;
804     const void *bytes = [data bytes];
805     const void *end = bytes + [data length];
807 #if MM_DEBUG_DRAWING
808     NSLog(@"====> BEGIN %s", _cmd);
809 #endif
810     [textStorage beginEditing];
812     // TODO: Sanity check input
814     while (bytes < end) {
815         int type = *((int*)bytes);  bytes += sizeof(int);
817         if (ClearAllDrawType == type) {
818             int color = *((int*)bytes);  bytes += sizeof(int);
820 #if MM_DEBUG_DRAWING
821             NSLog(@"   Clear all");
822 #endif
823             [textStorage clearAllWithColor:[NSColor colorWithRgbInt:color]];
824         } else if (ClearBlockDrawType == type) {
825             int color = *((int*)bytes);  bytes += sizeof(int);
826             int row1 = *((int*)bytes);  bytes += sizeof(int);
827             int col1 = *((int*)bytes);  bytes += sizeof(int);
828             int row2 = *((int*)bytes);  bytes += sizeof(int);
829             int col2 = *((int*)bytes);  bytes += sizeof(int);
831 #if MM_DEBUG_DRAWING
832             NSLog(@"   Clear block (%d,%d) -> (%d,%d)", row1, col1,
833                     row2,col2);
834 #endif
835             [textStorage clearBlockFromRow:row1 column:col1
836                     toRow:row2 column:col2
837                     color:[NSColor colorWithRgbInt:color]];
838         } else if (DeleteLinesDrawType == type) {
839             int color = *((int*)bytes);  bytes += sizeof(int);
840             int row = *((int*)bytes);  bytes += sizeof(int);
841             int count = *((int*)bytes);  bytes += sizeof(int);
842             int bot = *((int*)bytes);  bytes += sizeof(int);
843             int left = *((int*)bytes);  bytes += sizeof(int);
844             int right = *((int*)bytes);  bytes += sizeof(int);
846 #if MM_DEBUG_DRAWING
847             NSLog(@"   Delete %d line(s) from %d", count, row);
848 #endif
849             [textStorage deleteLinesFromRow:row lineCount:count
850                     scrollBottom:bot left:left right:right
851                            color:[NSColor colorWithRgbInt:color]];
852         } else if (ReplaceStringDrawType == type) {
853             int bg = *((int*)bytes);  bytes += sizeof(int);
854             int fg = *((int*)bytes);  bytes += sizeof(int);
855             int sp = *((int*)bytes);  bytes += sizeof(int);
856             int row = *((int*)bytes);  bytes += sizeof(int);
857             int col = *((int*)bytes);  bytes += sizeof(int);
858             int flags = *((int*)bytes);  bytes += sizeof(int);
859             int len = *((int*)bytes);  bytes += sizeof(int);
860             NSString *string = [[NSString alloc]
861                     initWithBytesNoCopy:(void*)bytes
862                                  length:len
863                                encoding:NSUTF8StringEncoding
864                            freeWhenDone:NO];
865             bytes += len;
867 #if MM_DEBUG_DRAWING
868             NSLog(@"   Draw string at (%d,%d) length=%d flags=%d fg=0x%x "
869                     "bg=0x%x sp=0x%x (%@)", row, col, len, flags, fg, bg, sp,
870                     len > 0 ? [string substringToIndex:1] : @"");
871 #endif
872             // NOTE: If this is a call to draw the (block) cursor, then cancel
873             // any previous request to draw the insertion point, or it might
874             // get drawn as well.
875             if (flags & DRAW_CURSOR) {
876                 [textView setShouldDrawInsertionPoint:NO];
877                 //[textView drawInsertionPointAtRow:row column:col
878                 //                            shape:MMInsertionPointBlock
879                 //                            color:[NSColor colorWithRgbInt:bg]];
880             }
881             [textStorage replaceString:string
882                                  atRow:row column:col
883                              withFlags:flags
884                        foregroundColor:[NSColor colorWithRgbInt:fg]
885                        backgroundColor:[NSColor colorWithRgbInt:bg]
886                           specialColor:[NSColor colorWithRgbInt:sp]];
888             [string release];
889         } else if (InsertLinesDrawType == type) {
890             int color = *((int*)bytes);  bytes += sizeof(int);
891             int row = *((int*)bytes);  bytes += sizeof(int);
892             int count = *((int*)bytes);  bytes += sizeof(int);
893             int bot = *((int*)bytes);  bytes += sizeof(int);
894             int left = *((int*)bytes);  bytes += sizeof(int);
895             int right = *((int*)bytes);  bytes += sizeof(int);
897 #if MM_DEBUG_DRAWING
898             NSLog(@"   Insert %d line(s) at row %d", count, row);
899 #endif
900             [textStorage insertLinesAtRow:row lineCount:count
901                              scrollBottom:bot left:left right:right
902                                     color:[NSColor colorWithRgbInt:color]];
903         } else if (DrawCursorDrawType == type) {
904             int color = *((int*)bytes);  bytes += sizeof(int);
905             int row = *((int*)bytes);  bytes += sizeof(int);
906             int col = *((int*)bytes);  bytes += sizeof(int);
907             int shape = *((int*)bytes);  bytes += sizeof(int);
908             int percent = *((int*)bytes);  bytes += sizeof(int);
910 #if MM_DEBUG_DRAWING
911             NSLog(@"   Draw cursor at (%d,%d)", row, col);
912 #endif
913             [textView drawInsertionPointAtRow:row column:col shape:shape
914                                      fraction:percent
915                                         color:[NSColor colorWithRgbInt:color]];
916         } else {
917             NSLog(@"WARNING: Unknown draw type (type=%d)", type);
918         }
919     }
921     [textStorage endEditing];
922 #if MM_DEBUG_DRAWING
923     NSLog(@"<==== END   %s", _cmd);
924 #endif
927 - (void)savePanelDidEnd:(NSSavePanel *)panel code:(int)code
928                 context:(void *)context
930     NSString *string = (code == NSOKButton) ? [panel filename] : nil;
931     @try {
932         [backendProxy setDialogReturn:string];
933     }
934     @catch (NSException *e) {
935         NSLog(@"Exception caught in %s %@", _cmd, e);
936     }
939 - (void)alertDidEnd:(MMAlert *)alert code:(int)code context:(void *)context
941     NSArray *ret = nil;
943     code = code - NSAlertFirstButtonReturn + 1;
945     if ([alert isKindOfClass:[MMAlert class]] && [alert textField]) {
946         ret = [NSArray arrayWithObjects:[NSNumber numberWithInt:code],
947             [[alert textField] stringValue], nil];
948     } else {
949         ret = [NSArray arrayWithObject:[NSNumber numberWithInt:code]];
950     }
952     @try {
953         [backendProxy setDialogReturn:ret];
954     }
955     @catch (NSException *e) {
956         NSLog(@"Exception caught in %s %@", _cmd, e);
957     }
960 - (NSMenuItem *)menuItemForTag:(int)tag
962     // Search the main menu.
963     int i, count = [mainMenuItems count];
964     for (i = 0; i < count; ++i) {
965         NSMenuItem *item = [mainMenuItems objectAtIndex:i];
966         if ([item tag] == tag) return item;
967         item = findMenuItemWithTagInMenu([item submenu], tag);
968         if (item) return item;
969     }
971     // Search the popup menus.
972     count = [popupMenuItems count];
973     for (i = 0; i < count; ++i) {
974         NSMenuItem *item = [popupMenuItems objectAtIndex:i];
975         if ([item tag] == tag) return item;
976         item = findMenuItemWithTagInMenu([item submenu], tag);
977         if (item) return item;
978     }
980     return nil;
983 - (NSMenu *)menuForTag:(int)tag
985     return [[self menuItemForTag:tag] submenu];
988 - (NSMenu *)topLevelMenuForTitle:(NSString *)title
990     // Search only the top-level menus.
992     unsigned i, count = [popupMenuItems count];
993     for (i = 0; i < count; ++i) {
994         NSMenuItem *item = [popupMenuItems objectAtIndex:i];
995         if ([title isEqual:[item title]])
996             return [item submenu];
997     }
999     count = [mainMenuItems count];
1000     for (i = 0; i < count; ++i) {
1001         NSMenuItem *item = [mainMenuItems objectAtIndex:i];
1002         if ([title isEqual:[item title]])
1003             return [item submenu];
1004     }
1006     return nil;
1009 - (void)addMenuWithTag:(int)tag parent:(int)parentTag title:(NSString *)title
1010                atIndex:(int)idx
1012     NSMenu *parent = [self menuForTag:parentTag];
1013     NSMenuItem *item = [[NSMenuItem alloc] init];
1014     NSMenu *menu = [[NSMenu alloc] initWithTitle:title];
1016     [menu setAutoenablesItems:NO];
1017     [item setTag:tag];
1018     [item setTitle:title];
1019     [item setSubmenu:menu];
1021     if (parent) {
1022         if ([parent numberOfItems] <= idx) {
1023             [parent addItem:item];
1024         } else {
1025             [parent insertItem:item atIndex:idx];
1026         }
1027     } else {
1028         NSMutableArray *items = (MenuPopupType == parentTag)
1029             ? popupMenuItems : mainMenuItems;
1030         if ([items count] <= idx) {
1031             [items addObject:item];
1032         } else {
1033             [items insertObject:item atIndex:idx];
1034         }
1036         shouldUpdateMainMenu = (MenuPopupType != parentTag);
1037     }
1039     [item release];
1040     [menu release];
1043 - (void)addMenuItemWithTag:(int)tag parent:(NSMenu *)parent
1044                      title:(NSString *)title tip:(NSString *)tip
1045              keyEquivalent:(int)key modifiers:(int)mask
1046                     action:(NSString *)action atIndex:(int)idx
1048     if (parent) {
1049         NSMenuItem *item = nil;
1050         if (!title || ([title hasPrefix:@"-"] && [title hasSuffix:@"-"])) {
1051             item = [NSMenuItem separatorItem];
1052         } else {
1053             item = [[[NSMenuItem alloc] init] autorelease];
1054             [item setTitle:title];
1055             // TODO: Check that 'action' is a valid action (nothing will happen
1056             // if it isn't, but it would be nice with a warning).
1057             if (action) [item setAction:NSSelectorFromString(action)];
1058             else        [item setAction:@selector(vimMenuItemAction:)];
1059             if (tip) [item setToolTip:tip];
1061             if (key != 0) {
1062                 NSString *keyString =
1063                     [NSString stringWithFormat:@"%C", key];
1064                 [item setKeyEquivalent:keyString];
1065                 [item setKeyEquivalentModifierMask:mask];
1066             }
1067         }
1069         // NOTE!  The tag is used to idenfity which menu items were
1070         // added by Vim (tag != 0) and which were added by the AppKit
1071         // (tag == 0).
1072         [item setTag:tag];
1074         if ([parent numberOfItems] <= idx) {
1075             [parent addItem:item];
1076         } else {
1077             [parent insertItem:item atIndex:idx];
1078         }
1079     } else {
1080         NSLog(@"WARNING: Menu item '%@' (tag=%d) has no parent.", title, tag);
1081     }
1084 - (void)updateMainMenu
1086     NSMenu *mainMenu = [NSApp mainMenu];
1088     // Stop NSApp from updating the Window menu.
1089     [NSApp setWindowsMenu:nil];
1091     // Remove all menus from main menu (except the MacVim menu).
1092     int i, count = [mainMenu numberOfItems];
1093     for (i = count-1; i > 0; --i) {
1094         [mainMenu removeItemAtIndex:i];
1095     }
1097     // Add menus from 'mainMenuItems' to main menu.
1098     count = [mainMenuItems count];
1099     for (i = 0; i < count; ++i) {
1100         [mainMenu addItem:[mainMenuItems objectAtIndex:i]];
1101     }
1103     // Set the new Window menu.
1104     // TODO!  Need to look for 'Window' in all localized languages.
1105     NSMenu *windowMenu = [[mainMenu itemWithTitle:@"Window"] submenu];
1106     if (windowMenu) {
1107         // Remove all AppKit owned menu items (tag == 0); they will be added
1108         // again when setWindowsMenu: is called.
1109         count = [windowMenu numberOfItems];
1110         for (i = count-1; i >= 0; --i) {
1111             NSMenuItem *item = [windowMenu itemAtIndex:i];
1112             if (![item tag]) {
1113                 [windowMenu removeItem:item];
1114             }
1115         }
1117         [NSApp setWindowsMenu:windowMenu];
1118     }
1120     shouldUpdateMainMenu = NO;
1123 - (NSToolbarItem *)toolbarItemForTag:(int)tag index:(int *)index
1125     if (!toolbar) return nil;
1127     NSArray *items = [toolbar items];
1128     int i, count = [items count];
1129     for (i = 0; i < count; ++i) {
1130         NSToolbarItem *item = [items objectAtIndex:i];
1131         if ([item tag] == tag) {
1132             if (index) *index = i;
1133             return item;
1134         }
1135     }
1137     return nil;
1140 - (void)addToolbarItemToDictionaryWithTag:(int)tag label:(NSString *)title
1141         toolTip:(NSString *)tip icon:(NSString *)icon
1143     // If the item corresponds to a separator then do nothing, since it is
1144     // already defined by Cocoa.
1145     if (!title || [title isEqual:NSToolbarSeparatorItemIdentifier]
1146                || [title isEqual:NSToolbarSpaceItemIdentifier]
1147                || [title isEqual:NSToolbarFlexibleSpaceItemIdentifier])
1148         return;
1150     NSToolbarItem *item = [[NSToolbarItem alloc] initWithItemIdentifier:title];
1151     [item setTag:tag];
1152     [item setLabel:title];
1153     [item setToolTip:tip];
1154     [item setAction:@selector(vimMenuItemAction:)];
1155     [item setAutovalidates:NO];
1157     NSImage *img = [NSImage imageNamed:icon];
1158     if (!img) {
1159         NSLog(@"WARNING: Could not find image with name '%@' to use as toolbar"
1160                " image for identifier '%@';"
1161                " using default toolbar icon '%@' instead.",
1162                icon, title, MMDefaultToolbarImageName);
1164         img = [NSImage imageNamed:MMDefaultToolbarImageName];
1165     }
1167     [item setImage:img];
1169     [toolbarItemDict setObject:item forKey:title];
1171     [item release];
1174 - (void)addToolbarItemWithTag:(int)tag label:(NSString *)label tip:(NSString
1175                    *)tip icon:(NSString *)icon atIndex:(int)idx
1177     if (!toolbar) return;
1179     // Check for separator items.
1180     if (!label) {
1181         label = NSToolbarSeparatorItemIdentifier;
1182     } else if ([label length] >= 2 && [label hasPrefix:@"-"]
1183                                    && [label hasSuffix:@"-"]) {
1184         // The label begins and ends with '-'; decided which kind of separator
1185         // item it is by looking at the prefix.
1186         if ([label hasPrefix:@"-space"]) {
1187             label = NSToolbarSpaceItemIdentifier;
1188         } else if ([label hasPrefix:@"-flexspace"]) {
1189             label = NSToolbarFlexibleSpaceItemIdentifier;
1190         } else {
1191             label = NSToolbarSeparatorItemIdentifier;
1192         }
1193     }
1195     [self addToolbarItemToDictionaryWithTag:tag label:label toolTip:tip
1196                                        icon:icon];
1198     int maxIdx = [[toolbar items] count];
1199     if (maxIdx < idx) idx = maxIdx;
1201     [toolbar insertItemWithItemIdentifier:label atIndex:idx];
1204 - (void)connectionDidDie:(NSNotification *)notification
1206     //NSLog(@"%@ %s%@", [self className], _cmd, notification);
1208     [self cleanup];
1210     // NOTE!  This causes the call to removeVimController: to be delayed.
1211     [[NSApp delegate]
1212             performSelectorOnMainThread:@selector(removeVimController:)
1213                              withObject:self waitUntilDone:NO];
1216 - (NSString *)description
1218     return [NSString stringWithFormat:@"%@ : isInitialized=%d inProcessCommandQueue=%d mainMenuItems=%@ popupMenuItems=%@ toolbar=%@", [self className], isInitialized, inProcessCommandQueue, mainMenuItems, popupMenuItems, toolbar];
1221 #if MM_RESEND_LAST_FAILURE
1222 - (void)resendTimerFired:(NSTimer *)timer
1224     int msgid = resendMsgid;
1225     NSData *data = nil;
1227     [resendTimer release];
1228     resendTimer = nil;
1230     if (!isInitialized)
1231         return;
1233     if (resendData)
1234         data = [resendData copy];
1236     //NSLog(@"Resending message: %s", MessageStrings[msgid]);
1237     [self sendMessage:msgid data:data];
1239 #endif
1241 @end // MMVimController (Private)
1245 @implementation NSColor (MMProtocol)
1247 + (NSColor *)colorWithRgbInt:(int)rgb
1249     float r = ((rgb>>16) & 0xff)/255.0f;
1250     float g = ((rgb>>8) & 0xff)/255.0f;
1251     float b = (rgb & 0xff)/255.0f;
1253     return [NSColor colorWithCalibratedRed:r green:g blue:b alpha:1.0f];
1256 @end // NSColor (MMProtocol)
1260 @implementation MMAlert
1261 - (void)dealloc
1263     [textField release];
1264     [super dealloc];
1267 - (void)setTextFieldString:(NSString *)textFieldString
1269     [textField release];
1270     textField = [[NSTextField alloc] init];
1271     [textField setStringValue:textFieldString];
1274 - (NSTextField *)textField
1276     return textField;
1279 - (void)setInformativeText:(NSString *)text
1281     if (textField) {
1282         // HACK! Add some space for the text field.
1283         [super setInformativeText:[text stringByAppendingString:@"\n\n\n"]];
1284     } else {
1285         [super setInformativeText:text];
1286     }
1289 - (void)beginSheetModalForWindow:(NSWindow *)window
1290                    modalDelegate:(id)delegate
1291                   didEndSelector:(SEL)didEndSelector
1292                      contextInfo:(void *)contextInfo
1294     [super beginSheetModalForWindow:window
1295                       modalDelegate:delegate
1296                      didEndSelector:didEndSelector
1297                         contextInfo:contextInfo];
1299     // HACK! Place the input text field at the bottom of the informative text
1300     // (which has been made a bit larger by adding newline characters).
1301     NSView *contentView = [[self window] contentView];
1302     NSRect rect = [contentView frame];
1303     rect.origin.y = rect.size.height;
1305     NSArray *subviews = [contentView subviews];
1306     unsigned i, count = [subviews count];
1307     for (i = 0; i < count; ++i) {
1308         NSView *view = [subviews objectAtIndex:i];
1309         if ([view isKindOfClass:[NSTextField class]]
1310                 && [view frame].origin.y < rect.origin.y) {
1311             // NOTE: The informative text field is the lowest NSTextField in
1312             // the alert dialog.
1313             rect = [view frame];
1314         }
1315     }
1317     rect.size.height = MMAlertTextFieldHeight;
1318     [textField setFrame:rect];
1319     [contentView addSubview:textField];
1320     [textField becomeFirstResponder];
1323 @end // MMAlert