1 /* vi:set ts=8 sts=4 sw=4 ft=objc:
3 * VIM - Vi IMproved by Bram Moolenaar
4 * MacVim GUI port by Bjorn Winckler
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.
13 * Coordinates input/output to/from backend. Each MMBackend communicates
14 * directly with a MMVimController.
16 * MMVimController does not deal with visual presentation. Essentially it
17 * should be able to run with no window present.
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.
26 #import "MMVimController.h"
27 #import "MMWindowController.h"
28 #import "MMAppController.h"
30 #import "MMTextView.h"
31 #import "MMAtsuiTextView.h"
34 static NSString *MMDefaultToolbarImageName = @"Attention";
35 static int MMAlertTextFieldHeight = 22;
37 // NOTE: By default a message sent to the backend will be dropped if it cannot
38 // be delivered instantly; otherwise there is a possibility that MacVim will
39 // 'beachball' while waiting to deliver DO messages to an unresponsive Vim
40 // process. This means that you cannot rely on any message sent with
41 // sendMessage: to actually reach Vim.
42 static NSTimeInterval MMBackendProxyRequestTimeout = 0;
44 #if MM_RESEND_LAST_FAILURE
45 // If a message send fails, the message will be resent after this many seconds
46 // have passed. (No queue is kept, only the very last message is resent.)
47 static NSTimeInterval MMResendInterval = 0.5;
51 @interface MMAlert : NSAlert {
52 NSTextField *textField;
54 - (void)setTextFieldString:(NSString *)textFieldString;
55 - (NSTextField *)textField;
59 @interface MMVimController (Private)
60 - (void)handleMessage:(int)msgid data:(NSData *)data;
61 - (void)savePanelDidEnd:(NSSavePanel *)panel code:(int)code
62 context:(void *)context;
63 - (void)alertDidEnd:(MMAlert *)alert code:(int)code context:(void *)context;
64 - (NSMenuItem *)recurseMenuItemForTag:(int)tag rootMenu:(NSMenu *)root;
65 - (NSMenuItem *)menuItemForTag:(int)tag;
66 - (NSMenu *)menuForTag:(int)tag;
67 - (NSMenu *)topLevelMenuForTitle:(NSString *)title;
68 - (void)addMenuWithTag:(int)tag parent:(int)parentTag title:(NSString *)title
70 - (void)addMenuItemWithTag:(int)tag parent:(NSMenu *)parent
71 title:(NSString *)title tip:(NSString *)tip
72 keyEquivalent:(int)key modifiers:(int)mask
73 action:(NSString *)action atIndex:(int)idx;
74 - (NSToolbarItem *)toolbarItemForTag:(int)tag index:(int *)index;
75 - (void)addToolbarItemToDictionaryWithTag:(int)tag label:(NSString *)title
76 toolTip:(NSString *)tip icon:(NSString *)icon;
77 - (void)addToolbarItemWithTag:(int)tag label:(NSString *)label
78 tip:(NSString *)tip icon:(NSString *)icon
80 - (void)connectionDidDie:(NSNotification *)notification;
81 #if MM_RESEND_LAST_FAILURE
82 - (void)resendTimerFired:(NSTimer *)timer;
84 - (void)replaceMenuItem:(NSMenuItem*)old with:(NSMenuItem*)new;
90 @implementation MMVimController
92 - (id)initWithBackend:(id)backend pid:(int)processIdentifier
93 recentFiles:(NSMenuItem*)menu;
95 if ((self = [super init])) {
97 recentFilesMenuItem = [menu retain];
100 [[MMWindowController alloc] initWithVimController:self];
101 backendProxy = [backend retain];
102 sendQueue = [NSMutableArray new];
103 mainMenuItems = [[NSMutableArray alloc] init];
104 popupMenuItems = [[NSMutableArray alloc] init];
105 toolbarItemDict = [[NSMutableDictionary alloc] init];
106 pid = processIdentifier;
108 NSConnection *connection = [backendProxy connectionForProxy];
110 // TODO: Check that this will not set the timeout for the root proxy
111 // (in MMAppController).
112 [connection setRequestTimeout:MMBackendProxyRequestTimeout];
114 [[NSNotificationCenter defaultCenter] addObserver:self
115 selector:@selector(connectionDidDie:)
116 name:NSConnectionDidDieNotification object:connection];
126 //NSLog(@"%@ %s", [self className], _cmd);
129 #if MM_RESEND_LAST_FAILURE
130 [resendData release]; resendData = nil;
133 [serverName release]; serverName = nil;
134 [backendProxy release]; backendProxy = nil;
135 [sendQueue release]; sendQueue = nil;
137 [toolbarItemDict release]; toolbarItemDict = nil;
138 [toolbar release]; toolbar = nil;
139 [popupMenuItems release]; popupMenuItems = nil;
140 [mainMenuItems release]; mainMenuItems = nil;
141 [windowController release]; windowController = nil;
143 [recentFilesMenuItem release]; recentFilesMenuItem = nil;
144 [recentFilesDummy release]; recentFilesDummy = nil;
149 - (MMWindowController *)windowController
151 return windowController;
154 - (void)setServerName:(NSString *)name
156 if (name != serverName) {
157 [serverName release];
158 serverName = [name copy];
162 - (NSString *)serverName
172 - (void)dropFiles:(NSArray *)filenames forceOpen:(BOOL)force
174 unsigned i, numberOfFiles = [filenames count];
175 NSMutableData *data = [NSMutableData data];
177 [data appendBytes:&force length:sizeof(BOOL)];
178 [data appendBytes:&numberOfFiles length:sizeof(int)];
180 for (i = 0; i < numberOfFiles; ++i) {
181 NSString *file = [filenames objectAtIndex:i];
182 int len = [file lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
185 ++len; // include NUL as well
186 [data appendBytes:&len length:sizeof(int)];
187 [data appendBytes:[file UTF8String] length:len];
191 [self sendMessage:DropFilesMsgID data:data];
194 - (void)dropString:(NSString *)string
196 int len = [string lengthOfBytesUsingEncoding:NSUTF8StringEncoding] + 1;
198 NSMutableData *data = [NSMutableData data];
200 [data appendBytes:&len length:sizeof(int)];
201 [data appendBytes:[string UTF8String] length:len];
203 [self sendMessage:DropStringMsgID data:data];
207 - (void)odbEdit:(NSArray *)filenames server:(OSType)theID path:(NSString *)path
208 token:(NSAppleEventDescriptor *)token
211 unsigned i, numberOfFiles = [filenames count];
212 NSMutableData *data = [NSMutableData data];
214 if (0 == numberOfFiles || 0 == theID)
217 [data appendBytes:&theID length:sizeof(theID)];
219 if (path && [path length] > 0) {
220 len = [path lengthOfBytesUsingEncoding:NSUTF8StringEncoding] + 1;
221 [data appendBytes:&len length:sizeof(int)];
222 [data appendBytes:[path UTF8String] length:len];
225 [data appendBytes:&len length:sizeof(int)];
229 DescType tokenType = [token descriptorType];
230 NSData *tokenData = [token data];
231 len = [tokenData length];
233 [data appendBytes:&tokenType length:sizeof(tokenType)];
234 [data appendBytes:&len length:sizeof(int)];
236 [data appendBytes:[tokenData bytes] length:len];
238 DescType tokenType = 0;
240 [data appendBytes:&tokenType length:sizeof(tokenType)];
241 [data appendBytes:&len length:sizeof(int)];
244 [data appendBytes:&numberOfFiles length:sizeof(int)];
246 for (i = 0; i < numberOfFiles; ++i) {
247 NSString *file = [filenames objectAtIndex:i];
248 len = [file lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
251 ++len; // include NUL as well
252 [data appendBytes:&len length:sizeof(unsigned)];
253 [data appendBytes:[file UTF8String] length:len];
257 [self sendMessage:ODBEditMsgID data:data];
260 - (void)sendMessage:(int)msgid data:(NSData *)data
262 //NSLog(@"sendMessage:%s (isInitialized=%d inProcessCommandQueue=%d)",
263 // MessageStrings[msgid], isInitialized, inProcessCommandQueue);
265 if (!isInitialized) return;
267 if (inProcessCommandQueue) {
268 //NSLog(@"In process command queue; delaying message send.");
269 [sendQueue addObject:[NSNumber numberWithInt:msgid]];
271 [sendQueue addObject:data];
273 [sendQueue addObject:[NSNull null]];
277 #if MM_RESEND_LAST_FAILURE
279 //NSLog(@"cancelling scheduled resend of %s",
280 // MessageStrings[resendMsgid]);
282 [resendTimer invalidate];
283 [resendTimer release];
288 [resendData release];
294 [backendProxy processInput:msgid data:data];
296 @catch (NSException *e) {
297 //NSLog(@"%@ %s Exception caught during DO call: %@",
298 // [self className], _cmd, e);
299 #if MM_RESEND_LAST_FAILURE
300 //NSLog(@"%s failed, scheduling message %s for resend", _cmd,
301 // MessageStrings[msgid]);
304 resendData = [data retain];
305 resendTimer = [NSTimer
306 scheduledTimerWithTimeInterval:MMResendInterval
308 selector:@selector(resendTimerFired:)
311 [resendTimer retain];
316 - (BOOL)sendMessageNow:(int)msgid data:(NSData *)data
317 timeout:(NSTimeInterval)timeout
319 // Send a message with a timeout. USE WITH EXTREME CAUTION! Sending
320 // messages in rapid succession with a timeout may cause MacVim to beach
321 // ball forever. In almost all circumstances sendMessage:data: should be
324 if (!isInitialized || inProcessCommandQueue)
327 if (timeout < 0) timeout = 0;
330 NSConnection *conn = [backendProxy connectionForProxy];
331 NSTimeInterval oldTimeout = [conn requestTimeout];
333 [conn setRequestTimeout:timeout];
336 [backendProxy processInput:msgid data:data];
338 @catch (NSException *e) {
342 [conn setRequestTimeout:oldTimeout];
348 - (void)addVimInput:(NSString *)string
350 // This is a very general method of adding input to the Vim process. It is
351 // basically the same as calling remote_send() on the process (see
352 // ':h remote_send').
354 NSData *data = [string dataUsingEncoding:NSUTF8StringEncoding];
355 [self sendMessage:AddInputMsgID data:data];
359 - (NSString *)evaluateVimExpression:(NSString *)expr
361 NSString *eval = nil;
364 eval = [backendProxy evaluateExpression:expr];
366 @catch (NSException *ex) { /* do nothing */ }
378 //NSLog(@"%@ %s", [self className], _cmd);
379 if (!isInitialized) return;
382 [toolbar setDelegate:nil];
383 [[NSNotificationCenter defaultCenter] removeObserver:self];
384 [windowController cleanup];
387 - (oneway void)showSavePanelForDirectory:(in bycopy NSString *)dir
388 title:(in bycopy NSString *)title
391 if (!isInitialized) return;
394 [[NSSavePanel savePanel] beginSheetForDirectory:dir file:nil
395 modalForWindow:[windowController window]
397 didEndSelector:@selector(savePanelDidEnd:code:context:)
400 NSOpenPanel *panel = [NSOpenPanel openPanel];
401 [panel setAllowsMultipleSelection:NO];
402 [panel beginSheetForDirectory:dir file:nil types:nil
403 modalForWindow:[windowController window]
405 didEndSelector:@selector(savePanelDidEnd:code:context:)
410 - (oneway void)presentDialogWithStyle:(int)style
411 message:(in bycopy NSString *)message
412 informativeText:(in bycopy NSString *)text
413 buttonTitles:(in bycopy NSArray *)buttonTitles
414 textFieldString:(in bycopy NSString *)textFieldString
416 if (!(windowController && buttonTitles && [buttonTitles count])) return;
418 MMAlert *alert = [[MMAlert alloc] init];
420 // NOTE! This has to be done before setting the informative text.
422 [alert setTextFieldString:textFieldString];
424 [alert setAlertStyle:style];
427 [alert setMessageText:message];
429 // If no message text is specified 'Alert' is used, which we don't
430 // want, so set an empty string as message text.
431 [alert setMessageText:@""];
435 [alert setInformativeText:text];
436 } else if (textFieldString) {
437 // Make sure there is always room for the input text field.
438 [alert setInformativeText:@""];
441 unsigned i, count = [buttonTitles count];
442 for (i = 0; i < count; ++i) {
443 NSString *title = [buttonTitles objectAtIndex:i];
444 // NOTE: The title of the button may contain the character '&' to
445 // indicate that the following letter should be the key equivalent
446 // associated with the button. Extract this letter and lowercase it.
447 NSString *keyEquivalent = nil;
448 NSRange hotkeyRange = [title rangeOfString:@"&"];
449 if (NSNotFound != hotkeyRange.location) {
450 if ([title length] > NSMaxRange(hotkeyRange)) {
451 NSRange keyEquivRange = NSMakeRange(hotkeyRange.location+1, 1);
452 keyEquivalent = [[title substringWithRange:keyEquivRange]
456 NSMutableString *string = [NSMutableString stringWithString:title];
457 [string deleteCharactersInRange:hotkeyRange];
461 [alert addButtonWithTitle:title];
463 // Set key equivalent for the button, but only if NSAlert hasn't
464 // already done so. (Check the documentation for
465 // - [NSAlert addButtonWithTitle:] to see what key equivalents are
466 // automatically assigned.)
467 NSButton *btn = [[alert buttons] lastObject];
468 if ([[btn keyEquivalent] length] == 0 && keyEquivalent) {
469 [btn setKeyEquivalent:keyEquivalent];
473 [alert beginSheetModalForWindow:[windowController window]
475 didEndSelector:@selector(alertDidEnd:code:context:)
481 - (oneway void)processCommandQueue:(in bycopy NSArray *)queue
483 if (!isInitialized) return;
485 unsigned i, count = [queue count];
487 NSLog(@"WARNING: Uneven number of components (%d) in flush queue "
488 "message; ignoring this message.", count);
492 inProcessCommandQueue = YES;
494 //NSLog(@"======== %s BEGIN ========", _cmd);
495 for (i = 0; i < count; i += 2) {
496 NSData *value = [queue objectAtIndex:i];
497 NSData *data = [queue objectAtIndex:i+1];
499 int msgid = *((int*)[value bytes]);
500 //NSLog(@"%s%s", _cmd, MessageStrings[msgid]);
502 [self handleMessage:msgid data:data];
504 //NSLog(@"======== %s END ========", _cmd);
506 if (shouldUpdateMainMenu) {
507 [self updateMainMenu];
510 [windowController processCommandQueueDidFinish];
512 inProcessCommandQueue = NO;
514 if ([sendQueue count] > 0) {
516 [backendProxy processInputAndData:sendQueue];
518 @catch (NSException *e) {
519 // Connection timed out, just ignore this.
520 //NSLog(@"WARNING! Connection timed out in %s", _cmd);
523 [sendQueue removeAllObjects];
527 - (NSToolbarItem *)toolbar:(NSToolbar *)theToolbar
528 itemForItemIdentifier:(NSString *)itemId
529 willBeInsertedIntoToolbar:(BOOL)flag
531 NSToolbarItem *item = [toolbarItemDict objectForKey:itemId];
533 NSLog(@"WARNING: No toolbar item with id '%@'", itemId);
539 - (NSArray *)toolbarAllowedItemIdentifiers:(NSToolbar *)theToolbar
544 - (NSArray *)toolbarDefaultItemIdentifiers:(NSToolbar *)theToolbar
549 - (void)updateMainMenu
551 NSMenu *mainMenu = [NSApp mainMenu];
553 // Stop NSApp from updating the Window menu.
554 [NSApp setWindowsMenu:nil];
556 // Remove all menus from main menu (except the MacVim menu).
557 int i, count = [mainMenu numberOfItems];
558 for (i = count-1; i > 0; --i) {
559 [mainMenu removeItemAtIndex:i];
562 // Add menus from 'mainMenuItems' to main menu.
563 count = [mainMenuItems count];
564 for (i = 0; i < count; ++i) {
565 [mainMenu addItem:[mainMenuItems objectAtIndex:i]];
568 // Set the new Window menu.
569 // TODO! Need to look for 'Window' in all localized languages.
570 NSMenu *windowMenu = [[mainMenu itemWithTitle:@"Window"] submenu];
572 // Remove all AppKit owned menu items (tag == 0); they will be added
573 // again when setWindowsMenu: is called.
574 count = [windowMenu numberOfItems];
575 for (i = count-1; i >= 0; --i) {
576 NSMenuItem *item = [windowMenu itemAtIndex:i];
578 [windowMenu removeItem:item];
582 [NSApp setWindowsMenu:windowMenu];
585 // Replace real Recent Files menu in the old menu with the dummy, then
586 // remove dummy from new menu and put Recent Files menu there
587 NSMenuItem *oldItem = (NSMenuItem*)[recentFilesMenuItem representedObject];
589 [self replaceMenuItem:recentFilesMenuItem with:oldItem];
590 [recentFilesMenuItem setRepresentedObject:recentFilesDummy];
591 [self replaceMenuItem:recentFilesDummy with:recentFilesMenuItem];
593 shouldUpdateMainMenu = NO;
596 @end // MMVimController
600 @implementation MMVimController (Private)
602 - (void)handleMessage:(int)msgid data:(NSData *)data
604 //if (msgid != AddMenuMsgID && msgid != AddMenuItemMsgID)
605 // NSLog(@"%@ %s%s", [self className], _cmd, MessageStrings[msgid]);
607 if (OpenVimWindowMsgID == msgid) {
608 [windowController openWindow];
609 } else if (BatchDrawMsgID == msgid) {
610 [[[windowController vimView] textView] performBatchDrawWithData:data];
611 } else if (SelectTabMsgID == msgid) {
612 #if 0 // NOTE: Tab selection is done inside updateTabsWithData:.
613 const void *bytes = [data bytes];
614 int idx = *((int*)bytes);
615 //NSLog(@"Selecting tab with index %d", idx);
616 [windowController selectTabWithIndex:idx];
618 } else if (UpdateTabBarMsgID == msgid) {
619 [windowController updateTabsWithData:data];
620 } else if (ShowTabBarMsgID == msgid) {
621 [windowController showTabBar:YES];
622 } else if (HideTabBarMsgID == msgid) {
623 [windowController showTabBar:NO];
624 } else if (SetTextDimensionsMsgID == msgid || LiveResizeMsgID == msgid) {
625 const void *bytes = [data bytes];
626 int rows = *((int*)bytes); bytes += sizeof(int);
627 int cols = *((int*)bytes); bytes += sizeof(int);
629 [windowController setTextDimensionsWithRows:rows columns:cols
630 live:(LiveResizeMsgID==msgid)];
631 } else if (SetWindowTitleMsgID == msgid) {
632 const void *bytes = [data bytes];
633 int len = *((int*)bytes); bytes += sizeof(int);
635 NSString *string = [[NSString alloc] initWithBytes:(void*)bytes
636 length:len encoding:NSUTF8StringEncoding];
638 [windowController setTitle:string];
641 } else if (AddMenuMsgID == msgid) {
642 NSString *title = nil;
643 const void *bytes = [data bytes];
644 int tag = *((int*)bytes); bytes += sizeof(int);
645 int parentTag = *((int*)bytes); bytes += sizeof(int);
646 int len = *((int*)bytes); bytes += sizeof(int);
648 title = [[NSString alloc] initWithBytes:(void*)bytes length:len
649 encoding:NSUTF8StringEncoding];
652 int idx = *((int*)bytes); bytes += sizeof(int);
654 if (MenuToolbarType == parentTag) {
656 // NOTE! Each toolbar must have a unique identifier, else each
657 // window will have the same toolbar.
658 NSString *ident = [NSString stringWithFormat:@"%d.%d",
660 toolbar = [[NSToolbar alloc] initWithIdentifier:ident];
662 [toolbar setShowsBaselineSeparator:NO];
663 [toolbar setDelegate:self];
664 [toolbar setDisplayMode:NSToolbarDisplayModeIconOnly];
665 [toolbar setSizeMode:NSToolbarSizeModeSmall];
667 [windowController setToolbar:toolbar];
670 [self addMenuWithTag:tag parent:parentTag title:title atIndex:idx];
674 } else if (AddMenuItemMsgID == msgid) {
675 NSString *title = nil, *tip = nil, *icon = nil, *action = nil;
676 const void *bytes = [data bytes];
677 int tag = *((int*)bytes); bytes += sizeof(int);
678 int parentTag = *((int*)bytes); bytes += sizeof(int);
679 int namelen = *((int*)bytes); bytes += sizeof(int);
681 title = [[NSString alloc] initWithBytes:(void*)bytes length:namelen
682 encoding:NSUTF8StringEncoding];
685 int tiplen = *((int*)bytes); bytes += sizeof(int);
687 tip = [[NSString alloc] initWithBytes:(void*)bytes length:tiplen
688 encoding:NSUTF8StringEncoding];
691 int iconlen = *((int*)bytes); bytes += sizeof(int);
693 icon = [[NSString alloc] initWithBytes:(void*)bytes length:iconlen
694 encoding:NSUTF8StringEncoding];
697 int actionlen = *((int*)bytes); bytes += sizeof(int);
699 action = [[NSString alloc] initWithBytes:(void*)bytes
701 encoding:NSUTF8StringEncoding];
704 int idx = *((int*)bytes); bytes += sizeof(int);
705 if (idx < 0) idx = 0;
706 int key = *((int*)bytes); bytes += sizeof(int);
707 int mask = *((int*)bytes); bytes += sizeof(int);
709 NSString *ident = [NSString stringWithFormat:@"%d.%d",
710 (int)self, parentTag];
711 if (toolbar && [[toolbar identifier] isEqual:ident]) {
712 [self addToolbarItemWithTag:tag label:title tip:tip icon:icon
715 NSMenu *parent = [self menuForTag:parentTag];
716 [self addMenuItemWithTag:tag parent:parent title:title tip:tip
717 keyEquivalent:key modifiers:mask action:action
725 } else if (RemoveMenuItemMsgID == msgid) {
726 const void *bytes = [data bytes];
727 int tag = *((int*)bytes); bytes += sizeof(int);
731 if ((item = [self toolbarItemForTag:tag index:&idx])) {
732 [toolbar removeItemAtIndex:idx];
733 } else if ((item = [self menuItemForTag:tag])) {
736 if ([item menu] == [NSApp mainMenu] || ![item menu]) {
737 // NOTE: To be on the safe side we try to remove the item from
738 // both arrays (it is ok to call removeObject: even if an array
739 // does not contain the object to remove).
740 [mainMenuItems removeObject:item];
741 [popupMenuItems removeObject:item];
745 [[item menu] removeItem:item];
750 // Reset cached menu, just to be on the safe side.
751 lastMenuSearched = nil;
752 } else if (EnableMenuItemMsgID == msgid) {
753 const void *bytes = [data bytes];
754 int tag = *((int*)bytes); bytes += sizeof(int);
755 int state = *((int*)bytes); bytes += sizeof(int);
757 id item = [self toolbarItemForTag:tag index:NULL];
759 item = [self menuItemForTag:tag];
761 [item setEnabled:state];
762 } else if (ShowToolbarMsgID == msgid) {
763 const void *bytes = [data bytes];
764 int enable = *((int*)bytes); bytes += sizeof(int);
765 int flags = *((int*)bytes); bytes += sizeof(int);
767 int mode = NSToolbarDisplayModeDefault;
768 if (flags & ToolbarLabelFlag) {
769 mode = flags & ToolbarIconFlag ? NSToolbarDisplayModeIconAndLabel
770 : NSToolbarDisplayModeLabelOnly;
771 } else if (flags & ToolbarIconFlag) {
772 mode = NSToolbarDisplayModeIconOnly;
775 int size = flags & ToolbarSizeRegularFlag ? NSToolbarSizeModeRegular
776 : NSToolbarSizeModeSmall;
778 [windowController showToolbar:enable size:size mode:mode];
779 } else if (CreateScrollbarMsgID == msgid) {
780 const void *bytes = [data bytes];
781 long ident = *((long*)bytes); bytes += sizeof(long);
782 int type = *((int*)bytes); bytes += sizeof(int);
784 [windowController createScrollbarWithIdentifier:ident type:type];
785 } else if (DestroyScrollbarMsgID == msgid) {
786 const void *bytes = [data bytes];
787 long ident = *((long*)bytes); bytes += sizeof(long);
789 [windowController destroyScrollbarWithIdentifier:ident];
790 } else if (ShowScrollbarMsgID == msgid) {
791 const void *bytes = [data bytes];
792 long ident = *((long*)bytes); bytes += sizeof(long);
793 int visible = *((int*)bytes); bytes += sizeof(int);
795 [windowController showScrollbarWithIdentifier:ident state:visible];
796 } else if (SetScrollbarPositionMsgID == msgid) {
797 const void *bytes = [data bytes];
798 long ident = *((long*)bytes); bytes += sizeof(long);
799 int pos = *((int*)bytes); bytes += sizeof(int);
800 int len = *((int*)bytes); bytes += sizeof(int);
802 [windowController setScrollbarPosition:pos length:len
804 } else if (SetScrollbarThumbMsgID == msgid) {
805 const void *bytes = [data bytes];
806 long ident = *((long*)bytes); bytes += sizeof(long);
807 float val = *((float*)bytes); bytes += sizeof(float);
808 float prop = *((float*)bytes); bytes += sizeof(float);
810 [windowController setScrollbarThumbValue:val proportion:prop
812 } else if (SetFontMsgID == msgid) {
813 const void *bytes = [data bytes];
814 float size = *((float*)bytes); bytes += sizeof(float);
815 int len = *((int*)bytes); bytes += sizeof(int);
816 NSString *name = [[NSString alloc]
817 initWithBytes:(void*)bytes length:len
818 encoding:NSUTF8StringEncoding];
819 NSFont *font = [NSFont fontWithName:name size:size];
822 [windowController setFont:font];
825 } else if (SetWideFontMsgID == msgid) {
826 const void *bytes = [data bytes];
827 float size = *((float*)bytes); bytes += sizeof(float);
828 int len = *((int*)bytes); bytes += sizeof(int);
830 NSString *name = [[NSString alloc]
831 initWithBytes:(void*)bytes length:len
832 encoding:NSUTF8StringEncoding];
833 NSFont *font = [NSFont fontWithName:name size:size];
834 [windowController setWideFont:font];
838 [windowController setWideFont:nil];
840 } else if (SetDefaultColorsMsgID == msgid) {
841 const void *bytes = [data bytes];
842 unsigned bg = *((unsigned*)bytes); bytes += sizeof(unsigned);
843 unsigned fg = *((unsigned*)bytes); bytes += sizeof(unsigned);
844 NSColor *back = [NSColor colorWithArgbInt:bg];
845 NSColor *fore = [NSColor colorWithRgbInt:fg];
847 [windowController setDefaultColorsBackground:back foreground:fore];
848 } else if (ExecuteActionMsgID == msgid) {
849 const void *bytes = [data bytes];
850 int len = *((int*)bytes); bytes += sizeof(int);
851 NSString *actionName = [[NSString alloc]
852 initWithBytes:(void*)bytes length:len
853 encoding:NSUTF8StringEncoding];
855 SEL sel = NSSelectorFromString(actionName);
856 [NSApp sendAction:sel to:nil from:self];
858 [actionName release];
859 } else if (ShowPopupMenuMsgID == msgid) {
860 const void *bytes = [data bytes];
861 int row = *((int*)bytes); bytes += sizeof(int);
862 int col = *((int*)bytes); bytes += sizeof(int);
863 int len = *((int*)bytes); bytes += sizeof(int);
864 NSString *title = [[NSString alloc]
865 initWithBytes:(void*)bytes length:len
866 encoding:NSUTF8StringEncoding];
868 NSMenu *menu = [self topLevelMenuForTitle:title];
870 [windowController popupMenu:menu atRow:row column:col];
872 NSLog(@"WARNING: Cannot popup menu with title %@; no such menu.",
877 } else if (SetMouseShapeMsgID == msgid) {
878 const void *bytes = [data bytes];
879 int shape = *((int*)bytes); bytes += sizeof(int);
881 [windowController setMouseShape:shape];
882 } else if (AdjustLinespaceMsgID == msgid) {
883 const void *bytes = [data bytes];
884 int linespace = *((int*)bytes); bytes += sizeof(int);
886 [windowController adjustLinespace:linespace];
887 } else if (ActivateMsgID == msgid) {
888 //NSLog(@"ActivateMsgID");
889 [NSApp activateIgnoringOtherApps:YES];
890 [[windowController window] makeKeyAndOrderFront:self];
891 } else if (SetServerNameMsgID == msgid) {
892 NSString *name = [[NSString alloc] initWithData:data
893 encoding:NSUTF8StringEncoding];
894 [self setServerName:name];
896 } else if (EnterFullscreenMsgID == msgid) {
897 const void *bytes = [data bytes];
898 int fuoptions = *((int*)bytes); bytes += sizeof(int);
899 int bg = *((int*)bytes);
900 NSColor *back = [NSColor colorWithArgbInt:bg];
902 [windowController enterFullscreen:fuoptions backgroundColor:back];
903 } else if (LeaveFullscreenMsgID == msgid) {
904 [windowController leaveFullscreen];
905 } else if (BuffersNotModifiedMsgID == msgid) {
906 [windowController setBuffersModified:NO];
907 } else if (BuffersModifiedMsgID == msgid) {
908 [windowController setBuffersModified:YES];
909 } else if (SetPreEditPositionMsgID == msgid) {
910 const int *dim = (const int*)[data bytes];
911 [[[windowController vimView] textView] setPreEditRow:dim[0]
913 } else if (EnableAntialiasMsgID == msgid) {
914 [[[windowController vimView] textView] setAntialias:YES];
915 } else if (DisableAntialiasMsgID == msgid) {
916 [[[windowController vimView] textView] setAntialias:NO];
918 NSLog(@"WARNING: Unknown message received (msgid=%d)", msgid);
922 - (void)savePanelDidEnd:(NSSavePanel *)panel code:(int)code
923 context:(void *)context
925 NSString *string = (code == NSOKButton) ? [panel filename] : nil;
927 [backendProxy setDialogReturn:string];
929 @catch (NSException *e) {
930 NSLog(@"Exception caught in %s %@", _cmd, e);
934 - (void)alertDidEnd:(MMAlert *)alert code:(int)code context:(void *)context
938 code = code - NSAlertFirstButtonReturn + 1;
940 if ([alert isKindOfClass:[MMAlert class]] && [alert textField]) {
941 ret = [NSArray arrayWithObjects:[NSNumber numberWithInt:code],
942 [[alert textField] stringValue], nil];
944 ret = [NSArray arrayWithObject:[NSNumber numberWithInt:code]];
948 [backendProxy setDialogReturn:ret];
950 @catch (NSException *e) {
951 NSLog(@"Exception caught in %s %@", _cmd, e);
955 - (NSMenuItem *)recurseMenuItemForTag:(int)tag rootMenu:(NSMenu *)root
958 NSMenuItem *item = [root itemWithTag:tag];
960 lastMenuSearched = root;
964 NSArray *items = [root itemArray];
965 unsigned i, count = [items count];
966 for (i = 0; i < count; ++i) {
967 item = [items objectAtIndex:i];
968 if ([item hasSubmenu]) {
969 item = [self recurseMenuItemForTag:tag
970 rootMenu:[item submenu]];
972 lastMenuSearched = [item submenu];
982 - (NSMenuItem *)menuItemForTag:(int)tag
984 // First search the same menu that was search last time this method was
985 // called. Since this method is often called for each menu item in a
986 // menu this can significantly improve search times.
987 if (lastMenuSearched) {
988 NSMenuItem *item = [self recurseMenuItemForTag:tag
989 rootMenu:lastMenuSearched];
990 if (item) return item;
993 // Search the main menu.
994 int i, count = [mainMenuItems count];
995 for (i = 0; i < count; ++i) {
996 NSMenuItem *item = [mainMenuItems objectAtIndex:i];
997 if ([item tag] == tag) return item;
998 item = [self recurseMenuItemForTag:tag rootMenu:[item submenu]];
1000 lastMenuSearched = [item submenu];
1005 // Search the popup menus.
1006 count = [popupMenuItems count];
1007 for (i = 0; i < count; ++i) {
1008 NSMenuItem *item = [popupMenuItems objectAtIndex:i];
1009 if ([item tag] == tag) return item;
1010 item = [self recurseMenuItemForTag:tag rootMenu:[item submenu]];
1012 lastMenuSearched = [item submenu];
1020 - (NSMenu *)menuForTag:(int)tag
1022 return [[self menuItemForTag:tag] submenu];
1025 - (NSMenu *)topLevelMenuForTitle:(NSString *)title
1027 // Search only the top-level menus.
1029 unsigned i, count = [popupMenuItems count];
1030 for (i = 0; i < count; ++i) {
1031 NSMenuItem *item = [popupMenuItems objectAtIndex:i];
1032 if ([title isEqual:[item title]])
1033 return [item submenu];
1036 count = [mainMenuItems count];
1037 for (i = 0; i < count; ++i) {
1038 NSMenuItem *item = [mainMenuItems objectAtIndex:i];
1039 if ([title isEqual:[item title]])
1040 return [item submenu];
1046 - (void)addMenuWithTag:(int)tag parent:(int)parentTag title:(NSString *)title
1049 NSMenu *parent = [self menuForTag:parentTag];
1050 NSMenuItem *item = [[NSMenuItem alloc] init];
1051 NSMenu *menu = [[NSMenu alloc] initWithTitle:title];
1053 [menu setAutoenablesItems:NO];
1055 [item setTitle:title];
1056 [item setSubmenu:menu];
1059 if ([parent numberOfItems] <= idx) {
1060 [parent addItem:item];
1062 [parent insertItem:item atIndex:idx];
1065 NSMutableArray *items = (MenuPopupType == parentTag)
1066 ? popupMenuItems : mainMenuItems;
1067 if ([items count] <= idx) {
1068 [items addObject:item];
1070 [items insertObject:item atIndex:idx];
1073 shouldUpdateMainMenu = (MenuPopupType != parentTag);
1080 - (void)addMenuItemWithTag:(int)tag parent:(NSMenu *)parent
1081 title:(NSString *)title tip:(NSString *)tip
1082 keyEquivalent:(int)key modifiers:(int)mask
1083 action:(NSString *)action atIndex:(int)idx
1086 NSMenuItem *item = nil;
1087 if (!title || ([title hasPrefix:@"-"] && [title hasSuffix:@"-"])) {
1088 item = [NSMenuItem separatorItem];
1090 item = [[[NSMenuItem alloc] init] autorelease];
1091 [item setTitle:title];
1093 if ([action isEqualToString:@"recentFilesDummy:"]) {
1094 // Remove the recent files menu item from its current menu
1095 // and put it in the current file menu. See -[MMAppController
1096 // applicationWillFinishLaunching for more information.
1097 //[[recentFilesMenuItem menu] removeItem:recentFilesMenuItem];
1098 //item = recentFilesMenuItem;
1099 recentFilesDummy = [item retain];
1102 // TODO: Check that 'action' is a valid action (nothing will
1103 // happen if it isn't, but it would be nice with a warning).
1104 if (action) [item setAction:NSSelectorFromString(action)];
1105 else [item setAction:@selector(vimMenuItemAction:)];
1106 if (tip) [item setToolTip:tip];
1109 NSString *keyString =
1110 [NSString stringWithFormat:@"%C", key];
1111 [item setKeyEquivalent:keyString];
1112 [item setKeyEquivalentModifierMask:mask];
1117 // NOTE! The tag is used to idenfity which menu items were
1118 // added by Vim (tag != 0) and which were added by the AppKit
1122 if ([parent numberOfItems] <= idx) {
1123 [parent addItem:item];
1125 [parent insertItem:item atIndex:idx];
1128 NSLog(@"WARNING: Menu item '%@' (tag=%d) has no parent.", title, tag);
1132 - (NSToolbarItem *)toolbarItemForTag:(int)tag index:(int *)index
1134 if (!toolbar) return nil;
1136 NSArray *items = [toolbar items];
1137 int i, count = [items count];
1138 for (i = 0; i < count; ++i) {
1139 NSToolbarItem *item = [items objectAtIndex:i];
1140 if ([item tag] == tag) {
1141 if (index) *index = i;
1149 - (void)addToolbarItemToDictionaryWithTag:(int)tag label:(NSString *)title
1150 toolTip:(NSString *)tip icon:(NSString *)icon
1152 // If the item corresponds to a separator then do nothing, since it is
1153 // already defined by Cocoa.
1154 if (!title || [title isEqual:NSToolbarSeparatorItemIdentifier]
1155 || [title isEqual:NSToolbarSpaceItemIdentifier]
1156 || [title isEqual:NSToolbarFlexibleSpaceItemIdentifier])
1159 NSToolbarItem *item = [[NSToolbarItem alloc] initWithItemIdentifier:title];
1161 [item setLabel:title];
1162 [item setToolTip:tip];
1163 [item setAction:@selector(vimMenuItemAction:)];
1164 [item setAutovalidates:NO];
1166 NSImage *img = [NSImage imageNamed:icon];
1168 NSLog(@"WARNING: Could not find image with name '%@' to use as toolbar"
1169 " image for identifier '%@';"
1170 " using default toolbar icon '%@' instead.",
1171 icon, title, MMDefaultToolbarImageName);
1173 img = [NSImage imageNamed:MMDefaultToolbarImageName];
1176 [item setImage:img];
1178 [toolbarItemDict setObject:item forKey:title];
1183 - (void)addToolbarItemWithTag:(int)tag label:(NSString *)label tip:(NSString
1184 *)tip icon:(NSString *)icon atIndex:(int)idx
1186 if (!toolbar) return;
1188 // Check for separator items.
1190 label = NSToolbarSeparatorItemIdentifier;
1191 } else if ([label length] >= 2 && [label hasPrefix:@"-"]
1192 && [label hasSuffix:@"-"]) {
1193 // The label begins and ends with '-'; decided which kind of separator
1194 // item it is by looking at the prefix.
1195 if ([label hasPrefix:@"-space"]) {
1196 label = NSToolbarSpaceItemIdentifier;
1197 } else if ([label hasPrefix:@"-flexspace"]) {
1198 label = NSToolbarFlexibleSpaceItemIdentifier;
1200 label = NSToolbarSeparatorItemIdentifier;
1204 [self addToolbarItemToDictionaryWithTag:tag label:label toolTip:tip
1207 int maxIdx = [[toolbar items] count];
1208 if (maxIdx < idx) idx = maxIdx;
1210 [toolbar insertItemWithItemIdentifier:label atIndex:idx];
1213 - (void)connectionDidDie:(NSNotification *)notification
1215 //NSLog(@"%@ %s%@", [self className], _cmd, notification);
1219 // NOTE! This causes the call to removeVimController: to be delayed.
1221 performSelectorOnMainThread:@selector(removeVimController:)
1222 withObject:self waitUntilDone:NO];
1225 - (NSString *)description
1227 return [NSString stringWithFormat:@"%@ : isInitialized=%d inProcessCommandQueue=%d mainMenuItems=%@ popupMenuItems=%@ toolbar=%@", [self className], isInitialized, inProcessCommandQueue, mainMenuItems, popupMenuItems, toolbar];
1230 #if MM_RESEND_LAST_FAILURE
1231 - (void)resendTimerFired:(NSTimer *)timer
1233 int msgid = resendMsgid;
1236 [resendTimer release];
1243 data = [resendData copy];
1245 //NSLog(@"Resending message: %s", MessageStrings[msgid]);
1246 [self sendMessage:msgid data:data];
1250 - (void)replaceMenuItem:(NSMenuItem*)old with:(NSMenuItem*)new
1252 NSMenu *menu = [old menu];
1253 int index = [menu indexOfItem:old];
1254 [menu removeItemAtIndex:index];
1255 [menu insertItem:new atIndex:index];
1258 @end // MMVimController (Private)
1262 @implementation MMAlert
1265 [textField release]; textField = nil;
1269 - (void)setTextFieldString:(NSString *)textFieldString
1271 [textField release];
1272 textField = [[NSTextField alloc] init];
1273 [textField setStringValue:textFieldString];
1276 - (NSTextField *)textField
1281 - (void)setInformativeText:(NSString *)text
1284 // HACK! Add some space for the text field.
1285 [super setInformativeText:[text stringByAppendingString:@"\n\n\n"]];
1287 [super setInformativeText:text];
1291 - (void)beginSheetModalForWindow:(NSWindow *)window
1292 modalDelegate:(id)delegate
1293 didEndSelector:(SEL)didEndSelector
1294 contextInfo:(void *)contextInfo
1296 [super beginSheetModalForWindow:window
1297 modalDelegate:delegate
1298 didEndSelector:didEndSelector
1299 contextInfo:contextInfo];
1301 // HACK! Place the input text field at the bottom of the informative text
1302 // (which has been made a bit larger by adding newline characters).
1303 NSView *contentView = [[self window] contentView];
1304 NSRect rect = [contentView frame];
1305 rect.origin.y = rect.size.height;
1307 NSArray *subviews = [contentView subviews];
1308 unsigned i, count = [subviews count];
1309 for (i = 0; i < count; ++i) {
1310 NSView *view = [subviews objectAtIndex:i];
1311 if ([view isKindOfClass:[NSTextField class]]
1312 && [view frame].origin.y < rect.origin.y) {
1313 // NOTE: The informative text field is the lowest NSTextField in
1314 // the alert dialog.
1315 rect = [view frame];
1319 rect.size.height = MMAlertTextFieldHeight;
1320 [textField setFrame:rect];
1321 [contentView addSubview:textField];
1322 [textField becomeFirstResponder];