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 "MMTextView.h"
29 #import "MMAppController.h"
30 #import "MMTextStorage.h"
31 #import "MMAtsuiTextView.h"
34 // This is taken from gui.h
35 #define DRAW_CURSOR 0x20
37 static NSString *MMDefaultToolbarImageName = @"Attention";
38 static int MMAlertTextFieldHeight = 22;
40 // NOTE: By default a message sent to the backend will be dropped if it cannot
41 // be delivered instantly; otherwise there is a possibility that MacVim will
42 // 'beachball' while waiting to deliver DO messages to an unresponsive Vim
43 // process. This means that you cannot rely on any message sent with
44 // sendMessage: to actually reach Vim.
45 static NSTimeInterval MMBackendProxyRequestTimeout = 0;
47 #if MM_RESEND_LAST_FAILURE
48 // If a message send fails, the message will be resent after this many seconds
49 // have passed. (No queue is kept, only the very last message is resent.)
50 static NSTimeInterval MMResendInterval = 0.5;
54 @interface MMAlert : NSAlert {
55 NSTextField *textField;
57 - (void)setTextFieldString:(NSString *)textFieldString;
58 - (NSTextField *)textField;
62 @interface MMVimController (Private)
63 - (void)handleMessage:(int)msgid data:(NSData *)data;
64 - (void)performBatchDrawWithData:(NSData *)data;
65 - (void)savePanelDidEnd:(NSSavePanel *)panel code:(int)code
66 context:(void *)context;
67 - (void)alertDidEnd:(MMAlert *)alert code:(int)code context:(void *)context;
68 - (NSMenuItem *)recurseMenuItemForTag:(int)tag rootMenu:(NSMenu *)root;
69 - (NSMenuItem *)menuItemForTag:(int)tag;
70 - (NSMenu *)menuForTag:(int)tag;
71 - (NSMenu *)topLevelMenuForTitle:(NSString *)title;
72 - (void)addMenuWithTag:(int)tag parent:(int)parentTag title:(NSString *)title
74 - (void)addMenuItemWithTag:(int)tag parent:(NSMenu *)parent
75 title:(NSString *)title tip:(NSString *)tip
76 keyEquivalent:(int)key modifiers:(int)mask
77 action:(NSString *)action atIndex:(int)idx;
78 - (void)updateMainMenu;
79 - (NSToolbarItem *)toolbarItemForTag:(int)tag index:(int *)index;
80 - (void)addToolbarItemToDictionaryWithTag:(int)tag label:(NSString *)title
81 toolTip:(NSString *)tip icon:(NSString *)icon;
82 - (void)addToolbarItemWithTag:(int)tag label:(NSString *)label
83 tip:(NSString *)tip icon:(NSString *)icon
85 - (void)connectionDidDie:(NSNotification *)notification;
86 #if MM_RESEND_LAST_FAILURE
87 - (void)resendTimerFired:(NSTimer *)timer;
94 @implementation MMVimController
96 - (id)initWithBackend:(id)backend pid:(int)processIdentifier
98 if ((self = [super init])) {
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];
119 NSWindow *win = [windowController window];
121 [[NSNotificationCenter defaultCenter]
123 selector:@selector(windowDidBecomeMain:)
124 name:NSWindowDidBecomeMainNotification
135 //NSLog(@"%@ %s", [self className], _cmd);
138 #if MM_RESEND_LAST_FAILURE
139 [resendData release]; resendData = nil;
142 [serverName release]; serverName = nil;
143 [backendProxy release]; backendProxy = nil;
144 [sendQueue release]; sendQueue = nil;
146 [toolbarItemDict release]; toolbarItemDict = nil;
147 [toolbar release]; toolbar = nil;
148 [popupMenuItems release]; popupMenuItems = nil;
149 [mainMenuItems release]; mainMenuItems = nil;
150 [windowController release]; windowController = nil;
155 - (MMWindowController *)windowController
157 return windowController;
160 - (void)setServerName:(NSString *)name
162 if (name != serverName) {
163 [serverName release];
164 serverName = [name copy];
168 - (NSString *)serverName
178 - (void)dropFiles:(NSArray *)filenames forceOpen:(BOOL)force
180 unsigned i, numberOfFiles = [filenames count];
181 NSMutableData *data = [NSMutableData data];
183 [data appendBytes:&force length:sizeof(BOOL)];
184 [data appendBytes:&numberOfFiles length:sizeof(int)];
186 for (i = 0; i < numberOfFiles; ++i) {
187 NSString *file = [filenames objectAtIndex:i];
188 int len = [file lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
191 ++len; // include NUL as well
192 [data appendBytes:&len length:sizeof(int)];
193 [data appendBytes:[file UTF8String] length:len];
197 [self sendMessage:DropFilesMsgID data:data];
200 - (void)dropString:(NSString *)string
202 int len = [string lengthOfBytesUsingEncoding:NSUTF8StringEncoding] + 1;
204 NSMutableData *data = [NSMutableData data];
206 [data appendBytes:&len length:sizeof(int)];
207 [data appendBytes:[string UTF8String] length:len];
209 [self sendMessage:DropStringMsgID data:data];
213 - (void)odbEdit:(NSArray *)filenames server:(OSType)theID path:(NSString *)path
214 token:(NSAppleEventDescriptor *)token
217 unsigned i, numberOfFiles = [filenames count];
218 NSMutableData *data = [NSMutableData data];
220 if (0 == numberOfFiles || 0 == theID)
223 [data appendBytes:&theID length:sizeof(theID)];
225 if (path && [path length] > 0) {
226 len = [path lengthOfBytesUsingEncoding:NSUTF8StringEncoding] + 1;
227 [data appendBytes:&len length:sizeof(int)];
228 [data appendBytes:[path UTF8String] length:len];
231 [data appendBytes:&len length:sizeof(int)];
235 DescType tokenType = [token descriptorType];
236 NSData *tokenData = [token data];
237 len = [tokenData length];
239 [data appendBytes:&tokenType length:sizeof(tokenType)];
240 [data appendBytes:&len length:sizeof(int)];
242 [data appendBytes:[tokenData bytes] length:len];
244 DescType tokenType = 0;
246 [data appendBytes:&tokenType length:sizeof(tokenType)];
247 [data appendBytes:&len length:sizeof(int)];
250 [data appendBytes:&numberOfFiles length:sizeof(int)];
252 for (i = 0; i < numberOfFiles; ++i) {
253 NSString *file = [filenames objectAtIndex:i];
254 len = [file lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
257 ++len; // include NUL as well
258 [data appendBytes:&len length:sizeof(unsigned)];
259 [data appendBytes:[file UTF8String] length:len];
263 [self sendMessage:ODBEditMsgID data:data];
266 - (void)sendMessage:(int)msgid data:(NSData *)data
268 //NSLog(@"sendMessage:%s (isInitialized=%d inProcessCommandQueue=%d)",
269 // MessageStrings[msgid], isInitialized, inProcessCommandQueue);
271 if (!isInitialized) return;
273 if (inProcessCommandQueue) {
274 //NSLog(@"In process command queue; delaying message send.");
275 [sendQueue addObject:[NSNumber numberWithInt:msgid]];
277 [sendQueue addObject:data];
279 [sendQueue addObject:[NSNull null]];
283 #if MM_RESEND_LAST_FAILURE
285 //NSLog(@"cancelling scheduled resend of %s",
286 // MessageStrings[resendMsgid]);
288 [resendTimer invalidate];
289 [resendTimer release];
294 [resendData release];
300 [backendProxy processInput:msgid data:data];
302 @catch (NSException *e) {
303 //NSLog(@"%@ %s Exception caught during DO call: %@",
304 // [self className], _cmd, e);
305 #if MM_RESEND_LAST_FAILURE
306 //NSLog(@"%s failed, scheduling message %s for resend", _cmd,
307 // MessageStrings[msgid]);
310 resendData = [data retain];
311 resendTimer = [NSTimer
312 scheduledTimerWithTimeInterval:MMResendInterval
314 selector:@selector(resendTimerFired:)
317 [resendTimer retain];
322 - (BOOL)sendMessageNow:(int)msgid data:(NSData *)data
323 timeout:(NSTimeInterval)timeout
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 //NSLog(@"%@ %s", [self className], _cmd);
554 if (OpenVimWindowMsgID == msgid) {
555 [windowController openWindow];
556 } else if (BatchDrawMsgID == msgid) {
557 if ([[NSUserDefaults standardUserDefaults]
558 boolForKey:MMAtsuiRendererKey])
559 [(MMAtsuiTextView *)[windowController textView] performBatchDrawWithData:data];
561 [self performBatchDrawWithData:data];
562 } else if (SelectTabMsgID == msgid) {
563 #if 0 // NOTE: Tab selection is done inside updateTabsWithData:.
564 const void *bytes = [data bytes];
565 int idx = *((int*)bytes);
566 //NSLog(@"Selecting tab with index %d", idx);
567 [windowController selectTabWithIndex:idx];
569 } else if (UpdateTabBarMsgID == msgid) {
570 [windowController updateTabsWithData:data];
571 } else if (ShowTabBarMsgID == msgid) {
572 [windowController showTabBar:YES];
573 } else if (HideTabBarMsgID == msgid) {
574 [windowController showTabBar:NO];
575 } else if (SetTextDimensionsMsgID == msgid) {
576 const void *bytes = [data bytes];
577 int rows = *((int*)bytes); bytes += sizeof(int);
578 int cols = *((int*)bytes); bytes += sizeof(int);
580 [windowController setTextDimensionsWithRows:rows columns:cols];
581 } else if (SetWindowTitleMsgID == msgid) {
582 const void *bytes = [data bytes];
583 int len = *((int*)bytes); bytes += sizeof(int);
585 NSString *string = [[NSString alloc] initWithBytes:(void*)bytes
586 length:len encoding:NSUTF8StringEncoding];
588 [[windowController window] setTitle:string];
591 } else if (AddMenuMsgID == msgid) {
592 NSString *title = nil;
593 const void *bytes = [data bytes];
594 int tag = *((int*)bytes); bytes += sizeof(int);
595 int parentTag = *((int*)bytes); bytes += sizeof(int);
596 int len = *((int*)bytes); bytes += sizeof(int);
598 title = [[NSString alloc] initWithBytes:(void*)bytes length:len
599 encoding:NSUTF8StringEncoding];
602 int idx = *((int*)bytes); bytes += sizeof(int);
604 if (MenuToolbarType == parentTag) {
606 // NOTE! Each toolbar must have a unique identifier, else each
607 // window will have the same toolbar.
608 NSString *ident = [NSString stringWithFormat:@"%d.%d",
610 toolbar = [[NSToolbar alloc] initWithIdentifier:ident];
612 [toolbar setShowsBaselineSeparator:NO];
613 [toolbar setDelegate:self];
614 [toolbar setDisplayMode:NSToolbarDisplayModeIconOnly];
615 [toolbar setSizeMode:NSToolbarSizeModeSmall];
617 NSWindow *win = [windowController window];
618 [win setToolbar:toolbar];
620 // HACK! Redirect the pill button so that we can ask Vim to
622 NSButton *pillButton = [win
623 standardWindowButton:NSWindowToolbarButton];
625 [pillButton setAction:@selector(toggleToolbar:)];
626 [pillButton setTarget:windowController];
630 [self addMenuWithTag:tag parent:parentTag title:title atIndex:idx];
634 } else if (AddMenuItemMsgID == msgid) {
635 NSString *title = nil, *tip = nil, *icon = nil, *action = nil;
636 const void *bytes = [data bytes];
637 int tag = *((int*)bytes); bytes += sizeof(int);
638 int parentTag = *((int*)bytes); bytes += sizeof(int);
639 int namelen = *((int*)bytes); bytes += sizeof(int);
641 title = [[NSString alloc] initWithBytes:(void*)bytes length:namelen
642 encoding:NSUTF8StringEncoding];
645 int tiplen = *((int*)bytes); bytes += sizeof(int);
647 tip = [[NSString alloc] initWithBytes:(void*)bytes length:tiplen
648 encoding:NSUTF8StringEncoding];
651 int iconlen = *((int*)bytes); bytes += sizeof(int);
653 icon = [[NSString alloc] initWithBytes:(void*)bytes length:iconlen
654 encoding:NSUTF8StringEncoding];
657 int actionlen = *((int*)bytes); bytes += sizeof(int);
659 action = [[NSString alloc] initWithBytes:(void*)bytes
661 encoding:NSUTF8StringEncoding];
664 int idx = *((int*)bytes); bytes += sizeof(int);
665 if (idx < 0) idx = 0;
666 int key = *((int*)bytes); bytes += sizeof(int);
667 int mask = *((int*)bytes); bytes += sizeof(int);
669 NSString *ident = [NSString stringWithFormat:@"%d.%d",
670 (int)self, parentTag];
671 if (toolbar && [[toolbar identifier] isEqual:ident]) {
672 [self addToolbarItemWithTag:tag label:title tip:tip icon:icon
675 NSMenu *parent = [self menuForTag:parentTag];
676 [self addMenuItemWithTag:tag parent:parent title:title tip:tip
677 keyEquivalent:key modifiers:mask action:action
685 } else if (RemoveMenuItemMsgID == msgid) {
686 const void *bytes = [data bytes];
687 int tag = *((int*)bytes); bytes += sizeof(int);
691 if ((item = [self toolbarItemForTag:tag index:&idx])) {
692 [toolbar removeItemAtIndex:idx];
693 } else if ((item = [self menuItemForTag:tag])) {
696 if ([item menu] == [NSApp mainMenu] || ![item menu]) {
697 // NOTE: To be on the safe side we try to remove the item from
698 // both arrays (it is ok to call removeObject: even if an array
699 // does not contain the object to remove).
700 [mainMenuItems removeObject:item];
701 [popupMenuItems removeObject:item];
705 [[item menu] removeItem:item];
710 // Reset cached menu, just to be on the safe side.
711 lastMenuSearched = nil;
712 } else if (EnableMenuItemMsgID == msgid) {
713 const void *bytes = [data bytes];
714 int tag = *((int*)bytes); bytes += sizeof(int);
715 int state = *((int*)bytes); bytes += sizeof(int);
717 id item = [self toolbarItemForTag:tag index:NULL];
719 item = [self menuItemForTag:tag];
721 [item setEnabled:state];
722 } else if (ShowToolbarMsgID == msgid) {
723 const void *bytes = [data bytes];
724 int enable = *((int*)bytes); bytes += sizeof(int);
725 int flags = *((int*)bytes); bytes += sizeof(int);
727 int mode = NSToolbarDisplayModeDefault;
728 if (flags & ToolbarLabelFlag) {
729 mode = flags & ToolbarIconFlag ? NSToolbarDisplayModeIconAndLabel
730 : NSToolbarDisplayModeLabelOnly;
731 } else if (flags & ToolbarIconFlag) {
732 mode = NSToolbarDisplayModeIconOnly;
735 int size = flags & ToolbarSizeRegularFlag ? NSToolbarSizeModeRegular
736 : NSToolbarSizeModeSmall;
738 [windowController showToolbar:enable size:size mode:mode];
739 } else if (CreateScrollbarMsgID == msgid) {
740 const void *bytes = [data bytes];
741 long ident = *((long*)bytes); bytes += sizeof(long);
742 int type = *((int*)bytes); bytes += sizeof(int);
744 [windowController createScrollbarWithIdentifier:ident type:type];
745 } else if (DestroyScrollbarMsgID == msgid) {
746 const void *bytes = [data bytes];
747 long ident = *((long*)bytes); bytes += sizeof(long);
749 [windowController destroyScrollbarWithIdentifier:ident];
750 } else if (ShowScrollbarMsgID == msgid) {
751 const void *bytes = [data bytes];
752 long ident = *((long*)bytes); bytes += sizeof(long);
753 int visible = *((int*)bytes); bytes += sizeof(int);
755 [windowController showScrollbarWithIdentifier:ident state:visible];
756 } else if (SetScrollbarPositionMsgID == msgid) {
757 const void *bytes = [data bytes];
758 long ident = *((long*)bytes); bytes += sizeof(long);
759 int pos = *((int*)bytes); bytes += sizeof(int);
760 int len = *((int*)bytes); bytes += sizeof(int);
762 [windowController setScrollbarPosition:pos length:len
764 } else if (SetScrollbarThumbMsgID == msgid) {
765 const void *bytes = [data bytes];
766 long ident = *((long*)bytes); bytes += sizeof(long);
767 float val = *((float*)bytes); bytes += sizeof(float);
768 float prop = *((float*)bytes); bytes += sizeof(float);
770 [windowController setScrollbarThumbValue:val proportion:prop
772 } else if (SetFontMsgID == msgid) {
773 const void *bytes = [data bytes];
774 float size = *((float*)bytes); bytes += sizeof(float);
775 int len = *((int*)bytes); bytes += sizeof(int);
776 NSString *name = [[NSString alloc]
777 initWithBytes:(void*)bytes length:len
778 encoding:NSUTF8StringEncoding];
779 NSFont *font = [NSFont fontWithName:name size:size];
782 [windowController setFont:font];
785 } else if (SetWideFontMsgID == msgid) {
786 const void *bytes = [data bytes];
787 float size = *((float*)bytes); bytes += sizeof(float);
788 int len = *((int*)bytes); bytes += sizeof(int);
790 NSString *name = [[NSString alloc]
791 initWithBytes:(void*)bytes length:len
792 encoding:NSUTF8StringEncoding];
793 NSFont *font = [NSFont fontWithName:name size:size];
794 [windowController setWideFont:font];
798 [windowController setWideFont:nil];
800 } else if (SetDefaultColorsMsgID == msgid) {
801 const void *bytes = [data bytes];
802 unsigned bg = *((unsigned*)bytes); bytes += sizeof(unsigned);
803 unsigned fg = *((unsigned*)bytes); bytes += sizeof(unsigned);
804 NSColor *back = [NSColor colorWithArgbInt:bg];
805 NSColor *fore = [NSColor colorWithRgbInt:fg];
807 [windowController setDefaultColorsBackground:back foreground:fore];
808 } else if (ExecuteActionMsgID == msgid) {
809 const void *bytes = [data bytes];
810 int len = *((int*)bytes); bytes += sizeof(int);
811 NSString *actionName = [[NSString alloc]
812 initWithBytesNoCopy:(void*)bytes
814 encoding:NSUTF8StringEncoding
817 SEL sel = NSSelectorFromString(actionName);
818 [NSApp sendAction:sel to:nil from:self];
820 [actionName release];
821 } else if (ShowPopupMenuMsgID == msgid) {
822 const void *bytes = [data bytes];
823 int row = *((int*)bytes); bytes += sizeof(int);
824 int col = *((int*)bytes); bytes += sizeof(int);
825 int len = *((int*)bytes); bytes += sizeof(int);
826 NSString *title = [[NSString alloc]
827 initWithBytesNoCopy:(void*)bytes
829 encoding:NSUTF8StringEncoding
832 NSMenu *menu = [self topLevelMenuForTitle:title];
834 [windowController popupMenu:menu atRow:row column:col];
836 NSLog(@"WARNING: Cannot popup menu with title %@; no such menu.",
841 } else if (SetMouseShapeMsgID == msgid) {
842 const void *bytes = [data bytes];
843 int shape = *((int*)bytes); bytes += sizeof(int);
845 [windowController setMouseShape:shape];
846 } else if (AdjustLinespaceMsgID == msgid) {
847 const void *bytes = [data bytes];
848 int linespace = *((int*)bytes); bytes += sizeof(int);
850 [windowController adjustLinespace:linespace];
851 } else if (ActivateMsgID == msgid) {
852 //NSLog(@"ActivateMsgID");
853 [NSApp activateIgnoringOtherApps:YES];
854 [[windowController window] makeKeyAndOrderFront:self];
855 } else if (SetServerNameMsgID == msgid) {
856 NSString *name = [[NSString alloc] initWithData:data
857 encoding:NSUTF8StringEncoding];
858 [self setServerName:name];
860 } else if (EnterFullscreenMsgID == msgid) {
861 [windowController enterFullscreen];
862 } else if (LeaveFullscreenMsgID == msgid) {
863 [windowController leaveFullscreen];
864 } else if (BuffersNotModifiedMsgID == msgid) {
865 [[windowController window] setDocumentEdited:NO];
866 } else if (BuffersModifiedMsgID == msgid) {
867 [[windowController window] setDocumentEdited:YES];
868 } else if (SetPreEditPositionMsgID == msgid) {
869 const int *dim = (const int*)[data bytes];
870 [[windowController textView] setPreEditRow:dim[0] column:dim[1]];
872 NSLog(@"WARNING: Unknown message received (msgid=%d)", msgid);
877 #define MM_DEBUG_DRAWING 0
879 - (void)performBatchDrawWithData:(NSData *)data
881 // TODO! Move to window controller.
882 MMTextStorage *textStorage = [windowController textStorage];
883 MMTextView *textView = [windowController textView];
884 if (!(textStorage && textView))
887 const void *bytes = [data bytes];
888 const void *end = bytes + [data length];
891 NSLog(@"====> BEGIN %s", _cmd);
893 [textStorage beginEditing];
895 // TODO: Sanity check input
897 while (bytes < end) {
898 int type = *((int*)bytes); bytes += sizeof(int);
900 if (ClearAllDrawType == type) {
902 NSLog(@" Clear all");
904 [textStorage clearAll];
905 } else if (ClearBlockDrawType == type) {
906 unsigned color = *((unsigned*)bytes); bytes += sizeof(unsigned);
907 int row1 = *((int*)bytes); bytes += sizeof(int);
908 int col1 = *((int*)bytes); bytes += sizeof(int);
909 int row2 = *((int*)bytes); bytes += sizeof(int);
910 int col2 = *((int*)bytes); bytes += sizeof(int);
913 NSLog(@" Clear block (%d,%d) -> (%d,%d)", row1, col1,
916 [textStorage clearBlockFromRow:row1 column:col1
917 toRow:row2 column:col2
918 color:[NSColor colorWithArgbInt:color]];
919 } else if (DeleteLinesDrawType == type) {
920 unsigned color = *((unsigned*)bytes); bytes += sizeof(unsigned);
921 int row = *((int*)bytes); bytes += sizeof(int);
922 int count = *((int*)bytes); bytes += sizeof(int);
923 int bot = *((int*)bytes); bytes += sizeof(int);
924 int left = *((int*)bytes); bytes += sizeof(int);
925 int right = *((int*)bytes); bytes += sizeof(int);
928 NSLog(@" Delete %d line(s) from %d", count, row);
930 [textStorage deleteLinesFromRow:row lineCount:count
931 scrollBottom:bot left:left right:right
932 color:[NSColor colorWithArgbInt:color]];
933 } else if (DrawStringDrawType == type) {
934 int bg = *((int*)bytes); bytes += sizeof(int);
935 int fg = *((int*)bytes); bytes += sizeof(int);
936 int sp = *((int*)bytes); bytes += sizeof(int);
937 int row = *((int*)bytes); bytes += sizeof(int);
938 int col = *((int*)bytes); bytes += sizeof(int);
939 int cells = *((int*)bytes); bytes += sizeof(int);
940 int flags = *((int*)bytes); bytes += sizeof(int);
941 int len = *((int*)bytes); bytes += sizeof(int);
942 NSString *string = [[NSString alloc]
943 initWithBytesNoCopy:(void*)bytes
945 encoding:NSUTF8StringEncoding
950 NSLog(@" Draw string at (%d,%d) length=%d flags=%d fg=0x%x "
951 "bg=0x%x sp=0x%x (%@)", row, col, len, flags, fg, bg, sp,
952 len > 0 ? [string substringToIndex:1] : @"");
954 // NOTE: If this is a call to draw the (block) cursor, then cancel
955 // any previous request to draw the insertion point, or it might
956 // get drawn as well.
957 if (flags & DRAW_CURSOR) {
958 [textView setShouldDrawInsertionPoint:NO];
959 //NSColor *color = [NSColor colorWithRgbInt:bg];
960 //[textView drawInsertionPointAtRow:row column:col
961 // shape:MMInsertionPointBlock
965 [textStorage drawString:string
966 atRow:row column:col cells:cells
968 foregroundColor:[NSColor colorWithRgbInt:fg]
969 backgroundColor:[NSColor colorWithArgbInt:bg]
970 specialColor:[NSColor colorWithRgbInt:sp]];
973 } else if (InsertLinesDrawType == type) {
974 unsigned color = *((unsigned*)bytes); bytes += sizeof(unsigned);
975 int row = *((int*)bytes); bytes += sizeof(int);
976 int count = *((int*)bytes); bytes += sizeof(int);
977 int bot = *((int*)bytes); bytes += sizeof(int);
978 int left = *((int*)bytes); bytes += sizeof(int);
979 int right = *((int*)bytes); bytes += sizeof(int);
982 NSLog(@" Insert %d line(s) at row %d", count, row);
984 [textStorage insertLinesAtRow:row lineCount:count
985 scrollBottom:bot left:left right:right
986 color:[NSColor colorWithArgbInt:color]];
987 } else if (DrawCursorDrawType == type) {
988 unsigned color = *((unsigned*)bytes); bytes += sizeof(unsigned);
989 int row = *((int*)bytes); bytes += sizeof(int);
990 int col = *((int*)bytes); bytes += sizeof(int);
991 int shape = *((int*)bytes); bytes += sizeof(int);
992 int percent = *((int*)bytes); bytes += sizeof(int);
995 NSLog(@" Draw cursor at (%d,%d)", row, col);
997 [textView drawInsertionPointAtRow:row column:col shape:shape
999 color:[NSColor colorWithRgbInt:color]];
1001 NSLog(@"WARNING: Unknown draw type (type=%d)", type);
1005 [textStorage endEditing];
1007 // NOTE: During resizing, Cocoa only sends draw messages before Vim's rows
1008 // and columns are changed (due to ipc delays). Force a redraw here.
1009 [[windowController vimView] displayIfNeeded];
1011 #if MM_DEBUG_DRAWING
1012 NSLog(@"<==== END %s", _cmd);
1016 - (void)savePanelDidEnd:(NSSavePanel *)panel code:(int)code
1017 context:(void *)context
1019 NSString *string = (code == NSOKButton) ? [panel filename] : nil;
1021 [backendProxy setDialogReturn:string];
1023 @catch (NSException *e) {
1024 NSLog(@"Exception caught in %s %@", _cmd, e);
1028 - (void)alertDidEnd:(MMAlert *)alert code:(int)code context:(void *)context
1032 code = code - NSAlertFirstButtonReturn + 1;
1034 if ([alert isKindOfClass:[MMAlert class]] && [alert textField]) {
1035 ret = [NSArray arrayWithObjects:[NSNumber numberWithInt:code],
1036 [[alert textField] stringValue], nil];
1038 ret = [NSArray arrayWithObject:[NSNumber numberWithInt:code]];
1042 [backendProxy setDialogReturn:ret];
1044 @catch (NSException *e) {
1045 NSLog(@"Exception caught in %s %@", _cmd, e);
1049 - (NSMenuItem *)recurseMenuItemForTag:(int)tag rootMenu:(NSMenu *)root
1052 NSMenuItem *item = [root itemWithTag:tag];
1054 lastMenuSearched = root;
1058 NSArray *items = [root itemArray];
1059 unsigned i, count = [items count];
1060 for (i = 0; i < count; ++i) {
1061 item = [items objectAtIndex:i];
1062 if ([item hasSubmenu]) {
1063 item = [self recurseMenuItemForTag:tag
1064 rootMenu:[item submenu]];
1066 lastMenuSearched = [item submenu];
1076 - (NSMenuItem *)menuItemForTag:(int)tag
1078 // First search the same menu that was search last time this method was
1079 // called. Since this method is often called for each menu item in a
1080 // menu this can significantly improve search times.
1081 if (lastMenuSearched) {
1082 NSMenuItem *item = [self recurseMenuItemForTag:tag
1083 rootMenu:lastMenuSearched];
1084 if (item) return item;
1087 // Search the main menu.
1088 int i, count = [mainMenuItems count];
1089 for (i = 0; i < count; ++i) {
1090 NSMenuItem *item = [mainMenuItems objectAtIndex:i];
1091 if ([item tag] == tag) return item;
1092 item = [self recurseMenuItemForTag:tag rootMenu:[item submenu]];
1094 lastMenuSearched = [item submenu];
1099 // Search the popup menus.
1100 count = [popupMenuItems count];
1101 for (i = 0; i < count; ++i) {
1102 NSMenuItem *item = [popupMenuItems objectAtIndex:i];
1103 if ([item tag] == tag) return item;
1104 item = [self recurseMenuItemForTag:tag rootMenu:[item submenu]];
1106 lastMenuSearched = [item submenu];
1114 - (NSMenu *)menuForTag:(int)tag
1116 return [[self menuItemForTag:tag] submenu];
1119 - (NSMenu *)topLevelMenuForTitle:(NSString *)title
1121 // Search only the top-level menus.
1123 unsigned i, count = [popupMenuItems count];
1124 for (i = 0; i < count; ++i) {
1125 NSMenuItem *item = [popupMenuItems objectAtIndex:i];
1126 if ([title isEqual:[item title]])
1127 return [item submenu];
1130 count = [mainMenuItems count];
1131 for (i = 0; i < count; ++i) {
1132 NSMenuItem *item = [mainMenuItems objectAtIndex:i];
1133 if ([title isEqual:[item title]])
1134 return [item submenu];
1140 - (void)addMenuWithTag:(int)tag parent:(int)parentTag title:(NSString *)title
1143 NSMenu *parent = [self menuForTag:parentTag];
1144 NSMenuItem *item = [[NSMenuItem alloc] init];
1145 NSMenu *menu = [[NSMenu alloc] initWithTitle:title];
1147 [menu setAutoenablesItems:NO];
1149 [item setTitle:title];
1150 [item setSubmenu:menu];
1153 if ([parent numberOfItems] <= idx) {
1154 [parent addItem:item];
1156 [parent insertItem:item atIndex:idx];
1159 NSMutableArray *items = (MenuPopupType == parentTag)
1160 ? popupMenuItems : mainMenuItems;
1161 if ([items count] <= idx) {
1162 [items addObject:item];
1164 [items insertObject:item atIndex:idx];
1167 shouldUpdateMainMenu = (MenuPopupType != parentTag);
1174 - (void)addMenuItemWithTag:(int)tag parent:(NSMenu *)parent
1175 title:(NSString *)title tip:(NSString *)tip
1176 keyEquivalent:(int)key modifiers:(int)mask
1177 action:(NSString *)action atIndex:(int)idx
1180 NSMenuItem *item = nil;
1181 if (!title || ([title hasPrefix:@"-"] && [title hasSuffix:@"-"])) {
1182 item = [NSMenuItem separatorItem];
1184 item = [[[NSMenuItem alloc] init] autorelease];
1185 [item setTitle:title];
1186 // TODO: Check that 'action' is a valid action (nothing will happen
1187 // if it isn't, but it would be nice with a warning).
1188 if (action) [item setAction:NSSelectorFromString(action)];
1189 else [item setAction:@selector(vimMenuItemAction:)];
1190 if (tip) [item setToolTip:tip];
1193 NSString *keyString =
1194 [NSString stringWithFormat:@"%C", key];
1195 [item setKeyEquivalent:keyString];
1196 [item setKeyEquivalentModifierMask:mask];
1200 // NOTE! The tag is used to idenfity which menu items were
1201 // added by Vim (tag != 0) and which were added by the AppKit
1205 if ([parent numberOfItems] <= idx) {
1206 [parent addItem:item];
1208 [parent insertItem:item atIndex:idx];
1211 NSLog(@"WARNING: Menu item '%@' (tag=%d) has no parent.", title, tag);
1215 - (void)updateMainMenu
1217 NSMenu *mainMenu = [NSApp mainMenu];
1219 // Stop NSApp from updating the Window menu.
1220 [NSApp setWindowsMenu:nil];
1222 // Remove all menus from main menu (except the MacVim menu).
1223 int i, count = [mainMenu numberOfItems];
1224 for (i = count-1; i > 0; --i) {
1225 [mainMenu removeItemAtIndex:i];
1228 // Add menus from 'mainMenuItems' to main menu.
1229 count = [mainMenuItems count];
1230 for (i = 0; i < count; ++i) {
1231 [mainMenu addItem:[mainMenuItems objectAtIndex:i]];
1234 // Set the new Window menu.
1235 // TODO! Need to look for 'Window' in all localized languages.
1236 NSMenu *windowMenu = [[mainMenu itemWithTitle:@"Window"] submenu];
1238 // Remove all AppKit owned menu items (tag == 0); they will be added
1239 // again when setWindowsMenu: is called.
1240 count = [windowMenu numberOfItems];
1241 for (i = count-1; i >= 0; --i) {
1242 NSMenuItem *item = [windowMenu itemAtIndex:i];
1244 [windowMenu removeItem:item];
1248 [NSApp setWindowsMenu:windowMenu];
1251 shouldUpdateMainMenu = NO;
1254 - (NSToolbarItem *)toolbarItemForTag:(int)tag index:(int *)index
1256 if (!toolbar) return nil;
1258 NSArray *items = [toolbar items];
1259 int i, count = [items count];
1260 for (i = 0; i < count; ++i) {
1261 NSToolbarItem *item = [items objectAtIndex:i];
1262 if ([item tag] == tag) {
1263 if (index) *index = i;
1271 - (void)addToolbarItemToDictionaryWithTag:(int)tag label:(NSString *)title
1272 toolTip:(NSString *)tip icon:(NSString *)icon
1274 // If the item corresponds to a separator then do nothing, since it is
1275 // already defined by Cocoa.
1276 if (!title || [title isEqual:NSToolbarSeparatorItemIdentifier]
1277 || [title isEqual:NSToolbarSpaceItemIdentifier]
1278 || [title isEqual:NSToolbarFlexibleSpaceItemIdentifier])
1281 NSToolbarItem *item = [[NSToolbarItem alloc] initWithItemIdentifier:title];
1283 [item setLabel:title];
1284 [item setToolTip:tip];
1285 [item setAction:@selector(vimMenuItemAction:)];
1286 [item setAutovalidates:NO];
1288 NSImage *img = [NSImage imageNamed:icon];
1290 NSLog(@"WARNING: Could not find image with name '%@' to use as toolbar"
1291 " image for identifier '%@';"
1292 " using default toolbar icon '%@' instead.",
1293 icon, title, MMDefaultToolbarImageName);
1295 img = [NSImage imageNamed:MMDefaultToolbarImageName];
1298 [item setImage:img];
1300 [toolbarItemDict setObject:item forKey:title];
1305 - (void)addToolbarItemWithTag:(int)tag label:(NSString *)label tip:(NSString
1306 *)tip icon:(NSString *)icon atIndex:(int)idx
1308 if (!toolbar) return;
1310 // Check for separator items.
1312 label = NSToolbarSeparatorItemIdentifier;
1313 } else if ([label length] >= 2 && [label hasPrefix:@"-"]
1314 && [label hasSuffix:@"-"]) {
1315 // The label begins and ends with '-'; decided which kind of separator
1316 // item it is by looking at the prefix.
1317 if ([label hasPrefix:@"-space"]) {
1318 label = NSToolbarSpaceItemIdentifier;
1319 } else if ([label hasPrefix:@"-flexspace"]) {
1320 label = NSToolbarFlexibleSpaceItemIdentifier;
1322 label = NSToolbarSeparatorItemIdentifier;
1326 [self addToolbarItemToDictionaryWithTag:tag label:label toolTip:tip
1329 int maxIdx = [[toolbar items] count];
1330 if (maxIdx < idx) idx = maxIdx;
1332 [toolbar insertItemWithItemIdentifier:label atIndex:idx];
1335 - (void)connectionDidDie:(NSNotification *)notification
1337 //NSLog(@"%@ %s%@", [self className], _cmd, notification);
1341 // NOTE! This causes the call to removeVimController: to be delayed.
1343 performSelectorOnMainThread:@selector(removeVimController:)
1344 withObject:self waitUntilDone:NO];
1347 - (NSString *)description
1349 return [NSString stringWithFormat:@"%@ : isInitialized=%d inProcessCommandQueue=%d mainMenuItems=%@ popupMenuItems=%@ toolbar=%@", [self className], isInitialized, inProcessCommandQueue, mainMenuItems, popupMenuItems, toolbar];
1352 #if MM_RESEND_LAST_FAILURE
1353 - (void)resendTimerFired:(NSTimer *)timer
1355 int msgid = resendMsgid;
1358 [resendTimer release];
1365 data = [resendData copy];
1367 //NSLog(@"Resending message: %s", MessageStrings[msgid]);
1368 [self sendMessage:msgid data:data];
1372 @end // MMVimController (Private)
1376 @implementation MMAlert
1379 [textField release];
1383 - (void)setTextFieldString:(NSString *)textFieldString
1385 [textField release];
1386 textField = [[NSTextField alloc] init];
1387 [textField setStringValue:textFieldString];
1390 - (NSTextField *)textField
1395 - (void)setInformativeText:(NSString *)text
1398 // HACK! Add some space for the text field.
1399 [super setInformativeText:[text stringByAppendingString:@"\n\n\n"]];
1401 [super setInformativeText:text];
1405 - (void)beginSheetModalForWindow:(NSWindow *)window
1406 modalDelegate:(id)delegate
1407 didEndSelector:(SEL)didEndSelector
1408 contextInfo:(void *)contextInfo
1410 [super beginSheetModalForWindow:window
1411 modalDelegate:delegate
1412 didEndSelector:didEndSelector
1413 contextInfo:contextInfo];
1415 // HACK! Place the input text field at the bottom of the informative text
1416 // (which has been made a bit larger by adding newline characters).
1417 NSView *contentView = [[self window] contentView];
1418 NSRect rect = [contentView frame];
1419 rect.origin.y = rect.size.height;
1421 NSArray *subviews = [contentView subviews];
1422 unsigned i, count = [subviews count];
1423 for (i = 0; i < count; ++i) {
1424 NSView *view = [subviews objectAtIndex:i];
1425 if ([view isKindOfClass:[NSTextField class]]
1426 && [view frame].origin.y < rect.origin.y) {
1427 // NOTE: The informative text field is the lowest NSTextField in
1428 // the alert dialog.
1429 rect = [view frame];
1433 rect.size.height = MMAlertTextFieldHeight;
1434 [textField setFrame:rect];
1435 [contentView addSubview:textField];
1436 [textField becomeFirstResponder];