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;
89 @implementation MMVimController
91 - (id)initWithBackend:(id)backend pid:(int)processIdentifier
93 if ((self = [super init])) {
95 [[MMWindowController alloc] initWithVimController:self];
96 backendProxy = [backend retain];
97 sendQueue = [NSMutableArray new];
98 mainMenuItems = [[NSMutableArray alloc] init];
99 popupMenuItems = [[NSMutableArray alloc] init];
100 toolbarItemDict = [[NSMutableDictionary alloc] init];
101 pid = processIdentifier;
103 NSConnection *connection = [backendProxy connectionForProxy];
105 // TODO: Check that this will not set the timeout for the root proxy
106 // (in MMAppController).
107 [connection setRequestTimeout:MMBackendProxyRequestTimeout];
109 [[NSNotificationCenter defaultCenter] addObserver:self
110 selector:@selector(connectionDidDie:)
111 name:NSConnectionDidDieNotification object:connection];
121 //NSLog(@"%@ %s", [self className], _cmd);
124 #if MM_RESEND_LAST_FAILURE
125 [resendData release]; resendData = nil;
128 [serverName release]; serverName = nil;
129 [backendProxy release]; backendProxy = nil;
130 [sendQueue release]; sendQueue = nil;
132 [toolbarItemDict release]; toolbarItemDict = nil;
133 [toolbar release]; toolbar = nil;
134 [popupMenuItems release]; popupMenuItems = nil;
135 [mainMenuItems release]; mainMenuItems = nil;
136 [windowController release]; windowController = nil;
141 - (MMWindowController *)windowController
143 return windowController;
146 - (void)setServerName:(NSString *)name
148 if (name != serverName) {
149 [serverName release];
150 serverName = [name copy];
154 - (NSString *)serverName
164 - (void)dropFiles:(NSArray *)filenames forceOpen:(BOOL)force
166 unsigned i, numberOfFiles = [filenames count];
167 NSMutableData *data = [NSMutableData data];
169 [data appendBytes:&force length:sizeof(BOOL)];
170 [data appendBytes:&numberOfFiles length:sizeof(int)];
172 for (i = 0; i < numberOfFiles; ++i) {
173 NSString *file = [filenames objectAtIndex:i];
174 int len = [file lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
177 ++len; // include NUL as well
178 [data appendBytes:&len length:sizeof(int)];
179 [data appendBytes:[file UTF8String] length:len];
183 [self sendMessage:DropFilesMsgID data:data];
186 - (void)dropString:(NSString *)string
188 int len = [string lengthOfBytesUsingEncoding:NSUTF8StringEncoding] + 1;
190 NSMutableData *data = [NSMutableData data];
192 [data appendBytes:&len length:sizeof(int)];
193 [data appendBytes:[string UTF8String] length:len];
195 [self sendMessage:DropStringMsgID data:data];
199 - (void)odbEdit:(NSArray *)filenames server:(OSType)theID path:(NSString *)path
200 token:(NSAppleEventDescriptor *)token
203 unsigned i, numberOfFiles = [filenames count];
204 NSMutableData *data = [NSMutableData data];
206 if (0 == numberOfFiles || 0 == theID)
209 [data appendBytes:&theID length:sizeof(theID)];
211 if (path && [path length] > 0) {
212 len = [path lengthOfBytesUsingEncoding:NSUTF8StringEncoding] + 1;
213 [data appendBytes:&len length:sizeof(int)];
214 [data appendBytes:[path UTF8String] length:len];
217 [data appendBytes:&len length:sizeof(int)];
221 DescType tokenType = [token descriptorType];
222 NSData *tokenData = [token data];
223 len = [tokenData length];
225 [data appendBytes:&tokenType length:sizeof(tokenType)];
226 [data appendBytes:&len length:sizeof(int)];
228 [data appendBytes:[tokenData bytes] length:len];
230 DescType tokenType = 0;
232 [data appendBytes:&tokenType length:sizeof(tokenType)];
233 [data appendBytes:&len length:sizeof(int)];
236 [data appendBytes:&numberOfFiles length:sizeof(int)];
238 for (i = 0; i < numberOfFiles; ++i) {
239 NSString *file = [filenames objectAtIndex:i];
240 len = [file lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
243 ++len; // include NUL as well
244 [data appendBytes:&len length:sizeof(unsigned)];
245 [data appendBytes:[file UTF8String] length:len];
249 [self sendMessage:ODBEditMsgID data:data];
252 - (void)sendMessage:(int)msgid data:(NSData *)data
254 //NSLog(@"sendMessage:%s (isInitialized=%d inProcessCommandQueue=%d)",
255 // MessageStrings[msgid], isInitialized, inProcessCommandQueue);
257 if (!isInitialized) return;
259 if (inProcessCommandQueue) {
260 //NSLog(@"In process command queue; delaying message send.");
261 [sendQueue addObject:[NSNumber numberWithInt:msgid]];
263 [sendQueue addObject:data];
265 [sendQueue addObject:[NSNull null]];
269 #if MM_RESEND_LAST_FAILURE
271 //NSLog(@"cancelling scheduled resend of %s",
272 // MessageStrings[resendMsgid]);
274 [resendTimer invalidate];
275 [resendTimer release];
280 [resendData release];
286 [backendProxy processInput:msgid data:data];
288 @catch (NSException *e) {
289 //NSLog(@"%@ %s Exception caught during DO call: %@",
290 // [self className], _cmd, e);
291 #if MM_RESEND_LAST_FAILURE
292 //NSLog(@"%s failed, scheduling message %s for resend", _cmd,
293 // MessageStrings[msgid]);
296 resendData = [data retain];
297 resendTimer = [NSTimer
298 scheduledTimerWithTimeInterval:MMResendInterval
300 selector:@selector(resendTimerFired:)
303 [resendTimer retain];
308 - (BOOL)sendMessageNow:(int)msgid data:(NSData *)data
309 timeout:(NSTimeInterval)timeout
311 // Send a message with a timeout. USE WITH EXTREME CAUTION! Sending
312 // messages in rapid succession with a timeout may cause MacVim to beach
313 // ball forever. In almost all circumstances sendMessage:data: should be
316 if (!isInitialized || inProcessCommandQueue)
319 if (timeout < 0) timeout = 0;
322 NSConnection *conn = [backendProxy connectionForProxy];
323 NSTimeInterval oldTimeout = [conn requestTimeout];
325 [conn setRequestTimeout:timeout];
328 [backendProxy processInput:msgid data:data];
330 @catch (NSException *e) {
334 [conn setRequestTimeout:oldTimeout];
340 - (void)addVimInput:(NSString *)string
342 // This is a very general method of adding input to the Vim process. It is
343 // basically the same as calling remote_send() on the process (see
344 // ':h remote_send').
346 NSData *data = [string dataUsingEncoding:NSUTF8StringEncoding];
347 [self sendMessage:AddInputMsgID data:data];
351 - (NSString *)evaluateVimExpression:(NSString *)expr
353 NSString *eval = nil;
356 eval = [backendProxy evaluateExpression:expr];
358 @catch (NSException *ex) { /* do nothing */ }
370 //NSLog(@"%@ %s", [self className], _cmd);
371 if (!isInitialized) return;
374 [toolbar setDelegate:nil];
375 [[NSNotificationCenter defaultCenter] removeObserver:self];
376 [windowController cleanup];
379 - (oneway void)showSavePanelForDirectory:(in bycopy NSString *)dir
380 title:(in bycopy NSString *)title
383 if (!isInitialized) return;
386 [[NSSavePanel savePanel] beginSheetForDirectory:dir file:nil
387 modalForWindow:[windowController window]
389 didEndSelector:@selector(savePanelDidEnd:code:context:)
392 NSOpenPanel *panel = [NSOpenPanel openPanel];
393 [panel setAllowsMultipleSelection:NO];
394 [panel beginSheetForDirectory:dir file:nil types:nil
395 modalForWindow:[windowController window]
397 didEndSelector:@selector(savePanelDidEnd:code:context:)
402 - (oneway void)presentDialogWithStyle:(int)style
403 message:(in bycopy NSString *)message
404 informativeText:(in bycopy NSString *)text
405 buttonTitles:(in bycopy NSArray *)buttonTitles
406 textFieldString:(in bycopy NSString *)textFieldString
408 if (!(windowController && buttonTitles && [buttonTitles count])) return;
410 MMAlert *alert = [[MMAlert alloc] init];
412 // NOTE! This has to be done before setting the informative text.
414 [alert setTextFieldString:textFieldString];
416 [alert setAlertStyle:style];
419 [alert setMessageText:message];
421 // If no message text is specified 'Alert' is used, which we don't
422 // want, so set an empty string as message text.
423 [alert setMessageText:@""];
427 [alert setInformativeText:text];
428 } else if (textFieldString) {
429 // Make sure there is always room for the input text field.
430 [alert setInformativeText:@""];
433 unsigned i, count = [buttonTitles count];
434 for (i = 0; i < count; ++i) {
435 NSString *title = [buttonTitles objectAtIndex:i];
436 // NOTE: The title of the button may contain the character '&' to
437 // indicate that the following letter should be the key equivalent
438 // associated with the button. Extract this letter and lowercase it.
439 NSString *keyEquivalent = nil;
440 NSRange hotkeyRange = [title rangeOfString:@"&"];
441 if (NSNotFound != hotkeyRange.location) {
442 if ([title length] > NSMaxRange(hotkeyRange)) {
443 NSRange keyEquivRange = NSMakeRange(hotkeyRange.location+1, 1);
444 keyEquivalent = [[title substringWithRange:keyEquivRange]
448 NSMutableString *string = [NSMutableString stringWithString:title];
449 [string deleteCharactersInRange:hotkeyRange];
453 [alert addButtonWithTitle:title];
455 // Set key equivalent for the button, but only if NSAlert hasn't
456 // already done so. (Check the documentation for
457 // - [NSAlert addButtonWithTitle:] to see what key equivalents are
458 // automatically assigned.)
459 NSButton *btn = [[alert buttons] lastObject];
460 if ([[btn keyEquivalent] length] == 0 && keyEquivalent) {
461 [btn setKeyEquivalent:keyEquivalent];
465 [alert beginSheetModalForWindow:[windowController window]
467 didEndSelector:@selector(alertDidEnd:code:context:)
473 - (oneway void)processCommandQueue:(in bycopy NSArray *)queue
475 if (!isInitialized) return;
477 unsigned i, count = [queue count];
479 NSLog(@"WARNING: Uneven number of components (%d) in flush queue "
480 "message; ignoring this message.", count);
484 inProcessCommandQueue = YES;
486 //NSLog(@"======== %s BEGIN ========", _cmd);
487 for (i = 0; i < count; i += 2) {
488 NSData *value = [queue objectAtIndex:i];
489 NSData *data = [queue objectAtIndex:i+1];
491 int msgid = *((int*)[value bytes]);
492 //NSLog(@"%s%s", _cmd, MessageStrings[msgid]);
494 [self handleMessage:msgid data:data];
496 //NSLog(@"======== %s END ========", _cmd);
498 if (shouldUpdateMainMenu) {
499 [self updateMainMenu];
502 [windowController processCommandQueueDidFinish];
504 inProcessCommandQueue = NO;
506 if ([sendQueue count] > 0) {
508 [backendProxy processInputAndData:sendQueue];
510 @catch (NSException *e) {
511 // Connection timed out, just ignore this.
512 //NSLog(@"WARNING! Connection timed out in %s", _cmd);
515 [sendQueue removeAllObjects];
519 - (NSToolbarItem *)toolbar:(NSToolbar *)theToolbar
520 itemForItemIdentifier:(NSString *)itemId
521 willBeInsertedIntoToolbar:(BOOL)flag
523 NSToolbarItem *item = [toolbarItemDict objectForKey:itemId];
525 NSLog(@"WARNING: No toolbar item with id '%@'", itemId);
531 - (NSArray *)toolbarAllowedItemIdentifiers:(NSToolbar *)theToolbar
536 - (NSArray *)toolbarDefaultItemIdentifiers:(NSToolbar *)theToolbar
541 - (void)updateMainMenu
543 NSMenu *mainMenu = [NSApp mainMenu];
545 // Stop NSApp from updating the Window menu.
546 [NSApp setWindowsMenu:nil];
548 // Remove all menus from main menu (except the MacVim menu).
549 int i, count = [mainMenu numberOfItems];
550 for (i = count-1; i > 0; --i) {
551 [mainMenu removeItemAtIndex:i];
554 // Add menus from 'mainMenuItems' to main menu.
555 count = [mainMenuItems count];
556 for (i = 0; i < count; ++i) {
557 [mainMenu addItem:[mainMenuItems objectAtIndex:i]];
560 // Set the new Window menu.
561 // TODO! Need to look for 'Window' in all localized languages.
562 NSMenu *windowMenu = [[mainMenu itemWithTitle:@"Window"] submenu];
564 // Remove all AppKit owned menu items (tag == 0); they will be added
565 // again when setWindowsMenu: is called.
566 count = [windowMenu numberOfItems];
567 for (i = count-1; i >= 0; --i) {
568 NSMenuItem *item = [windowMenu itemAtIndex:i];
570 [windowMenu removeItem:item];
574 [NSApp setWindowsMenu:windowMenu];
577 shouldUpdateMainMenu = NO;
580 @end // MMVimController
584 @implementation MMVimController (Private)
586 - (void)handleMessage:(int)msgid data:(NSData *)data
588 //if (msgid != AddMenuMsgID && msgid != AddMenuItemMsgID)
589 // NSLog(@"%@ %s%s", [self className], _cmd, MessageStrings[msgid]);
591 if (OpenVimWindowMsgID == msgid) {
592 [windowController openWindow];
593 } else if (BatchDrawMsgID == msgid) {
594 [[[windowController vimView] textView] performBatchDrawWithData:data];
595 } else if (SelectTabMsgID == msgid) {
596 #if 0 // NOTE: Tab selection is done inside updateTabsWithData:.
597 const void *bytes = [data bytes];
598 int idx = *((int*)bytes);
599 //NSLog(@"Selecting tab with index %d", idx);
600 [windowController selectTabWithIndex:idx];
602 } else if (UpdateTabBarMsgID == msgid) {
603 [windowController updateTabsWithData:data];
604 } else if (ShowTabBarMsgID == msgid) {
605 [windowController showTabBar:YES];
606 } else if (HideTabBarMsgID == msgid) {
607 [windowController showTabBar:NO];
608 } else if (SetTextDimensionsMsgID == msgid || LiveResizeMsgID == msgid) {
609 const void *bytes = [data bytes];
610 int rows = *((int*)bytes); bytes += sizeof(int);
611 int cols = *((int*)bytes); bytes += sizeof(int);
613 [windowController setTextDimensionsWithRows:rows columns:cols
614 live:(LiveResizeMsgID==msgid)];
615 } else if (SetWindowTitleMsgID == msgid) {
616 const void *bytes = [data bytes];
617 int len = *((int*)bytes); bytes += sizeof(int);
619 NSString *string = [[NSString alloc] initWithBytes:(void*)bytes
620 length:len encoding:NSUTF8StringEncoding];
622 [windowController setTitle:string];
625 } else if (AddMenuMsgID == msgid) {
626 NSString *title = nil;
627 const void *bytes = [data bytes];
628 int tag = *((int*)bytes); bytes += sizeof(int);
629 int parentTag = *((int*)bytes); bytes += sizeof(int);
630 int len = *((int*)bytes); bytes += sizeof(int);
632 title = [[NSString alloc] initWithBytes:(void*)bytes length:len
633 encoding:NSUTF8StringEncoding];
636 int idx = *((int*)bytes); bytes += sizeof(int);
638 if (MenuToolbarType == parentTag) {
640 // NOTE! Each toolbar must have a unique identifier, else each
641 // window will have the same toolbar.
642 NSString *ident = [NSString stringWithFormat:@"%d.%d",
644 toolbar = [[NSToolbar alloc] initWithIdentifier:ident];
646 [toolbar setShowsBaselineSeparator:NO];
647 [toolbar setDelegate:self];
648 [toolbar setDisplayMode:NSToolbarDisplayModeIconOnly];
649 [toolbar setSizeMode:NSToolbarSizeModeSmall];
651 [windowController setToolbar:toolbar];
654 [self addMenuWithTag:tag parent:parentTag title:title atIndex:idx];
658 } else if (AddMenuItemMsgID == msgid) {
659 NSString *title = nil, *tip = nil, *icon = nil, *action = nil;
660 const void *bytes = [data bytes];
661 int tag = *((int*)bytes); bytes += sizeof(int);
662 int parentTag = *((int*)bytes); bytes += sizeof(int);
663 int namelen = *((int*)bytes); bytes += sizeof(int);
665 title = [[NSString alloc] initWithBytes:(void*)bytes length:namelen
666 encoding:NSUTF8StringEncoding];
669 int tiplen = *((int*)bytes); bytes += sizeof(int);
671 tip = [[NSString alloc] initWithBytes:(void*)bytes length:tiplen
672 encoding:NSUTF8StringEncoding];
675 int iconlen = *((int*)bytes); bytes += sizeof(int);
677 icon = [[NSString alloc] initWithBytes:(void*)bytes length:iconlen
678 encoding:NSUTF8StringEncoding];
681 int actionlen = *((int*)bytes); bytes += sizeof(int);
683 action = [[NSString alloc] initWithBytes:(void*)bytes
685 encoding:NSUTF8StringEncoding];
688 int idx = *((int*)bytes); bytes += sizeof(int);
689 if (idx < 0) idx = 0;
690 int key = *((int*)bytes); bytes += sizeof(int);
691 int mask = *((int*)bytes); bytes += sizeof(int);
693 NSString *ident = [NSString stringWithFormat:@"%d.%d",
694 (int)self, parentTag];
695 if (toolbar && [[toolbar identifier] isEqual:ident]) {
696 [self addToolbarItemWithTag:tag label:title tip:tip icon:icon
699 NSMenu *parent = [self menuForTag:parentTag];
700 [self addMenuItemWithTag:tag parent:parent title:title tip:tip
701 keyEquivalent:key modifiers:mask action:action
709 } else if (RemoveMenuItemMsgID == msgid) {
710 const void *bytes = [data bytes];
711 int tag = *((int*)bytes); bytes += sizeof(int);
715 if ((item = [self toolbarItemForTag:tag index:&idx])) {
716 [toolbar removeItemAtIndex:idx];
717 } else if ((item = [self menuItemForTag:tag])) {
720 if ([item menu] == [NSApp mainMenu] || ![item menu]) {
721 // NOTE: To be on the safe side we try to remove the item from
722 // both arrays (it is ok to call removeObject: even if an array
723 // does not contain the object to remove).
724 [mainMenuItems removeObject:item];
725 [popupMenuItems removeObject:item];
729 [[item menu] removeItem:item];
734 // Reset cached menu, just to be on the safe side.
735 lastMenuSearched = nil;
736 } else if (EnableMenuItemMsgID == msgid) {
737 const void *bytes = [data bytes];
738 int tag = *((int*)bytes); bytes += sizeof(int);
739 int state = *((int*)bytes); bytes += sizeof(int);
741 id item = [self toolbarItemForTag:tag index:NULL];
743 item = [self menuItemForTag:tag];
745 [item setEnabled:state];
746 } else if (ShowToolbarMsgID == msgid) {
747 const void *bytes = [data bytes];
748 int enable = *((int*)bytes); bytes += sizeof(int);
749 int flags = *((int*)bytes); bytes += sizeof(int);
751 int mode = NSToolbarDisplayModeDefault;
752 if (flags & ToolbarLabelFlag) {
753 mode = flags & ToolbarIconFlag ? NSToolbarDisplayModeIconAndLabel
754 : NSToolbarDisplayModeLabelOnly;
755 } else if (flags & ToolbarIconFlag) {
756 mode = NSToolbarDisplayModeIconOnly;
759 int size = flags & ToolbarSizeRegularFlag ? NSToolbarSizeModeRegular
760 : NSToolbarSizeModeSmall;
762 [windowController showToolbar:enable size:size mode:mode];
763 } else if (CreateScrollbarMsgID == msgid) {
764 const void *bytes = [data bytes];
765 long ident = *((long*)bytes); bytes += sizeof(long);
766 int type = *((int*)bytes); bytes += sizeof(int);
768 [windowController createScrollbarWithIdentifier:ident type:type];
769 } else if (DestroyScrollbarMsgID == msgid) {
770 const void *bytes = [data bytes];
771 long ident = *((long*)bytes); bytes += sizeof(long);
773 [windowController destroyScrollbarWithIdentifier:ident];
774 } else if (ShowScrollbarMsgID == msgid) {
775 const void *bytes = [data bytes];
776 long ident = *((long*)bytes); bytes += sizeof(long);
777 int visible = *((int*)bytes); bytes += sizeof(int);
779 [windowController showScrollbarWithIdentifier:ident state:visible];
780 } else if (SetScrollbarPositionMsgID == msgid) {
781 const void *bytes = [data bytes];
782 long ident = *((long*)bytes); bytes += sizeof(long);
783 int pos = *((int*)bytes); bytes += sizeof(int);
784 int len = *((int*)bytes); bytes += sizeof(int);
786 [windowController setScrollbarPosition:pos length:len
788 } else if (SetScrollbarThumbMsgID == msgid) {
789 const void *bytes = [data bytes];
790 long ident = *((long*)bytes); bytes += sizeof(long);
791 float val = *((float*)bytes); bytes += sizeof(float);
792 float prop = *((float*)bytes); bytes += sizeof(float);
794 [windowController setScrollbarThumbValue:val proportion:prop
796 } else if (SetFontMsgID == msgid) {
797 const void *bytes = [data bytes];
798 float size = *((float*)bytes); bytes += sizeof(float);
799 int len = *((int*)bytes); bytes += sizeof(int);
800 NSString *name = [[NSString alloc]
801 initWithBytes:(void*)bytes length:len
802 encoding:NSUTF8StringEncoding];
803 NSFont *font = [NSFont fontWithName:name size:size];
806 [windowController setFont:font];
809 } else if (SetWideFontMsgID == msgid) {
810 const void *bytes = [data bytes];
811 float size = *((float*)bytes); bytes += sizeof(float);
812 int len = *((int*)bytes); bytes += sizeof(int);
814 NSString *name = [[NSString alloc]
815 initWithBytes:(void*)bytes length:len
816 encoding:NSUTF8StringEncoding];
817 NSFont *font = [NSFont fontWithName:name size:size];
818 [windowController setWideFont:font];
822 [windowController setWideFont:nil];
824 } else if (SetDefaultColorsMsgID == msgid) {
825 const void *bytes = [data bytes];
826 unsigned bg = *((unsigned*)bytes); bytes += sizeof(unsigned);
827 unsigned fg = *((unsigned*)bytes); bytes += sizeof(unsigned);
828 NSColor *back = [NSColor colorWithArgbInt:bg];
829 NSColor *fore = [NSColor colorWithRgbInt:fg];
831 [windowController setDefaultColorsBackground:back foreground:fore];
832 } else if (ExecuteActionMsgID == msgid) {
833 const void *bytes = [data bytes];
834 int len = *((int*)bytes); bytes += sizeof(int);
835 NSString *actionName = [[NSString alloc]
836 initWithBytes:(void*)bytes length:len
837 encoding:NSUTF8StringEncoding];
839 SEL sel = NSSelectorFromString(actionName);
840 [NSApp sendAction:sel to:nil from:self];
842 [actionName release];
843 } else if (ShowPopupMenuMsgID == msgid) {
844 const void *bytes = [data bytes];
845 int row = *((int*)bytes); bytes += sizeof(int);
846 int col = *((int*)bytes); bytes += sizeof(int);
847 int len = *((int*)bytes); bytes += sizeof(int);
848 NSString *title = [[NSString alloc]
849 initWithBytes:(void*)bytes length:len
850 encoding:NSUTF8StringEncoding];
852 NSMenu *menu = [self topLevelMenuForTitle:title];
854 [windowController popupMenu:menu atRow:row column:col];
856 NSLog(@"WARNING: Cannot popup menu with title %@; no such menu.",
861 } else if (SetMouseShapeMsgID == msgid) {
862 const void *bytes = [data bytes];
863 int shape = *((int*)bytes); bytes += sizeof(int);
865 [windowController setMouseShape:shape];
866 } else if (AdjustLinespaceMsgID == msgid) {
867 const void *bytes = [data bytes];
868 int linespace = *((int*)bytes); bytes += sizeof(int);
870 [windowController adjustLinespace:linespace];
871 } else if (ActivateMsgID == msgid) {
872 //NSLog(@"ActivateMsgID");
873 [NSApp activateIgnoringOtherApps:YES];
874 [[windowController window] makeKeyAndOrderFront:self];
875 } else if (SetServerNameMsgID == msgid) {
876 NSString *name = [[NSString alloc] initWithData:data
877 encoding:NSUTF8StringEncoding];
878 [self setServerName:name];
880 } else if (EnterFullscreenMsgID == msgid) {
881 [windowController enterFullscreen];
882 } else if (LeaveFullscreenMsgID == msgid) {
883 [windowController leaveFullscreen];
884 } else if (BuffersNotModifiedMsgID == msgid) {
885 [windowController setBuffersModified:NO];
886 } else if (BuffersModifiedMsgID == msgid) {
887 [windowController setBuffersModified:YES];
888 } else if (SetPreEditPositionMsgID == msgid) {
889 const int *dim = (const int*)[data bytes];
890 [[[windowController vimView] textView] setPreEditRow:dim[0]
893 NSLog(@"WARNING: Unknown message received (msgid=%d)", msgid);
897 - (void)savePanelDidEnd:(NSSavePanel *)panel code:(int)code
898 context:(void *)context
900 NSString *string = (code == NSOKButton) ? [panel filename] : nil;
902 [backendProxy setDialogReturn:string];
904 @catch (NSException *e) {
905 NSLog(@"Exception caught in %s %@", _cmd, e);
909 - (void)alertDidEnd:(MMAlert *)alert code:(int)code context:(void *)context
913 code = code - NSAlertFirstButtonReturn + 1;
915 if ([alert isKindOfClass:[MMAlert class]] && [alert textField]) {
916 ret = [NSArray arrayWithObjects:[NSNumber numberWithInt:code],
917 [[alert textField] stringValue], nil];
919 ret = [NSArray arrayWithObject:[NSNumber numberWithInt:code]];
923 [backendProxy setDialogReturn:ret];
925 @catch (NSException *e) {
926 NSLog(@"Exception caught in %s %@", _cmd, e);
930 - (NSMenuItem *)recurseMenuItemForTag:(int)tag rootMenu:(NSMenu *)root
933 NSMenuItem *item = [root itemWithTag:tag];
935 lastMenuSearched = root;
939 NSArray *items = [root itemArray];
940 unsigned i, count = [items count];
941 for (i = 0; i < count; ++i) {
942 item = [items objectAtIndex:i];
943 if ([item hasSubmenu]) {
944 item = [self recurseMenuItemForTag:tag
945 rootMenu:[item submenu]];
947 lastMenuSearched = [item submenu];
957 - (NSMenuItem *)menuItemForTag:(int)tag
959 // First search the same menu that was search last time this method was
960 // called. Since this method is often called for each menu item in a
961 // menu this can significantly improve search times.
962 if (lastMenuSearched) {
963 NSMenuItem *item = [self recurseMenuItemForTag:tag
964 rootMenu:lastMenuSearched];
965 if (item) return item;
968 // Search the main menu.
969 int i, count = [mainMenuItems count];
970 for (i = 0; i < count; ++i) {
971 NSMenuItem *item = [mainMenuItems objectAtIndex:i];
972 if ([item tag] == tag) return item;
973 item = [self recurseMenuItemForTag:tag rootMenu:[item submenu]];
975 lastMenuSearched = [item submenu];
980 // Search the popup menus.
981 count = [popupMenuItems count];
982 for (i = 0; i < count; ++i) {
983 NSMenuItem *item = [popupMenuItems objectAtIndex:i];
984 if ([item tag] == tag) return item;
985 item = [self recurseMenuItemForTag:tag rootMenu:[item submenu]];
987 lastMenuSearched = [item submenu];
995 - (NSMenu *)menuForTag:(int)tag
997 return [[self menuItemForTag:tag] submenu];
1000 - (NSMenu *)topLevelMenuForTitle:(NSString *)title
1002 // Search only the top-level menus.
1004 unsigned i, count = [popupMenuItems count];
1005 for (i = 0; i < count; ++i) {
1006 NSMenuItem *item = [popupMenuItems objectAtIndex:i];
1007 if ([title isEqual:[item title]])
1008 return [item submenu];
1011 count = [mainMenuItems count];
1012 for (i = 0; i < count; ++i) {
1013 NSMenuItem *item = [mainMenuItems objectAtIndex:i];
1014 if ([title isEqual:[item title]])
1015 return [item submenu];
1021 - (void)addMenuWithTag:(int)tag parent:(int)parentTag title:(NSString *)title
1024 NSMenu *parent = [self menuForTag:parentTag];
1025 NSMenuItem *item = [[NSMenuItem alloc] init];
1026 NSMenu *menu = [[NSMenu alloc] initWithTitle:title];
1028 [menu setAutoenablesItems:NO];
1030 [item setTitle:title];
1031 [item setSubmenu:menu];
1034 if ([parent numberOfItems] <= idx) {
1035 [parent addItem:item];
1037 [parent insertItem:item atIndex:idx];
1040 NSMutableArray *items = (MenuPopupType == parentTag)
1041 ? popupMenuItems : mainMenuItems;
1042 if ([items count] <= idx) {
1043 [items addObject:item];
1045 [items insertObject:item atIndex:idx];
1048 shouldUpdateMainMenu = (MenuPopupType != parentTag);
1055 - (void)addMenuItemWithTag:(int)tag parent:(NSMenu *)parent
1056 title:(NSString *)title tip:(NSString *)tip
1057 keyEquivalent:(int)key modifiers:(int)mask
1058 action:(NSString *)action atIndex:(int)idx
1061 NSMenuItem *item = nil;
1062 if (!title || ([title hasPrefix:@"-"] && [title hasSuffix:@"-"])) {
1063 item = [NSMenuItem separatorItem];
1065 item = [[[NSMenuItem alloc] init] autorelease];
1066 [item setTitle:title];
1067 // TODO: Check that 'action' is a valid action (nothing will happen
1068 // if it isn't, but it would be nice with a warning).
1069 if (action) [item setAction:NSSelectorFromString(action)];
1070 else [item setAction:@selector(vimMenuItemAction:)];
1071 if (tip) [item setToolTip:tip];
1074 NSString *keyString =
1075 [NSString stringWithFormat:@"%C", key];
1076 [item setKeyEquivalent:keyString];
1077 [item setKeyEquivalentModifierMask:mask];
1081 // NOTE! The tag is used to idenfity which menu items were
1082 // added by Vim (tag != 0) and which were added by the AppKit
1086 if ([parent numberOfItems] <= idx) {
1087 [parent addItem:item];
1089 [parent insertItem:item atIndex:idx];
1092 NSLog(@"WARNING: Menu item '%@' (tag=%d) has no parent.", title, tag);
1096 - (NSToolbarItem *)toolbarItemForTag:(int)tag index:(int *)index
1098 if (!toolbar) return nil;
1100 NSArray *items = [toolbar items];
1101 int i, count = [items count];
1102 for (i = 0; i < count; ++i) {
1103 NSToolbarItem *item = [items objectAtIndex:i];
1104 if ([item tag] == tag) {
1105 if (index) *index = i;
1113 - (void)addToolbarItemToDictionaryWithTag:(int)tag label:(NSString *)title
1114 toolTip:(NSString *)tip icon:(NSString *)icon
1116 // If the item corresponds to a separator then do nothing, since it is
1117 // already defined by Cocoa.
1118 if (!title || [title isEqual:NSToolbarSeparatorItemIdentifier]
1119 || [title isEqual:NSToolbarSpaceItemIdentifier]
1120 || [title isEqual:NSToolbarFlexibleSpaceItemIdentifier])
1123 NSToolbarItem *item = [[NSToolbarItem alloc] initWithItemIdentifier:title];
1125 [item setLabel:title];
1126 [item setToolTip:tip];
1127 [item setAction:@selector(vimMenuItemAction:)];
1128 [item setAutovalidates:NO];
1130 NSImage *img = [NSImage imageNamed:icon];
1132 NSLog(@"WARNING: Could not find image with name '%@' to use as toolbar"
1133 " image for identifier '%@';"
1134 " using default toolbar icon '%@' instead.",
1135 icon, title, MMDefaultToolbarImageName);
1137 img = [NSImage imageNamed:MMDefaultToolbarImageName];
1140 [item setImage:img];
1142 [toolbarItemDict setObject:item forKey:title];
1147 - (void)addToolbarItemWithTag:(int)tag label:(NSString *)label tip:(NSString
1148 *)tip icon:(NSString *)icon atIndex:(int)idx
1150 if (!toolbar) return;
1152 // Check for separator items.
1154 label = NSToolbarSeparatorItemIdentifier;
1155 } else if ([label length] >= 2 && [label hasPrefix:@"-"]
1156 && [label hasSuffix:@"-"]) {
1157 // The label begins and ends with '-'; decided which kind of separator
1158 // item it is by looking at the prefix.
1159 if ([label hasPrefix:@"-space"]) {
1160 label = NSToolbarSpaceItemIdentifier;
1161 } else if ([label hasPrefix:@"-flexspace"]) {
1162 label = NSToolbarFlexibleSpaceItemIdentifier;
1164 label = NSToolbarSeparatorItemIdentifier;
1168 [self addToolbarItemToDictionaryWithTag:tag label:label toolTip:tip
1171 int maxIdx = [[toolbar items] count];
1172 if (maxIdx < idx) idx = maxIdx;
1174 [toolbar insertItemWithItemIdentifier:label atIndex:idx];
1177 - (void)connectionDidDie:(NSNotification *)notification
1179 //NSLog(@"%@ %s%@", [self className], _cmd, notification);
1183 // NOTE! This causes the call to removeVimController: to be delayed.
1185 performSelectorOnMainThread:@selector(removeVimController:)
1186 withObject:self waitUntilDone:NO];
1189 - (NSString *)description
1191 return [NSString stringWithFormat:@"%@ : isInitialized=%d inProcessCommandQueue=%d mainMenuItems=%@ popupMenuItems=%@ toolbar=%@", [self className], isInitialized, inProcessCommandQueue, mainMenuItems, popupMenuItems, toolbar];
1194 #if MM_RESEND_LAST_FAILURE
1195 - (void)resendTimerFired:(NSTimer *)timer
1197 int msgid = resendMsgid;
1200 [resendTimer release];
1207 data = [resendData copy];
1209 //NSLog(@"Resending message: %s", MessageStrings[msgid]);
1210 [self sendMessage:msgid data:data];
1214 @end // MMVimController (Private)
1218 @implementation MMAlert
1221 [textField release]; textField = nil;
1225 - (void)setTextFieldString:(NSString *)textFieldString
1227 [textField release];
1228 textField = [[NSTextField alloc] init];
1229 [textField setStringValue:textFieldString];
1232 - (NSTextField *)textField
1237 - (void)setInformativeText:(NSString *)text
1240 // HACK! Add some space for the text field.
1241 [super setInformativeText:[text stringByAppendingString:@"\n\n\n"]];
1243 [super setInformativeText:text];
1247 - (void)beginSheetModalForWindow:(NSWindow *)window
1248 modalDelegate:(id)delegate
1249 didEndSelector:(SEL)didEndSelector
1250 contextInfo:(void *)contextInfo
1252 [super beginSheetModalForWindow:window
1253 modalDelegate:delegate
1254 didEndSelector:didEndSelector
1255 contextInfo:contextInfo];
1257 // HACK! Place the input text field at the bottom of the informative text
1258 // (which has been made a bit larger by adding newline characters).
1259 NSView *contentView = [[self window] contentView];
1260 NSRect rect = [contentView frame];
1261 rect.origin.y = rect.size.height;
1263 NSArray *subviews = [contentView subviews];
1264 unsigned i, count = [subviews count];
1265 for (i = 0; i < count; ++i) {
1266 NSView *view = [subviews objectAtIndex:i];
1267 if ([view isKindOfClass:[NSTextField class]]
1268 && [view frame].origin.y < rect.origin.y) {
1269 // NOTE: The informative text field is the lowest NSTextField in
1270 // the alert dialog.
1271 rect = [view frame];
1275 rect.size.height = MMAlertTextFieldHeight;
1276 [textField setFrame:rect];
1277 [contentView addSubview:textField];
1278 [textField becomeFirstResponder];