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 - (void)updateMainMenu;
75 - (NSToolbarItem *)toolbarItemForTag:(int)tag index:(int *)index;
76 - (void)addToolbarItemToDictionaryWithTag:(int)tag label:(NSString *)title
77 toolTip:(NSString *)tip icon:(NSString *)icon;
78 - (void)addToolbarItemWithTag:(int)tag label:(NSString *)label
79 tip:(NSString *)tip icon:(NSString *)icon
81 - (void)connectionDidDie:(NSNotification *)notification;
82 #if MM_RESEND_LAST_FAILURE
83 - (void)resendTimerFired:(NSTimer *)timer;
90 @implementation MMVimController
92 - (id)initWithBackend:(id)backend pid:(int)processIdentifier
94 if ((self = [super init])) {
96 [[MMWindowController alloc] initWithVimController:self];
97 backendProxy = [backend retain];
98 sendQueue = [NSMutableArray new];
99 mainMenuItems = [[NSMutableArray alloc] init];
100 popupMenuItems = [[NSMutableArray alloc] init];
101 toolbarItemDict = [[NSMutableDictionary alloc] init];
102 pid = processIdentifier;
104 NSConnection *connection = [backendProxy connectionForProxy];
106 // TODO: Check that this will not set the timeout for the root proxy
107 // (in MMAppController).
108 [connection setRequestTimeout:MMBackendProxyRequestTimeout];
110 [[NSNotificationCenter defaultCenter] addObserver:self
111 selector:@selector(connectionDidDie:)
112 name:NSConnectionDidDieNotification object:connection];
115 // TODO: What if [windowController window] is the full-screen window?
116 [[NSNotificationCenter defaultCenter]
118 selector:@selector(windowDidBecomeMain:)
119 name:NSWindowDidBecomeMainNotification
120 object:[windowController window]];
130 //NSLog(@"%@ %s", [self className], _cmd);
133 #if MM_RESEND_LAST_FAILURE
134 [resendData release]; resendData = nil;
137 [serverName release]; serverName = nil;
138 [backendProxy release]; backendProxy = nil;
139 [sendQueue release]; sendQueue = nil;
141 [toolbarItemDict release]; toolbarItemDict = nil;
142 [toolbar release]; toolbar = nil;
143 [popupMenuItems release]; popupMenuItems = nil;
144 [mainMenuItems release]; mainMenuItems = nil;
145 [windowController release]; windowController = nil;
150 - (MMWindowController *)windowController
152 return windowController;
155 - (void)setServerName:(NSString *)name
157 if (name != serverName) {
158 [serverName release];
159 serverName = [name copy];
163 - (NSString *)serverName
173 - (void)dropFiles:(NSArray *)filenames forceOpen:(BOOL)force
175 unsigned i, numberOfFiles = [filenames count];
176 NSMutableData *data = [NSMutableData data];
178 [data appendBytes:&force length:sizeof(BOOL)];
179 [data appendBytes:&numberOfFiles length:sizeof(int)];
181 for (i = 0; i < numberOfFiles; ++i) {
182 NSString *file = [filenames objectAtIndex:i];
183 int len = [file lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
186 ++len; // include NUL as well
187 [data appendBytes:&len length:sizeof(int)];
188 [data appendBytes:[file UTF8String] length:len];
192 [self sendMessage:DropFilesMsgID data:data];
195 - (void)dropString:(NSString *)string
197 int len = [string lengthOfBytesUsingEncoding:NSUTF8StringEncoding] + 1;
199 NSMutableData *data = [NSMutableData data];
201 [data appendBytes:&len length:sizeof(int)];
202 [data appendBytes:[string UTF8String] length:len];
204 [self sendMessage:DropStringMsgID data:data];
208 - (void)odbEdit:(NSArray *)filenames server:(OSType)theID path:(NSString *)path
209 token:(NSAppleEventDescriptor *)token
212 unsigned i, numberOfFiles = [filenames count];
213 NSMutableData *data = [NSMutableData data];
215 if (0 == numberOfFiles || 0 == theID)
218 [data appendBytes:&theID length:sizeof(theID)];
220 if (path && [path length] > 0) {
221 len = [path lengthOfBytesUsingEncoding:NSUTF8StringEncoding] + 1;
222 [data appendBytes:&len length:sizeof(int)];
223 [data appendBytes:[path UTF8String] length:len];
226 [data appendBytes:&len length:sizeof(int)];
230 DescType tokenType = [token descriptorType];
231 NSData *tokenData = [token data];
232 len = [tokenData length];
234 [data appendBytes:&tokenType length:sizeof(tokenType)];
235 [data appendBytes:&len length:sizeof(int)];
237 [data appendBytes:[tokenData bytes] length:len];
239 DescType tokenType = 0;
241 [data appendBytes:&tokenType length:sizeof(tokenType)];
242 [data appendBytes:&len length:sizeof(int)];
245 [data appendBytes:&numberOfFiles length:sizeof(int)];
247 for (i = 0; i < numberOfFiles; ++i) {
248 NSString *file = [filenames objectAtIndex:i];
249 len = [file lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
252 ++len; // include NUL as well
253 [data appendBytes:&len length:sizeof(unsigned)];
254 [data appendBytes:[file UTF8String] length:len];
258 [self sendMessage:ODBEditMsgID data:data];
261 - (void)sendMessage:(int)msgid data:(NSData *)data
263 //NSLog(@"sendMessage:%s (isInitialized=%d inProcessCommandQueue=%d)",
264 // MessageStrings[msgid], isInitialized, inProcessCommandQueue);
266 if (!isInitialized) return;
268 if (inProcessCommandQueue) {
269 //NSLog(@"In process command queue; delaying message send.");
270 [sendQueue addObject:[NSNumber numberWithInt:msgid]];
272 [sendQueue addObject:data];
274 [sendQueue addObject:[NSNull null]];
278 #if MM_RESEND_LAST_FAILURE
280 //NSLog(@"cancelling scheduled resend of %s",
281 // MessageStrings[resendMsgid]);
283 [resendTimer invalidate];
284 [resendTimer release];
289 [resendData release];
295 [backendProxy processInput:msgid data:data];
297 @catch (NSException *e) {
298 //NSLog(@"%@ %s Exception caught during DO call: %@",
299 // [self className], _cmd, e);
300 #if MM_RESEND_LAST_FAILURE
301 //NSLog(@"%s failed, scheduling message %s for resend", _cmd,
302 // MessageStrings[msgid]);
305 resendData = [data retain];
306 resendTimer = [NSTimer
307 scheduledTimerWithTimeInterval:MMResendInterval
309 selector:@selector(resendTimerFired:)
312 [resendTimer retain];
317 - (BOOL)sendMessageNow:(int)msgid data:(NSData *)data
318 timeout:(NSTimeInterval)timeout
320 // Send a message with a timeout. USE WITH EXTREME CAUTION! Sending
321 // messages in rapid succession with a timeout may cause MacVim to beach
322 // ball forever. In almost all circumstances sendMessage:data: should be
325 if (!isInitialized || inProcessCommandQueue)
328 if (timeout < 0) timeout = 0;
331 NSConnection *conn = [backendProxy connectionForProxy];
332 NSTimeInterval oldTimeout = [conn requestTimeout];
334 [conn setRequestTimeout:timeout];
337 [backendProxy processInput:msgid data:data];
339 @catch (NSException *e) {
343 [conn setRequestTimeout:oldTimeout];
349 - (void)addVimInput:(NSString *)string
351 // This is a very general method of adding input to the Vim process. It is
352 // basically the same as calling remote_send() on the process (see
353 // ':h remote_send').
355 NSData *data = [string dataUsingEncoding:NSUTF8StringEncoding];
356 [self sendMessage:AddInputMsgID data:data];
367 //NSLog(@"%@ %s", [self className], _cmd);
368 if (!isInitialized) return;
371 [toolbar setDelegate:nil];
372 [[NSNotificationCenter defaultCenter] removeObserver:self];
373 [windowController cleanup];
376 - (oneway void)showSavePanelForDirectory:(in bycopy NSString *)dir
377 title:(in bycopy NSString *)title
380 if (!isInitialized) return;
383 [[NSSavePanel savePanel] beginSheetForDirectory:dir file:nil
384 modalForWindow:[windowController window]
386 didEndSelector:@selector(savePanelDidEnd:code:context:)
389 NSOpenPanel *panel = [NSOpenPanel openPanel];
390 [panel setAllowsMultipleSelection:NO];
391 [panel beginSheetForDirectory:dir file:nil types:nil
392 modalForWindow:[windowController window]
394 didEndSelector:@selector(savePanelDidEnd:code:context:)
399 - (oneway void)presentDialogWithStyle:(int)style
400 message:(in bycopy NSString *)message
401 informativeText:(in bycopy NSString *)text
402 buttonTitles:(in bycopy NSArray *)buttonTitles
403 textFieldString:(in bycopy NSString *)textFieldString
405 if (!(windowController && buttonTitles && [buttonTitles count])) return;
407 MMAlert *alert = [[MMAlert alloc] init];
409 // NOTE! This has to be done before setting the informative text.
411 [alert setTextFieldString:textFieldString];
413 [alert setAlertStyle:style];
416 [alert setMessageText:message];
418 // If no message text is specified 'Alert' is used, which we don't
419 // want, so set an empty string as message text.
420 [alert setMessageText:@""];
424 [alert setInformativeText:text];
425 } else if (textFieldString) {
426 // Make sure there is always room for the input text field.
427 [alert setInformativeText:@""];
430 unsigned i, count = [buttonTitles count];
431 for (i = 0; i < count; ++i) {
432 NSString *title = [buttonTitles objectAtIndex:i];
433 // NOTE: The title of the button may contain the character '&' to
434 // indicate that the following letter should be the key equivalent
435 // associated with the button. Extract this letter and lowercase it.
436 NSString *keyEquivalent = nil;
437 NSRange hotkeyRange = [title rangeOfString:@"&"];
438 if (NSNotFound != hotkeyRange.location) {
439 if ([title length] > NSMaxRange(hotkeyRange)) {
440 NSRange keyEquivRange = NSMakeRange(hotkeyRange.location+1, 1);
441 keyEquivalent = [[title substringWithRange:keyEquivRange]
445 NSMutableString *string = [NSMutableString stringWithString:title];
446 [string deleteCharactersInRange:hotkeyRange];
450 [alert addButtonWithTitle:title];
452 // Set key equivalent for the button, but only if NSAlert hasn't
453 // already done so. (Check the documentation for
454 // - [NSAlert addButtonWithTitle:] to see what key equivalents are
455 // automatically assigned.)
456 NSButton *btn = [[alert buttons] lastObject];
457 if ([[btn keyEquivalent] length] == 0 && keyEquivalent) {
458 [btn setKeyEquivalent:keyEquivalent];
462 [alert beginSheetModalForWindow:[windowController window]
464 didEndSelector:@selector(alertDidEnd:code:context:)
470 - (oneway void)processCommandQueue:(in bycopy NSArray *)queue
472 if (!isInitialized) return;
474 unsigned i, count = [queue count];
476 NSLog(@"WARNING: Uneven number of components (%d) in flush queue "
477 "message; ignoring this message.", count);
481 inProcessCommandQueue = YES;
483 //NSLog(@"======== %s BEGIN ========", _cmd);
484 for (i = 0; i < count; i += 2) {
485 NSData *value = [queue objectAtIndex:i];
486 NSData *data = [queue objectAtIndex:i+1];
488 int msgid = *((int*)[value bytes]);
489 //NSLog(@"%s%s", _cmd, MessageStrings[msgid]);
491 [self handleMessage:msgid data:data];
493 //NSLog(@"======== %s END ========", _cmd);
495 if (shouldUpdateMainMenu) {
496 [self updateMainMenu];
499 [windowController processCommandQueueDidFinish];
501 inProcessCommandQueue = NO;
503 if ([sendQueue count] > 0) {
505 [backendProxy processInputAndData:sendQueue];
507 @catch (NSException *e) {
508 // Connection timed out, just ignore this.
509 //NSLog(@"WARNING! Connection timed out in %s", _cmd);
512 [sendQueue removeAllObjects];
516 - (void)windowDidBecomeMain:(NSNotification *)notification
519 [self updateMainMenu];
522 - (NSToolbarItem *)toolbar:(NSToolbar *)theToolbar
523 itemForItemIdentifier:(NSString *)itemId
524 willBeInsertedIntoToolbar:(BOOL)flag
526 NSToolbarItem *item = [toolbarItemDict objectForKey:itemId];
528 NSLog(@"WARNING: No toolbar item with id '%@'", itemId);
534 - (NSArray *)toolbarAllowedItemIdentifiers:(NSToolbar *)theToolbar
539 - (NSArray *)toolbarDefaultItemIdentifiers:(NSToolbar *)theToolbar
544 @end // MMVimController
548 @implementation MMVimController (Private)
550 - (void)handleMessage:(int)msgid data:(NSData *)data
552 //if (msgid != AddMenuMsgID && msgid != AddMenuItemMsgID)
553 // NSLog(@"%@ %s%s", [self className], _cmd, MessageStrings[msgid]);
555 if (OpenVimWindowMsgID == msgid) {
556 [windowController openWindow];
557 } else if (BatchDrawMsgID == msgid) {
558 [[[windowController vimView] textView] performBatchDrawWithData:data];
559 } else if (SelectTabMsgID == msgid) {
560 #if 0 // NOTE: Tab selection is done inside updateTabsWithData:.
561 const void *bytes = [data bytes];
562 int idx = *((int*)bytes);
563 //NSLog(@"Selecting tab with index %d", idx);
564 [windowController selectTabWithIndex:idx];
566 } else if (UpdateTabBarMsgID == msgid) {
567 [windowController updateTabsWithData:data];
568 } else if (ShowTabBarMsgID == msgid) {
569 [windowController showTabBar:YES];
570 } else if (HideTabBarMsgID == msgid) {
571 [windowController showTabBar:NO];
572 } else if (SetTextDimensionsMsgID == msgid || LiveResizeMsgID == msgid) {
573 const void *bytes = [data bytes];
574 int rows = *((int*)bytes); bytes += sizeof(int);
575 int cols = *((int*)bytes); bytes += sizeof(int);
577 [windowController setTextDimensionsWithRows:rows columns:cols
578 live:(LiveResizeMsgID==msgid)];
579 } else if (SetWindowTitleMsgID == msgid) {
580 const void *bytes = [data bytes];
581 int len = *((int*)bytes); bytes += sizeof(int);
583 NSString *string = [[NSString alloc] initWithBytes:(void*)bytes
584 length:len encoding:NSUTF8StringEncoding];
586 [windowController setTitle:string];
589 } else if (AddMenuMsgID == msgid) {
590 NSString *title = nil;
591 const void *bytes = [data bytes];
592 int tag = *((int*)bytes); bytes += sizeof(int);
593 int parentTag = *((int*)bytes); bytes += sizeof(int);
594 int len = *((int*)bytes); bytes += sizeof(int);
596 title = [[NSString alloc] initWithBytes:(void*)bytes length:len
597 encoding:NSUTF8StringEncoding];
600 int idx = *((int*)bytes); bytes += sizeof(int);
602 if (MenuToolbarType == parentTag) {
604 // NOTE! Each toolbar must have a unique identifier, else each
605 // window will have the same toolbar.
606 NSString *ident = [NSString stringWithFormat:@"%d.%d",
608 toolbar = [[NSToolbar alloc] initWithIdentifier:ident];
610 [toolbar setShowsBaselineSeparator:NO];
611 [toolbar setDelegate:self];
612 [toolbar setDisplayMode:NSToolbarDisplayModeIconOnly];
613 [toolbar setSizeMode:NSToolbarSizeModeSmall];
615 [windowController setToolbar:toolbar];
618 [self addMenuWithTag:tag parent:parentTag title:title atIndex:idx];
622 } else if (AddMenuItemMsgID == msgid) {
623 NSString *title = nil, *tip = nil, *icon = nil, *action = nil;
624 const void *bytes = [data bytes];
625 int tag = *((int*)bytes); bytes += sizeof(int);
626 int parentTag = *((int*)bytes); bytes += sizeof(int);
627 int namelen = *((int*)bytes); bytes += sizeof(int);
629 title = [[NSString alloc] initWithBytes:(void*)bytes length:namelen
630 encoding:NSUTF8StringEncoding];
633 int tiplen = *((int*)bytes); bytes += sizeof(int);
635 tip = [[NSString alloc] initWithBytes:(void*)bytes length:tiplen
636 encoding:NSUTF8StringEncoding];
639 int iconlen = *((int*)bytes); bytes += sizeof(int);
641 icon = [[NSString alloc] initWithBytes:(void*)bytes length:iconlen
642 encoding:NSUTF8StringEncoding];
645 int actionlen = *((int*)bytes); bytes += sizeof(int);
647 action = [[NSString alloc] initWithBytes:(void*)bytes
649 encoding:NSUTF8StringEncoding];
652 int idx = *((int*)bytes); bytes += sizeof(int);
653 if (idx < 0) idx = 0;
654 int key = *((int*)bytes); bytes += sizeof(int);
655 int mask = *((int*)bytes); bytes += sizeof(int);
657 NSString *ident = [NSString stringWithFormat:@"%d.%d",
658 (int)self, parentTag];
659 if (toolbar && [[toolbar identifier] isEqual:ident]) {
660 [self addToolbarItemWithTag:tag label:title tip:tip icon:icon
663 NSMenu *parent = [self menuForTag:parentTag];
664 [self addMenuItemWithTag:tag parent:parent title:title tip:tip
665 keyEquivalent:key modifiers:mask action:action
673 } else if (RemoveMenuItemMsgID == msgid) {
674 const void *bytes = [data bytes];
675 int tag = *((int*)bytes); bytes += sizeof(int);
679 if ((item = [self toolbarItemForTag:tag index:&idx])) {
680 [toolbar removeItemAtIndex:idx];
681 } else if ((item = [self menuItemForTag:tag])) {
684 if ([item menu] == [NSApp mainMenu] || ![item menu]) {
685 // NOTE: To be on the safe side we try to remove the item from
686 // both arrays (it is ok to call removeObject: even if an array
687 // does not contain the object to remove).
688 [mainMenuItems removeObject:item];
689 [popupMenuItems removeObject:item];
693 [[item menu] removeItem:item];
698 // Reset cached menu, just to be on the safe side.
699 lastMenuSearched = nil;
700 } else if (EnableMenuItemMsgID == msgid) {
701 const void *bytes = [data bytes];
702 int tag = *((int*)bytes); bytes += sizeof(int);
703 int state = *((int*)bytes); bytes += sizeof(int);
705 id item = [self toolbarItemForTag:tag index:NULL];
707 item = [self menuItemForTag:tag];
709 [item setEnabled:state];
710 } else if (ShowToolbarMsgID == msgid) {
711 const void *bytes = [data bytes];
712 int enable = *((int*)bytes); bytes += sizeof(int);
713 int flags = *((int*)bytes); bytes += sizeof(int);
715 int mode = NSToolbarDisplayModeDefault;
716 if (flags & ToolbarLabelFlag) {
717 mode = flags & ToolbarIconFlag ? NSToolbarDisplayModeIconAndLabel
718 : NSToolbarDisplayModeLabelOnly;
719 } else if (flags & ToolbarIconFlag) {
720 mode = NSToolbarDisplayModeIconOnly;
723 int size = flags & ToolbarSizeRegularFlag ? NSToolbarSizeModeRegular
724 : NSToolbarSizeModeSmall;
726 [windowController showToolbar:enable size:size mode:mode];
727 } else if (CreateScrollbarMsgID == msgid) {
728 const void *bytes = [data bytes];
729 long ident = *((long*)bytes); bytes += sizeof(long);
730 int type = *((int*)bytes); bytes += sizeof(int);
732 [windowController createScrollbarWithIdentifier:ident type:type];
733 } else if (DestroyScrollbarMsgID == msgid) {
734 const void *bytes = [data bytes];
735 long ident = *((long*)bytes); bytes += sizeof(long);
737 [windowController destroyScrollbarWithIdentifier:ident];
738 } else if (ShowScrollbarMsgID == msgid) {
739 const void *bytes = [data bytes];
740 long ident = *((long*)bytes); bytes += sizeof(long);
741 int visible = *((int*)bytes); bytes += sizeof(int);
743 [windowController showScrollbarWithIdentifier:ident state:visible];
744 } else if (SetScrollbarPositionMsgID == msgid) {
745 const void *bytes = [data bytes];
746 long ident = *((long*)bytes); bytes += sizeof(long);
747 int pos = *((int*)bytes); bytes += sizeof(int);
748 int len = *((int*)bytes); bytes += sizeof(int);
750 [windowController setScrollbarPosition:pos length:len
752 } else if (SetScrollbarThumbMsgID == msgid) {
753 const void *bytes = [data bytes];
754 long ident = *((long*)bytes); bytes += sizeof(long);
755 float val = *((float*)bytes); bytes += sizeof(float);
756 float prop = *((float*)bytes); bytes += sizeof(float);
758 [windowController setScrollbarThumbValue:val proportion:prop
760 } else if (SetFontMsgID == msgid) {
761 const void *bytes = [data bytes];
762 float size = *((float*)bytes); bytes += sizeof(float);
763 int len = *((int*)bytes); bytes += sizeof(int);
764 NSString *name = [[NSString alloc]
765 initWithBytes:(void*)bytes length:len
766 encoding:NSUTF8StringEncoding];
767 NSFont *font = [NSFont fontWithName:name size:size];
770 [windowController setFont:font];
773 } else if (SetWideFontMsgID == msgid) {
774 const void *bytes = [data bytes];
775 float size = *((float*)bytes); bytes += sizeof(float);
776 int len = *((int*)bytes); bytes += sizeof(int);
778 NSString *name = [[NSString alloc]
779 initWithBytes:(void*)bytes length:len
780 encoding:NSUTF8StringEncoding];
781 NSFont *font = [NSFont fontWithName:name size:size];
782 [windowController setWideFont:font];
786 [windowController setWideFont:nil];
788 } else if (SetDefaultColorsMsgID == msgid) {
789 const void *bytes = [data bytes];
790 unsigned bg = *((unsigned*)bytes); bytes += sizeof(unsigned);
791 unsigned fg = *((unsigned*)bytes); bytes += sizeof(unsigned);
792 NSColor *back = [NSColor colorWithArgbInt:bg];
793 NSColor *fore = [NSColor colorWithRgbInt:fg];
795 [windowController setDefaultColorsBackground:back foreground:fore];
796 } else if (ExecuteActionMsgID == msgid) {
797 const void *bytes = [data bytes];
798 int len = *((int*)bytes); bytes += sizeof(int);
799 NSString *actionName = [[NSString alloc]
800 initWithBytesNoCopy:(void*)bytes
802 encoding:NSUTF8StringEncoding
805 SEL sel = NSSelectorFromString(actionName);
806 [NSApp sendAction:sel to:nil from:self];
808 [actionName release];
809 } else if (ShowPopupMenuMsgID == msgid) {
810 const void *bytes = [data bytes];
811 int row = *((int*)bytes); bytes += sizeof(int);
812 int col = *((int*)bytes); bytes += sizeof(int);
813 int len = *((int*)bytes); bytes += sizeof(int);
814 NSString *title = [[NSString alloc]
815 initWithBytesNoCopy:(void*)bytes
817 encoding:NSUTF8StringEncoding
820 NSMenu *menu = [self topLevelMenuForTitle:title];
822 [windowController popupMenu:menu atRow:row column:col];
824 NSLog(@"WARNING: Cannot popup menu with title %@; no such menu.",
829 } else if (SetMouseShapeMsgID == msgid) {
830 const void *bytes = [data bytes];
831 int shape = *((int*)bytes); bytes += sizeof(int);
833 [windowController setMouseShape:shape];
834 } else if (AdjustLinespaceMsgID == msgid) {
835 const void *bytes = [data bytes];
836 int linespace = *((int*)bytes); bytes += sizeof(int);
838 [windowController adjustLinespace:linespace];
839 } else if (ActivateMsgID == msgid) {
840 //NSLog(@"ActivateMsgID");
841 [NSApp activateIgnoringOtherApps:YES];
842 [[windowController window] makeKeyAndOrderFront:self];
843 } else if (SetServerNameMsgID == msgid) {
844 NSString *name = [[NSString alloc] initWithData:data
845 encoding:NSUTF8StringEncoding];
846 [self setServerName:name];
848 } else if (EnterFullscreenMsgID == msgid) {
849 [windowController enterFullscreen];
850 } else if (LeaveFullscreenMsgID == msgid) {
851 [windowController leaveFullscreen];
852 } else if (BuffersNotModifiedMsgID == msgid) {
853 [windowController setBuffersModified:NO];
854 } else if (BuffersModifiedMsgID == msgid) {
855 [windowController setBuffersModified:YES];
856 } else if (SetPreEditPositionMsgID == msgid) {
857 const int *dim = (const int*)[data bytes];
858 [[[windowController vimView] textView] setPreEditRow:dim[0]
861 NSLog(@"WARNING: Unknown message received (msgid=%d)", msgid);
865 - (void)savePanelDidEnd:(NSSavePanel *)panel code:(int)code
866 context:(void *)context
868 NSString *string = (code == NSOKButton) ? [panel filename] : nil;
870 [backendProxy setDialogReturn:string];
872 @catch (NSException *e) {
873 NSLog(@"Exception caught in %s %@", _cmd, e);
877 - (void)alertDidEnd:(MMAlert *)alert code:(int)code context:(void *)context
881 code = code - NSAlertFirstButtonReturn + 1;
883 if ([alert isKindOfClass:[MMAlert class]] && [alert textField]) {
884 ret = [NSArray arrayWithObjects:[NSNumber numberWithInt:code],
885 [[alert textField] stringValue], nil];
887 ret = [NSArray arrayWithObject:[NSNumber numberWithInt:code]];
891 [backendProxy setDialogReturn:ret];
893 @catch (NSException *e) {
894 NSLog(@"Exception caught in %s %@", _cmd, e);
898 - (NSMenuItem *)recurseMenuItemForTag:(int)tag rootMenu:(NSMenu *)root
901 NSMenuItem *item = [root itemWithTag:tag];
903 lastMenuSearched = root;
907 NSArray *items = [root itemArray];
908 unsigned i, count = [items count];
909 for (i = 0; i < count; ++i) {
910 item = [items objectAtIndex:i];
911 if ([item hasSubmenu]) {
912 item = [self recurseMenuItemForTag:tag
913 rootMenu:[item submenu]];
915 lastMenuSearched = [item submenu];
925 - (NSMenuItem *)menuItemForTag:(int)tag
927 // First search the same menu that was search last time this method was
928 // called. Since this method is often called for each menu item in a
929 // menu this can significantly improve search times.
930 if (lastMenuSearched) {
931 NSMenuItem *item = [self recurseMenuItemForTag:tag
932 rootMenu:lastMenuSearched];
933 if (item) return item;
936 // Search the main menu.
937 int i, count = [mainMenuItems count];
938 for (i = 0; i < count; ++i) {
939 NSMenuItem *item = [mainMenuItems objectAtIndex:i];
940 if ([item tag] == tag) return item;
941 item = [self recurseMenuItemForTag:tag rootMenu:[item submenu]];
943 lastMenuSearched = [item submenu];
948 // Search the popup menus.
949 count = [popupMenuItems count];
950 for (i = 0; i < count; ++i) {
951 NSMenuItem *item = [popupMenuItems objectAtIndex:i];
952 if ([item tag] == tag) return item;
953 item = [self recurseMenuItemForTag:tag rootMenu:[item submenu]];
955 lastMenuSearched = [item submenu];
963 - (NSMenu *)menuForTag:(int)tag
965 return [[self menuItemForTag:tag] submenu];
968 - (NSMenu *)topLevelMenuForTitle:(NSString *)title
970 // Search only the top-level menus.
972 unsigned i, count = [popupMenuItems count];
973 for (i = 0; i < count; ++i) {
974 NSMenuItem *item = [popupMenuItems objectAtIndex:i];
975 if ([title isEqual:[item title]])
976 return [item submenu];
979 count = [mainMenuItems count];
980 for (i = 0; i < count; ++i) {
981 NSMenuItem *item = [mainMenuItems objectAtIndex:i];
982 if ([title isEqual:[item title]])
983 return [item submenu];
989 - (void)addMenuWithTag:(int)tag parent:(int)parentTag title:(NSString *)title
992 NSMenu *parent = [self menuForTag:parentTag];
993 NSMenuItem *item = [[NSMenuItem alloc] init];
994 NSMenu *menu = [[NSMenu alloc] initWithTitle:title];
996 [menu setAutoenablesItems:NO];
998 [item setTitle:title];
999 [item setSubmenu:menu];
1002 if ([parent numberOfItems] <= idx) {
1003 [parent addItem:item];
1005 [parent insertItem:item atIndex:idx];
1008 NSMutableArray *items = (MenuPopupType == parentTag)
1009 ? popupMenuItems : mainMenuItems;
1010 if ([items count] <= idx) {
1011 [items addObject:item];
1013 [items insertObject:item atIndex:idx];
1016 shouldUpdateMainMenu = (MenuPopupType != parentTag);
1023 - (void)addMenuItemWithTag:(int)tag parent:(NSMenu *)parent
1024 title:(NSString *)title tip:(NSString *)tip
1025 keyEquivalent:(int)key modifiers:(int)mask
1026 action:(NSString *)action atIndex:(int)idx
1029 NSMenuItem *item = nil;
1030 if (!title || ([title hasPrefix:@"-"] && [title hasSuffix:@"-"])) {
1031 item = [NSMenuItem separatorItem];
1033 item = [[[NSMenuItem alloc] init] autorelease];
1034 [item setTitle:title];
1035 // TODO: Check that 'action' is a valid action (nothing will happen
1036 // if it isn't, but it would be nice with a warning).
1037 if (action) [item setAction:NSSelectorFromString(action)];
1038 else [item setAction:@selector(vimMenuItemAction:)];
1039 if (tip) [item setToolTip:tip];
1042 NSString *keyString =
1043 [NSString stringWithFormat:@"%C", key];
1044 [item setKeyEquivalent:keyString];
1045 [item setKeyEquivalentModifierMask:mask];
1049 // NOTE! The tag is used to idenfity which menu items were
1050 // added by Vim (tag != 0) and which were added by the AppKit
1054 if ([parent numberOfItems] <= idx) {
1055 [parent addItem:item];
1057 [parent insertItem:item atIndex:idx];
1060 NSLog(@"WARNING: Menu item '%@' (tag=%d) has no parent.", title, tag);
1064 - (void)updateMainMenu
1066 NSMenu *mainMenu = [NSApp mainMenu];
1068 // Stop NSApp from updating the Window menu.
1069 [NSApp setWindowsMenu:nil];
1071 // Remove all menus from main menu (except the MacVim menu).
1072 int i, count = [mainMenu numberOfItems];
1073 for (i = count-1; i > 0; --i) {
1074 [mainMenu removeItemAtIndex:i];
1077 // Add menus from 'mainMenuItems' to main menu.
1078 count = [mainMenuItems count];
1079 for (i = 0; i < count; ++i) {
1080 [mainMenu addItem:[mainMenuItems objectAtIndex:i]];
1083 // Set the new Window menu.
1084 // TODO! Need to look for 'Window' in all localized languages.
1085 NSMenu *windowMenu = [[mainMenu itemWithTitle:@"Window"] submenu];
1087 // Remove all AppKit owned menu items (tag == 0); they will be added
1088 // again when setWindowsMenu: is called.
1089 count = [windowMenu numberOfItems];
1090 for (i = count-1; i >= 0; --i) {
1091 NSMenuItem *item = [windowMenu itemAtIndex:i];
1093 [windowMenu removeItem:item];
1097 [NSApp setWindowsMenu:windowMenu];
1100 shouldUpdateMainMenu = NO;
1103 - (NSToolbarItem *)toolbarItemForTag:(int)tag index:(int *)index
1105 if (!toolbar) return nil;
1107 NSArray *items = [toolbar items];
1108 int i, count = [items count];
1109 for (i = 0; i < count; ++i) {
1110 NSToolbarItem *item = [items objectAtIndex:i];
1111 if ([item tag] == tag) {
1112 if (index) *index = i;
1120 - (void)addToolbarItemToDictionaryWithTag:(int)tag label:(NSString *)title
1121 toolTip:(NSString *)tip icon:(NSString *)icon
1123 // If the item corresponds to a separator then do nothing, since it is
1124 // already defined by Cocoa.
1125 if (!title || [title isEqual:NSToolbarSeparatorItemIdentifier]
1126 || [title isEqual:NSToolbarSpaceItemIdentifier]
1127 || [title isEqual:NSToolbarFlexibleSpaceItemIdentifier])
1130 NSToolbarItem *item = [[NSToolbarItem alloc] initWithItemIdentifier:title];
1132 [item setLabel:title];
1133 [item setToolTip:tip];
1134 [item setAction:@selector(vimMenuItemAction:)];
1135 [item setAutovalidates:NO];
1137 NSImage *img = [NSImage imageNamed:icon];
1139 NSLog(@"WARNING: Could not find image with name '%@' to use as toolbar"
1140 " image for identifier '%@';"
1141 " using default toolbar icon '%@' instead.",
1142 icon, title, MMDefaultToolbarImageName);
1144 img = [NSImage imageNamed:MMDefaultToolbarImageName];
1147 [item setImage:img];
1149 [toolbarItemDict setObject:item forKey:title];
1154 - (void)addToolbarItemWithTag:(int)tag label:(NSString *)label tip:(NSString
1155 *)tip icon:(NSString *)icon atIndex:(int)idx
1157 if (!toolbar) return;
1159 // Check for separator items.
1161 label = NSToolbarSeparatorItemIdentifier;
1162 } else if ([label length] >= 2 && [label hasPrefix:@"-"]
1163 && [label hasSuffix:@"-"]) {
1164 // The label begins and ends with '-'; decided which kind of separator
1165 // item it is by looking at the prefix.
1166 if ([label hasPrefix:@"-space"]) {
1167 label = NSToolbarSpaceItemIdentifier;
1168 } else if ([label hasPrefix:@"-flexspace"]) {
1169 label = NSToolbarFlexibleSpaceItemIdentifier;
1171 label = NSToolbarSeparatorItemIdentifier;
1175 [self addToolbarItemToDictionaryWithTag:tag label:label toolTip:tip
1178 int maxIdx = [[toolbar items] count];
1179 if (maxIdx < idx) idx = maxIdx;
1181 [toolbar insertItemWithItemIdentifier:label atIndex:idx];
1184 - (void)connectionDidDie:(NSNotification *)notification
1186 //NSLog(@"%@ %s%@", [self className], _cmd, notification);
1190 // NOTE! This causes the call to removeVimController: to be delayed.
1192 performSelectorOnMainThread:@selector(removeVimController:)
1193 withObject:self waitUntilDone:NO];
1196 - (NSString *)description
1198 return [NSString stringWithFormat:@"%@ : isInitialized=%d inProcessCommandQueue=%d mainMenuItems=%@ popupMenuItems=%@ toolbar=%@", [self className], isInitialized, inProcessCommandQueue, mainMenuItems, popupMenuItems, toolbar];
1201 #if MM_RESEND_LAST_FAILURE
1202 - (void)resendTimerFired:(NSTimer *)timer
1204 int msgid = resendMsgid;
1207 [resendTimer release];
1214 data = [resendData copy];
1216 //NSLog(@"Resending message: %s", MessageStrings[msgid]);
1217 [self sendMessage:msgid data:data];
1221 @end // MMVimController (Private)
1225 @implementation MMAlert
1228 [textField release];
1232 - (void)setTextFieldString:(NSString *)textFieldString
1234 [textField release];
1235 textField = [[NSTextField alloc] init];
1236 [textField setStringValue:textFieldString];
1239 - (NSTextField *)textField
1244 - (void)setInformativeText:(NSString *)text
1247 // HACK! Add some space for the text field.
1248 [super setInformativeText:[text stringByAppendingString:@"\n\n\n"]];
1250 [super setInformativeText:text];
1254 - (void)beginSheetModalForWindow:(NSWindow *)window
1255 modalDelegate:(id)delegate
1256 didEndSelector:(SEL)didEndSelector
1257 contextInfo:(void *)contextInfo
1259 [super beginSheetModalForWindow:window
1260 modalDelegate:delegate
1261 didEndSelector:didEndSelector
1262 contextInfo:contextInfo];
1264 // HACK! Place the input text field at the bottom of the informative text
1265 // (which has been made a bit larger by adding newline characters).
1266 NSView *contentView = [[self window] contentView];
1267 NSRect rect = [contentView frame];
1268 rect.origin.y = rect.size.height;
1270 NSArray *subviews = [contentView subviews];
1271 unsigned i, count = [subviews count];
1272 for (i = 0; i < count; ++i) {
1273 NSView *view = [subviews objectAtIndex:i];
1274 if ([view isKindOfClass:[NSTextField class]]
1275 && [view frame].origin.y < rect.origin.y) {
1276 // NOTE: The informative text field is the lowest NSTextField in
1277 // the alert dialog.
1278 rect = [view frame];
1282 rect.size.height = MMAlertTextFieldHeight;
1283 [textField setFrame:rect];
1284 [contentView addSubview:textField];
1285 [textField becomeFirstResponder];