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.
11 #import "MMVimController.h"
12 #import "MMWindowController.h"
13 #import "MMTextView.h"
14 #import "MMAppController.h"
15 #import "MMTextStorage.h"
18 // This is taken from gui.h
19 #define DRAW_CURSOR 0x20
21 static NSString *MMDefaultToolbarImageName = @"Attention";
22 static int MMAlertTextFieldHeight = 22;
24 // NOTE: By default a message sent to the backend will be dropped if it cannot
25 // be delivered instantly; otherwise there is a possibility that MacVim will
26 // 'beachball' while waiting to deliver DO messages to an unresponsive Vim
27 // process. This means that you cannot rely on any message sent with
28 // sendMessage: to actually reach Vim.
29 static NSTimeInterval MMBackendProxyRequestTimeout = 0;
31 #if MM_RESEND_LAST_FAILURE
32 // If a message send fails, the message will be resent after this many seconds
33 // have passed. (No queue is kept, only the very last message is resent.)
34 static NSTimeInterval MMResendInterval = 0.5;
38 @interface MMAlert : NSAlert {
39 NSTextField *textField;
41 - (void)setTextFieldString:(NSString *)textFieldString;
42 - (NSTextField *)textField;
46 @interface MMVimController (Private)
47 - (void)handleMessage:(int)msgid data:(NSData *)data;
48 - (void)performBatchDrawWithData:(NSData *)data;
49 - (void)savePanelDidEnd:(NSSavePanel *)panel code:(int)code
50 context:(void *)context;
51 - (void)alertDidEnd:(MMAlert *)alert code:(int)code context:(void *)context;
52 - (NSMenuItem *)recurseMenuItemForTag:(int)tag rootMenu:(NSMenu *)root;
53 - (NSMenuItem *)menuItemForTag:(int)tag;
54 - (NSMenu *)menuForTag:(int)tag;
55 - (NSMenu *)topLevelMenuForTitle:(NSString *)title;
56 - (void)addMenuWithTag:(int)tag parent:(int)parentTag title:(NSString *)title
58 - (void)addMenuItemWithTag:(int)tag parent:(NSMenu *)parent
59 title:(NSString *)title tip:(NSString *)tip
60 keyEquivalent:(int)key modifiers:(int)mask
61 action:(NSString *)action atIndex:(int)idx;
62 - (void)updateMainMenu;
63 - (NSToolbarItem *)toolbarItemForTag:(int)tag index:(int *)index;
64 - (void)addToolbarItemToDictionaryWithTag:(int)tag label:(NSString *)title
65 toolTip:(NSString *)tip icon:(NSString *)icon;
66 - (void)addToolbarItemWithTag:(int)tag label:(NSString *)label
67 tip:(NSString *)tip icon:(NSString *)icon
69 - (void)connectionDidDie:(NSNotification *)notification;
70 #if MM_RESEND_LAST_FAILURE
71 - (void)resendTimerFired:(NSTimer *)timer;
77 // TODO: Move to separate file
78 @interface NSColor (MMProtocol)
79 + (NSColor *)colorWithRgbInt:(unsigned)rgb;
80 + (NSColor *)colorWithArgbInt:(unsigned)argb;
86 @implementation MMVimController
88 - (id)initWithBackend:(id)backend pid:(int)processIdentifier
90 if ((self = [super init])) {
92 [[MMWindowController alloc] initWithVimController:self];
93 backendProxy = [backend retain];
94 sendQueue = [NSMutableArray new];
95 mainMenuItems = [[NSMutableArray alloc] init];
96 popupMenuItems = [[NSMutableArray alloc] init];
97 toolbarItemDict = [[NSMutableDictionary alloc] init];
98 pid = processIdentifier;
100 NSConnection *connection = [backendProxy connectionForProxy];
102 // TODO: Check that this will not set the timeout for the root proxy
103 // (in MMAppController).
104 [connection setRequestTimeout:MMBackendProxyRequestTimeout];
106 [[NSNotificationCenter defaultCenter] addObserver:self
107 selector:@selector(connectionDidDie:)
108 name:NSConnectionDidDieNotification object:connection];
111 NSWindow *win = [windowController window];
113 [[NSNotificationCenter defaultCenter]
115 selector:@selector(windowDidBecomeMain:)
116 name:NSWindowDidBecomeMainNotification
127 //NSLog(@"%@ %s", [self className], _cmd);
130 #if MM_RESEND_LAST_FAILURE
131 [resendData release]; resendData = nil;
134 [serverName release]; serverName = nil;
135 [backendProxy release]; backendProxy = nil;
136 [sendQueue release]; sendQueue = nil;
138 [toolbarItemDict release]; toolbarItemDict = nil;
139 [toolbar release]; toolbar = nil;
140 [popupMenuItems release]; popupMenuItems = nil;
141 [mainMenuItems release]; mainMenuItems = nil;
142 [windowController release]; windowController = nil;
147 - (MMWindowController *)windowController
149 return windowController;
152 - (void)setServerName:(NSString *)name
154 if (name != serverName) {
155 [serverName release];
156 serverName = [name copy];
160 - (NSString *)serverName
170 - (void)dropFiles:(NSArray *)filenames
172 int i, numberOfFiles = [filenames count];
173 NSMutableData *data = [NSMutableData data];
175 [data appendBytes:&numberOfFiles length:sizeof(int)];
177 for (i = 0; i < numberOfFiles; ++i) {
178 NSString *file = [filenames objectAtIndex:i];
179 int len = [file lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
182 ++len; // include NUL as well
183 [data appendBytes:&len length:sizeof(int)];
184 [data appendBytes:[file UTF8String] length:len];
188 [self sendMessage:DropFilesMsgID data:data];
191 - (void)dropString:(NSString *)string
193 int len = [string lengthOfBytesUsingEncoding:NSUTF8StringEncoding] + 1;
195 NSMutableData *data = [NSMutableData data];
197 [data appendBytes:&len length:sizeof(int)];
198 [data appendBytes:[string UTF8String] length:len];
200 [self sendMessage:DropStringMsgID data:data];
204 - (void)sendMessage:(int)msgid data:(NSData *)data
206 //NSLog(@"sendMessage:%s (isInitialized=%d inProcessCommandQueue=%d)",
207 // MessageStrings[msgid], isInitialized, inProcessCommandQueue);
209 if (!isInitialized) return;
211 if (inProcessCommandQueue) {
212 //NSLog(@"In process command queue; delaying message send.");
213 [sendQueue addObject:[NSNumber numberWithInt:msgid]];
215 [sendQueue addObject:data];
217 [sendQueue addObject:[NSNull null]];
221 #if MM_RESEND_LAST_FAILURE
223 //NSLog(@"cancelling scheduled resend of %s",
224 // MessageStrings[resendMsgid]);
226 [resendTimer invalidate];
227 [resendTimer release];
232 [resendData release];
238 [backendProxy processInput:msgid data:data];
240 @catch (NSException *e) {
241 //NSLog(@"%@ %s Exception caught during DO call: %@",
242 // [self className], _cmd, e);
243 #if MM_RESEND_LAST_FAILURE
244 //NSLog(@"%s failed, scheduling message %s for resend", _cmd,
245 // MessageStrings[msgid]);
248 resendData = [data retain];
249 resendTimer = [NSTimer
250 scheduledTimerWithTimeInterval:MMResendInterval
252 selector:@selector(resendTimerFired:)
255 [resendTimer retain];
260 - (BOOL)sendMessageNow:(int)msgid data:(NSData *)data
261 timeout:(NSTimeInterval)timeout
263 if (!isInitialized || inProcessCommandQueue)
266 if (timeout < 0) timeout = 0;
269 NSConnection *conn = [backendProxy connectionForProxy];
270 NSTimeInterval oldTimeout = [conn requestTimeout];
272 [conn setRequestTimeout:timeout];
275 [backendProxy processInput:msgid data:data];
277 @catch (NSException *e) {
281 [conn setRequestTimeout:oldTimeout];
287 - (void)addVimInput:(NSString *)string
289 // This is a very general method of adding input to the Vim process. It is
290 // basically the same as calling remote_send() on the process (see
291 // ':h remote_send').
293 NSData *data = [string dataUsingEncoding:NSUTF8StringEncoding];
294 [self sendMessage:AddInputMsgID data:data];
305 //NSLog(@"%@ %s", [self className], _cmd);
306 if (!isInitialized) return;
309 [toolbar setDelegate:nil];
310 [[NSNotificationCenter defaultCenter] removeObserver:self];
311 [windowController cleanup];
314 - (oneway void)showSavePanelForDirectory:(in bycopy NSString *)dir
315 title:(in bycopy NSString *)title
318 if (!isInitialized) return;
321 [[NSSavePanel savePanel] beginSheetForDirectory:dir file:nil
322 modalForWindow:[windowController window]
324 didEndSelector:@selector(savePanelDidEnd:code:context:)
327 NSOpenPanel *panel = [NSOpenPanel openPanel];
328 [panel setAllowsMultipleSelection:NO];
329 [panel beginSheetForDirectory:dir file:nil types:nil
330 modalForWindow:[windowController window]
332 didEndSelector:@selector(savePanelDidEnd:code:context:)
337 - (oneway void)presentDialogWithStyle:(int)style
338 message:(in bycopy NSString *)message
339 informativeText:(in bycopy NSString *)text
340 buttonTitles:(in bycopy NSArray *)buttonTitles
341 textFieldString:(in bycopy NSString *)textFieldString
343 if (!(windowController && buttonTitles && [buttonTitles count])) return;
345 MMAlert *alert = [[MMAlert alloc] init];
347 // NOTE! This has to be done before setting the informative text.
349 [alert setTextFieldString:textFieldString];
351 [alert setAlertStyle:style];
354 [alert setMessageText:message];
356 // If no message text is specified 'Alert' is used, which we don't
357 // want, so set an empty string as message text.
358 [alert setMessageText:@""];
362 [alert setInformativeText:text];
363 } else if (textFieldString) {
364 // Make sure there is always room for the input text field.
365 [alert setInformativeText:@""];
368 unsigned i, count = [buttonTitles count];
369 for (i = 0; i < count; ++i) {
370 NSString *title = [buttonTitles objectAtIndex:i];
371 // NOTE: The title of the button may contain the character '&' to
372 // indicate that the following letter should be the key equivalent
373 // associated with the button. Extract this letter and lowercase it.
374 NSString *keyEquivalent = nil;
375 NSRange hotkeyRange = [title rangeOfString:@"&"];
376 if (NSNotFound != hotkeyRange.location) {
377 if ([title length] > NSMaxRange(hotkeyRange)) {
378 NSRange keyEquivRange = NSMakeRange(hotkeyRange.location+1, 1);
379 keyEquivalent = [[title substringWithRange:keyEquivRange]
383 NSMutableString *string = [NSMutableString stringWithString:title];
384 [string deleteCharactersInRange:hotkeyRange];
388 [alert addButtonWithTitle:title];
390 // Set key equivalent for the button, but only if NSAlert hasn't
391 // already done so. (Check the documentation for
392 // - [NSAlert addButtonWithTitle:] to see what key equivalents are
393 // automatically assigned.)
394 NSButton *btn = [[alert buttons] lastObject];
395 if ([[btn keyEquivalent] length] == 0 && keyEquivalent) {
396 [btn setKeyEquivalent:keyEquivalent];
400 [alert beginSheetModalForWindow:[windowController window]
402 didEndSelector:@selector(alertDidEnd:code:context:)
408 - (oneway void)processCommandQueue:(in bycopy NSArray *)queue
410 if (!isInitialized) return;
412 unsigned i, count = [queue count];
414 NSLog(@"WARNING: Uneven number of components (%d) in flush queue "
415 "message; ignoring this message.", count);
419 inProcessCommandQueue = YES;
421 //NSLog(@"======== %s BEGIN ========", _cmd);
422 for (i = 0; i < count; i += 2) {
423 NSData *value = [queue objectAtIndex:i];
424 NSData *data = [queue objectAtIndex:i+1];
426 int msgid = *((int*)[value bytes]);
427 //NSLog(@"%s%s", _cmd, MessageStrings[msgid]);
429 [self handleMessage:msgid data:data];
431 //NSLog(@"======== %s END ========", _cmd);
433 if (shouldUpdateMainMenu) {
434 [self updateMainMenu];
437 [windowController processCommandQueueDidFinish];
439 inProcessCommandQueue = NO;
441 if ([sendQueue count] > 0) {
443 [backendProxy processInputAndData:sendQueue];
445 @catch (NSException *e) {
446 // Connection timed out, just ignore this.
447 //NSLog(@"WARNING! Connection timed out in %s", _cmd);
450 [sendQueue removeAllObjects];
454 - (void)windowDidBecomeMain:(NSNotification *)notification
457 [self updateMainMenu];
460 - (NSToolbarItem *)toolbar:(NSToolbar *)theToolbar
461 itemForItemIdentifier:(NSString *)itemId
462 willBeInsertedIntoToolbar:(BOOL)flag
464 NSToolbarItem *item = [toolbarItemDict objectForKey:itemId];
466 NSLog(@"WARNING: No toolbar item with id '%@'", itemId);
472 - (NSArray *)toolbarAllowedItemIdentifiers:(NSToolbar *)theToolbar
477 - (NSArray *)toolbarDefaultItemIdentifiers:(NSToolbar *)theToolbar
482 @end // MMVimController
486 @implementation MMVimController (Private)
488 - (void)handleMessage:(int)msgid data:(NSData *)data
490 //NSLog(@"%@ %s", [self className], _cmd);
492 if (OpenVimWindowMsgID == msgid) {
493 [windowController openWindow];
494 } else if (BatchDrawMsgID == msgid) {
495 [self performBatchDrawWithData:data];
496 } else if (SelectTabMsgID == msgid) {
497 #if 0 // NOTE: Tab selection is done inside updateTabsWithData:.
498 const void *bytes = [data bytes];
499 int idx = *((int*)bytes);
500 //NSLog(@"Selecting tab with index %d", idx);
501 [windowController selectTabWithIndex:idx];
503 } else if (UpdateTabBarMsgID == msgid) {
504 [windowController updateTabsWithData:data];
505 } else if (ShowTabBarMsgID == msgid) {
506 [windowController showTabBar:YES];
507 } else if (HideTabBarMsgID == msgid) {
508 [windowController showTabBar:NO];
509 } else if (SetTextDimensionsMsgID == msgid) {
510 const void *bytes = [data bytes];
511 int rows = *((int*)bytes); bytes += sizeof(int);
512 int cols = *((int*)bytes); bytes += sizeof(int);
514 [windowController setTextDimensionsWithRows:rows columns:cols];
515 } else if (SetWindowTitleMsgID == msgid) {
516 const void *bytes = [data bytes];
517 int len = *((int*)bytes); bytes += sizeof(int);
519 NSString *string = [[NSString alloc] initWithBytes:(void*)bytes
520 length:len encoding:NSUTF8StringEncoding];
522 [[windowController window] setTitle:string];
525 } else if (AddMenuMsgID == msgid) {
526 NSString *title = nil;
527 const void *bytes = [data bytes];
528 int tag = *((int*)bytes); bytes += sizeof(int);
529 int parentTag = *((int*)bytes); bytes += sizeof(int);
530 int len = *((int*)bytes); bytes += sizeof(int);
532 title = [[NSString alloc] initWithBytes:(void*)bytes length:len
533 encoding:NSUTF8StringEncoding];
536 int idx = *((int*)bytes); bytes += sizeof(int);
538 if (MenuToolbarType == parentTag) {
540 // NOTE! Each toolbar must have a unique identifier, else each
541 // window will have the same toolbar.
542 NSString *ident = [NSString stringWithFormat:@"%d.%d",
544 toolbar = [[NSToolbar alloc] initWithIdentifier:ident];
546 [toolbar setShowsBaselineSeparator:NO];
547 [toolbar setDelegate:self];
548 [toolbar setDisplayMode:NSToolbarDisplayModeIconOnly];
549 [toolbar setSizeMode:NSToolbarSizeModeSmall];
551 NSWindow *win = [windowController window];
552 [win setToolbar:toolbar];
554 // HACK! Redirect the pill button so that we can ask Vim to
556 NSButton *pillButton = [win
557 standardWindowButton:NSWindowToolbarButton];
559 [pillButton setAction:@selector(toggleToolbar:)];
560 [pillButton setTarget:windowController];
564 [self addMenuWithTag:tag parent:parentTag title:title atIndex:idx];
568 } else if (AddMenuItemMsgID == msgid) {
569 NSString *title = nil, *tip = nil, *icon = nil, *action = nil;
570 const void *bytes = [data bytes];
571 int tag = *((int*)bytes); bytes += sizeof(int);
572 int parentTag = *((int*)bytes); bytes += sizeof(int);
573 int namelen = *((int*)bytes); bytes += sizeof(int);
575 title = [[NSString alloc] initWithBytes:(void*)bytes length:namelen
576 encoding:NSUTF8StringEncoding];
579 int tiplen = *((int*)bytes); bytes += sizeof(int);
581 tip = [[NSString alloc] initWithBytes:(void*)bytes length:tiplen
582 encoding:NSUTF8StringEncoding];
585 int iconlen = *((int*)bytes); bytes += sizeof(int);
587 icon = [[NSString alloc] initWithBytes:(void*)bytes length:iconlen
588 encoding:NSUTF8StringEncoding];
591 int actionlen = *((int*)bytes); bytes += sizeof(int);
593 action = [[NSString alloc] initWithBytes:(void*)bytes
595 encoding:NSUTF8StringEncoding];
598 int idx = *((int*)bytes); bytes += sizeof(int);
599 if (idx < 0) idx = 0;
600 int key = *((int*)bytes); bytes += sizeof(int);
601 int mask = *((int*)bytes); bytes += sizeof(int);
603 NSString *ident = [NSString stringWithFormat:@"%d.%d",
604 (int)self, parentTag];
605 if (toolbar && [[toolbar identifier] isEqual:ident]) {
606 [self addToolbarItemWithTag:tag label:title tip:tip icon:icon
609 NSMenu *parent = [self menuForTag:parentTag];
610 [self addMenuItemWithTag:tag parent:parent title:title tip:tip
611 keyEquivalent:key modifiers:mask action:action
619 } else if (RemoveMenuItemMsgID == msgid) {
620 const void *bytes = [data bytes];
621 int tag = *((int*)bytes); bytes += sizeof(int);
625 if ((item = [self toolbarItemForTag:tag index:&idx])) {
626 [toolbar removeItemAtIndex:idx];
627 } else if ((item = [self menuItemForTag:tag])) {
630 if ([item menu] == [NSApp mainMenu] || ![item menu]) {
631 // NOTE: To be on the safe side we try to remove the item from
632 // both arrays (it is ok to call removeObject: even if an array
633 // does not contain the object to remove).
634 [mainMenuItems removeObject:item];
635 [popupMenuItems removeObject:item];
639 [[item menu] removeItem:item];
644 // Reset cached menu, just to be on the safe side.
645 lastMenuSearched = nil;
646 } else if (EnableMenuItemMsgID == msgid) {
647 const void *bytes = [data bytes];
648 int tag = *((int*)bytes); bytes += sizeof(int);
649 int state = *((int*)bytes); bytes += sizeof(int);
651 id item = [self toolbarItemForTag:tag index:NULL];
653 item = [self menuItemForTag:tag];
655 [item setEnabled:state];
656 } else if (ShowToolbarMsgID == msgid) {
657 const void *bytes = [data bytes];
658 int enable = *((int*)bytes); bytes += sizeof(int);
659 int flags = *((int*)bytes); bytes += sizeof(int);
661 int mode = NSToolbarDisplayModeDefault;
662 if (flags & ToolbarLabelFlag) {
663 mode = flags & ToolbarIconFlag ? NSToolbarDisplayModeIconAndLabel
664 : NSToolbarDisplayModeLabelOnly;
665 } else if (flags & ToolbarIconFlag) {
666 mode = NSToolbarDisplayModeIconOnly;
669 int size = flags & ToolbarSizeRegularFlag ? NSToolbarSizeModeRegular
670 : NSToolbarSizeModeSmall;
672 [windowController showToolbar:enable size:size mode:mode];
673 } else if (CreateScrollbarMsgID == msgid) {
674 const void *bytes = [data bytes];
675 long ident = *((long*)bytes); bytes += sizeof(long);
676 int type = *((int*)bytes); bytes += sizeof(int);
678 [windowController createScrollbarWithIdentifier:ident type:type];
679 } else if (DestroyScrollbarMsgID == msgid) {
680 const void *bytes = [data bytes];
681 long ident = *((long*)bytes); bytes += sizeof(long);
683 [windowController destroyScrollbarWithIdentifier:ident];
684 } else if (ShowScrollbarMsgID == msgid) {
685 const void *bytes = [data bytes];
686 long ident = *((long*)bytes); bytes += sizeof(long);
687 int visible = *((int*)bytes); bytes += sizeof(int);
689 [windowController showScrollbarWithIdentifier:ident state:visible];
690 } else if (SetScrollbarPositionMsgID == msgid) {
691 const void *bytes = [data bytes];
692 long ident = *((long*)bytes); bytes += sizeof(long);
693 int pos = *((int*)bytes); bytes += sizeof(int);
694 int len = *((int*)bytes); bytes += sizeof(int);
696 [windowController setScrollbarPosition:pos length:len
698 } else if (SetScrollbarThumbMsgID == msgid) {
699 const void *bytes = [data bytes];
700 long ident = *((long*)bytes); bytes += sizeof(long);
701 float val = *((float*)bytes); bytes += sizeof(float);
702 float prop = *((float*)bytes); bytes += sizeof(float);
704 [windowController setScrollbarThumbValue:val proportion:prop
706 } else if (SetFontMsgID == msgid) {
707 const void *bytes = [data bytes];
708 float size = *((float*)bytes); bytes += sizeof(float);
709 int len = *((int*)bytes); bytes += sizeof(int);
710 NSString *name = [[NSString alloc]
711 initWithBytes:(void*)bytes length:len
712 encoding:NSUTF8StringEncoding];
713 NSFont *font = [NSFont fontWithName:name size:size];
716 [windowController setFont:font];
719 } else if (SetDefaultColorsMsgID == msgid) {
720 const void *bytes = [data bytes];
721 unsigned bg = *((unsigned*)bytes); bytes += sizeof(unsigned);
722 unsigned fg = *((unsigned*)bytes); bytes += sizeof(unsigned);
723 NSColor *back = [NSColor colorWithArgbInt:bg];
724 NSColor *fore = [NSColor colorWithRgbInt:fg];
726 [windowController setDefaultColorsBackground:back foreground:fore];
727 } else if (ExecuteActionMsgID == msgid) {
728 const void *bytes = [data bytes];
729 int len = *((int*)bytes); bytes += sizeof(int);
730 NSString *actionName = [[NSString alloc]
731 initWithBytesNoCopy:(void*)bytes
733 encoding:NSUTF8StringEncoding
736 SEL sel = NSSelectorFromString(actionName);
737 [NSApp sendAction:sel to:nil from:self];
739 [actionName release];
740 } else if (ShowPopupMenuMsgID == msgid) {
741 const void *bytes = [data bytes];
742 int row = *((int*)bytes); bytes += sizeof(int);
743 int col = *((int*)bytes); bytes += sizeof(int);
744 int len = *((int*)bytes); bytes += sizeof(int);
745 NSString *title = [[NSString alloc]
746 initWithBytesNoCopy:(void*)bytes
748 encoding:NSUTF8StringEncoding
751 NSMenu *menu = [self topLevelMenuForTitle:title];
753 [windowController popupMenu:menu atRow:row column:col];
755 NSLog(@"WARNING: Cannot popup menu with title %@; no such menu.",
760 } else if (SetMouseShapeMsgID == msgid) {
761 const void *bytes = [data bytes];
762 int shape = *((int*)bytes); bytes += sizeof(int);
764 [windowController setMouseShape:shape];
765 } else if (AdjustLinespaceMsgID == msgid) {
766 const void *bytes = [data bytes];
767 int linespace = *((int*)bytes); bytes += sizeof(int);
769 [windowController adjustLinespace:linespace];
770 } else if (ActivateMsgID == msgid) {
771 //NSLog(@"ActivateMsgID");
772 [NSApp activateIgnoringOtherApps:YES];
773 [[windowController window] makeKeyAndOrderFront:self];
774 } else if (SetServerNameMsgID == msgid) {
775 NSString *name = [[NSString alloc] initWithData:data
776 encoding:NSUTF8StringEncoding];
777 [self setServerName:name];
779 } else if (EnterFullscreenMsgID == msgid) {
780 [windowController enterFullscreen];
781 } else if (LeaveFullscreenMsgID == msgid) {
782 [windowController leaveFullscreen];
783 } else if (BuffersNotModifiedMsgID == msgid) {
784 [[windowController window] setDocumentEdited:NO];
785 } else if (BuffersModifiedMsgID == msgid) {
786 [[windowController window] setDocumentEdited:YES];
788 NSLog(@"WARNING: Unknown message received (msgid=%d)", msgid);
793 #define MM_DEBUG_DRAWING 0
795 - (void)performBatchDrawWithData:(NSData *)data
797 // TODO! Move to window controller.
798 MMTextStorage *textStorage = [windowController textStorage];
799 MMTextView *textView = [windowController textView];
800 if (!(textStorage && textView))
803 const void *bytes = [data bytes];
804 const void *end = bytes + [data length];
807 NSLog(@"====> BEGIN %s", _cmd);
809 [textStorage beginEditing];
811 // TODO: Sanity check input
813 while (bytes < end) {
814 int type = *((int*)bytes); bytes += sizeof(int);
816 if (ClearAllDrawType == type) {
818 NSLog(@" Clear all");
820 [textStorage clearAll];
821 } else if (ClearBlockDrawType == type) {
822 unsigned color = *((unsigned*)bytes); bytes += sizeof(unsigned);
823 int row1 = *((int*)bytes); bytes += sizeof(int);
824 int col1 = *((int*)bytes); bytes += sizeof(int);
825 int row2 = *((int*)bytes); bytes += sizeof(int);
826 int col2 = *((int*)bytes); bytes += sizeof(int);
829 NSLog(@" Clear block (%d,%d) -> (%d,%d)", row1, col1,
832 [textStorage clearBlockFromRow:row1 column:col1
833 toRow:row2 column:col2
834 color:[NSColor colorWithArgbInt:color]];
835 } else if (DeleteLinesDrawType == type) {
836 unsigned color = *((unsigned*)bytes); bytes += sizeof(unsigned);
837 int row = *((int*)bytes); bytes += sizeof(int);
838 int count = *((int*)bytes); bytes += sizeof(int);
839 int bot = *((int*)bytes); bytes += sizeof(int);
840 int left = *((int*)bytes); bytes += sizeof(int);
841 int right = *((int*)bytes); bytes += sizeof(int);
844 NSLog(@" Delete %d line(s) from %d", count, row);
846 [textStorage deleteLinesFromRow:row lineCount:count
847 scrollBottom:bot left:left right:right
848 color:[NSColor colorWithArgbInt:color]];
849 } else if (ReplaceStringDrawType == type) {
850 int bg = *((int*)bytes); bytes += sizeof(int);
851 int fg = *((int*)bytes); bytes += sizeof(int);
852 int sp = *((int*)bytes); bytes += sizeof(int);
853 int row = *((int*)bytes); bytes += sizeof(int);
854 int col = *((int*)bytes); bytes += sizeof(int);
855 int flags = *((int*)bytes); bytes += sizeof(int);
856 int len = *((int*)bytes); bytes += sizeof(int);
857 NSString *string = [[NSString alloc]
858 initWithBytesNoCopy:(void*)bytes
860 encoding:NSUTF8StringEncoding
865 NSLog(@" Draw string at (%d,%d) length=%d flags=%d fg=0x%x "
866 "bg=0x%x sp=0x%x (%@)", row, col, len, flags, fg, bg, sp,
867 len > 0 ? [string substringToIndex:1] : @"");
869 // NOTE: If this is a call to draw the (block) cursor, then cancel
870 // any previous request to draw the insertion point, or it might
871 // get drawn as well.
872 if (flags & DRAW_CURSOR) {
873 [textView setShouldDrawInsertionPoint:NO];
874 //NSColor *color = [NSColor colorWithRgbInt:bg];
875 //[textView drawInsertionPointAtRow:row column:col
876 // shape:MMInsertionPointBlock
879 [textStorage replaceString:string
882 foregroundColor:[NSColor colorWithRgbInt:fg]
883 backgroundColor:[NSColor colorWithArgbInt:bg]
884 specialColor:[NSColor colorWithRgbInt:sp]];
887 } else if (InsertLinesDrawType == type) {
888 unsigned color = *((unsigned*)bytes); bytes += sizeof(unsigned);
889 int row = *((int*)bytes); bytes += sizeof(int);
890 int count = *((int*)bytes); bytes += sizeof(int);
891 int bot = *((int*)bytes); bytes += sizeof(int);
892 int left = *((int*)bytes); bytes += sizeof(int);
893 int right = *((int*)bytes); bytes += sizeof(int);
896 NSLog(@" Insert %d line(s) at row %d", count, row);
898 [textStorage insertLinesAtRow:row lineCount:count
899 scrollBottom:bot left:left right:right
900 color:[NSColor colorWithArgbInt:color]];
901 } else if (DrawCursorDrawType == type) {
902 unsigned color = *((unsigned*)bytes); bytes += sizeof(unsigned);
903 int row = *((int*)bytes); bytes += sizeof(int);
904 int col = *((int*)bytes); bytes += sizeof(int);
905 int shape = *((int*)bytes); bytes += sizeof(int);
906 int percent = *((int*)bytes); bytes += sizeof(int);
909 NSLog(@" Draw cursor at (%d,%d)", row, col);
911 [textView drawInsertionPointAtRow:row column:col shape:shape
913 color:[NSColor colorWithRgbInt:color]];
915 NSLog(@"WARNING: Unknown draw type (type=%d)", type);
919 [textStorage endEditing];
921 // NOTE: During resizing, Cocoa only sends draw messages before Vim's rows
922 // and columns are changed (due to ipc delays). Force a redraw here.
923 [[windowController vimView] displayIfNeeded];
926 NSLog(@"<==== END %s", _cmd);
930 - (void)savePanelDidEnd:(NSSavePanel *)panel code:(int)code
931 context:(void *)context
933 NSString *string = (code == NSOKButton) ? [panel filename] : nil;
935 [backendProxy setDialogReturn:string];
937 @catch (NSException *e) {
938 NSLog(@"Exception caught in %s %@", _cmd, e);
942 - (void)alertDidEnd:(MMAlert *)alert code:(int)code context:(void *)context
946 code = code - NSAlertFirstButtonReturn + 1;
948 if ([alert isKindOfClass:[MMAlert class]] && [alert textField]) {
949 ret = [NSArray arrayWithObjects:[NSNumber numberWithInt:code],
950 [[alert textField] stringValue], nil];
952 ret = [NSArray arrayWithObject:[NSNumber numberWithInt:code]];
956 [backendProxy setDialogReturn:ret];
958 @catch (NSException *e) {
959 NSLog(@"Exception caught in %s %@", _cmd, e);
963 - (NSMenuItem *)recurseMenuItemForTag:(int)tag rootMenu:(NSMenu *)root
966 NSMenuItem *item = [root itemWithTag:tag];
968 lastMenuSearched = root;
972 NSArray *items = [root itemArray];
973 unsigned i, count = [items count];
974 for (i = 0; i < count; ++i) {
975 item = [items objectAtIndex:i];
976 if ([item hasSubmenu]) {
977 item = [self recurseMenuItemForTag:tag
978 rootMenu:[item submenu]];
980 lastMenuSearched = [item submenu];
990 - (NSMenuItem *)menuItemForTag:(int)tag
992 // First search the same menu that was search last time this method was
993 // called. Since this method is often called for each menu item in a
994 // menu this can significantly improve search times.
995 if (lastMenuSearched) {
996 NSMenuItem *item = [self recurseMenuItemForTag:tag
997 rootMenu:lastMenuSearched];
998 if (item) return item;
1001 // Search the main menu.
1002 int i, count = [mainMenuItems count];
1003 for (i = 0; i < count; ++i) {
1004 NSMenuItem *item = [mainMenuItems objectAtIndex:i];
1005 if ([item tag] == tag) return item;
1006 item = [self recurseMenuItemForTag:tag rootMenu:[item submenu]];
1008 lastMenuSearched = [item submenu];
1013 // Search the popup menus.
1014 count = [popupMenuItems count];
1015 for (i = 0; i < count; ++i) {
1016 NSMenuItem *item = [popupMenuItems objectAtIndex:i];
1017 if ([item tag] == tag) return item;
1018 item = [self recurseMenuItemForTag:tag rootMenu:[item submenu]];
1020 lastMenuSearched = [item submenu];
1028 - (NSMenu *)menuForTag:(int)tag
1030 return [[self menuItemForTag:tag] submenu];
1033 - (NSMenu *)topLevelMenuForTitle:(NSString *)title
1035 // Search only the top-level menus.
1037 unsigned i, count = [popupMenuItems count];
1038 for (i = 0; i < count; ++i) {
1039 NSMenuItem *item = [popupMenuItems objectAtIndex:i];
1040 if ([title isEqual:[item title]])
1041 return [item submenu];
1044 count = [mainMenuItems count];
1045 for (i = 0; i < count; ++i) {
1046 NSMenuItem *item = [mainMenuItems objectAtIndex:i];
1047 if ([title isEqual:[item title]])
1048 return [item submenu];
1054 - (void)addMenuWithTag:(int)tag parent:(int)parentTag title:(NSString *)title
1057 NSMenu *parent = [self menuForTag:parentTag];
1058 NSMenuItem *item = [[NSMenuItem alloc] init];
1059 NSMenu *menu = [[NSMenu alloc] initWithTitle:title];
1061 [menu setAutoenablesItems:NO];
1063 [item setTitle:title];
1064 [item setSubmenu:menu];
1067 if ([parent numberOfItems] <= idx) {
1068 [parent addItem:item];
1070 [parent insertItem:item atIndex:idx];
1073 NSMutableArray *items = (MenuPopupType == parentTag)
1074 ? popupMenuItems : mainMenuItems;
1075 if ([items count] <= idx) {
1076 [items addObject:item];
1078 [items insertObject:item atIndex:idx];
1081 shouldUpdateMainMenu = (MenuPopupType != parentTag);
1088 - (void)addMenuItemWithTag:(int)tag parent:(NSMenu *)parent
1089 title:(NSString *)title tip:(NSString *)tip
1090 keyEquivalent:(int)key modifiers:(int)mask
1091 action:(NSString *)action atIndex:(int)idx
1094 NSMenuItem *item = nil;
1095 if (!title || ([title hasPrefix:@"-"] && [title hasSuffix:@"-"])) {
1096 item = [NSMenuItem separatorItem];
1098 item = [[[NSMenuItem alloc] init] autorelease];
1099 [item setTitle:title];
1100 // TODO: Check that 'action' is a valid action (nothing will happen
1101 // if it isn't, but it would be nice with a warning).
1102 if (action) [item setAction:NSSelectorFromString(action)];
1103 else [item setAction:@selector(vimMenuItemAction:)];
1104 if (tip) [item setToolTip:tip];
1107 NSString *keyString =
1108 [NSString stringWithFormat:@"%C", key];
1109 [item setKeyEquivalent:keyString];
1110 [item setKeyEquivalentModifierMask:mask];
1114 // NOTE! The tag is used to idenfity which menu items were
1115 // added by Vim (tag != 0) and which were added by the AppKit
1119 if ([parent numberOfItems] <= idx) {
1120 [parent addItem:item];
1122 [parent insertItem:item atIndex:idx];
1125 NSLog(@"WARNING: Menu item '%@' (tag=%d) has no parent.", title, tag);
1129 - (void)updateMainMenu
1131 NSMenu *mainMenu = [NSApp mainMenu];
1133 // Stop NSApp from updating the Window menu.
1134 [NSApp setWindowsMenu:nil];
1136 // Remove all menus from main menu (except the MacVim menu).
1137 int i, count = [mainMenu numberOfItems];
1138 for (i = count-1; i > 0; --i) {
1139 [mainMenu removeItemAtIndex:i];
1142 // Add menus from 'mainMenuItems' to main menu.
1143 count = [mainMenuItems count];
1144 for (i = 0; i < count; ++i) {
1145 [mainMenu addItem:[mainMenuItems objectAtIndex:i]];
1148 // Set the new Window menu.
1149 // TODO! Need to look for 'Window' in all localized languages.
1150 NSMenu *windowMenu = [[mainMenu itemWithTitle:@"Window"] submenu];
1152 // Remove all AppKit owned menu items (tag == 0); they will be added
1153 // again when setWindowsMenu: is called.
1154 count = [windowMenu numberOfItems];
1155 for (i = count-1; i >= 0; --i) {
1156 NSMenuItem *item = [windowMenu itemAtIndex:i];
1158 [windowMenu removeItem:item];
1162 [NSApp setWindowsMenu:windowMenu];
1165 shouldUpdateMainMenu = NO;
1168 - (NSToolbarItem *)toolbarItemForTag:(int)tag index:(int *)index
1170 if (!toolbar) return nil;
1172 NSArray *items = [toolbar items];
1173 int i, count = [items count];
1174 for (i = 0; i < count; ++i) {
1175 NSToolbarItem *item = [items objectAtIndex:i];
1176 if ([item tag] == tag) {
1177 if (index) *index = i;
1185 - (void)addToolbarItemToDictionaryWithTag:(int)tag label:(NSString *)title
1186 toolTip:(NSString *)tip icon:(NSString *)icon
1188 // If the item corresponds to a separator then do nothing, since it is
1189 // already defined by Cocoa.
1190 if (!title || [title isEqual:NSToolbarSeparatorItemIdentifier]
1191 || [title isEqual:NSToolbarSpaceItemIdentifier]
1192 || [title isEqual:NSToolbarFlexibleSpaceItemIdentifier])
1195 NSToolbarItem *item = [[NSToolbarItem alloc] initWithItemIdentifier:title];
1197 [item setLabel:title];
1198 [item setToolTip:tip];
1199 [item setAction:@selector(vimMenuItemAction:)];
1200 [item setAutovalidates:NO];
1202 NSImage *img = [NSImage imageNamed:icon];
1204 NSLog(@"WARNING: Could not find image with name '%@' to use as toolbar"
1205 " image for identifier '%@';"
1206 " using default toolbar icon '%@' instead.",
1207 icon, title, MMDefaultToolbarImageName);
1209 img = [NSImage imageNamed:MMDefaultToolbarImageName];
1212 [item setImage:img];
1214 [toolbarItemDict setObject:item forKey:title];
1219 - (void)addToolbarItemWithTag:(int)tag label:(NSString *)label tip:(NSString
1220 *)tip icon:(NSString *)icon atIndex:(int)idx
1222 if (!toolbar) return;
1224 // Check for separator items.
1226 label = NSToolbarSeparatorItemIdentifier;
1227 } else if ([label length] >= 2 && [label hasPrefix:@"-"]
1228 && [label hasSuffix:@"-"]) {
1229 // The label begins and ends with '-'; decided which kind of separator
1230 // item it is by looking at the prefix.
1231 if ([label hasPrefix:@"-space"]) {
1232 label = NSToolbarSpaceItemIdentifier;
1233 } else if ([label hasPrefix:@"-flexspace"]) {
1234 label = NSToolbarFlexibleSpaceItemIdentifier;
1236 label = NSToolbarSeparatorItemIdentifier;
1240 [self addToolbarItemToDictionaryWithTag:tag label:label toolTip:tip
1243 int maxIdx = [[toolbar items] count];
1244 if (maxIdx < idx) idx = maxIdx;
1246 [toolbar insertItemWithItemIdentifier:label atIndex:idx];
1249 - (void)connectionDidDie:(NSNotification *)notification
1251 //NSLog(@"%@ %s%@", [self className], _cmd, notification);
1255 // NOTE! This causes the call to removeVimController: to be delayed.
1257 performSelectorOnMainThread:@selector(removeVimController:)
1258 withObject:self waitUntilDone:NO];
1261 - (NSString *)description
1263 return [NSString stringWithFormat:@"%@ : isInitialized=%d inProcessCommandQueue=%d mainMenuItems=%@ popupMenuItems=%@ toolbar=%@", [self className], isInitialized, inProcessCommandQueue, mainMenuItems, popupMenuItems, toolbar];
1266 #if MM_RESEND_LAST_FAILURE
1267 - (void)resendTimerFired:(NSTimer *)timer
1269 int msgid = resendMsgid;
1272 [resendTimer release];
1279 data = [resendData copy];
1281 //NSLog(@"Resending message: %s", MessageStrings[msgid]);
1282 [self sendMessage:msgid data:data];
1286 @end // MMVimController (Private)
1290 @implementation NSColor (MMProtocol)
1292 + (NSColor *)colorWithRgbInt:(unsigned)rgb
1294 float r = ((rgb>>16) & 0xff)/255.0f;
1295 float g = ((rgb>>8) & 0xff)/255.0f;
1296 float b = (rgb & 0xff)/255.0f;
1298 return [NSColor colorWithCalibratedRed:r green:g blue:b alpha:1.0f];
1301 + (NSColor *)colorWithArgbInt:(unsigned)argb
1303 float a = ((argb>>24) & 0xff)/255.0f;
1304 float r = ((argb>>16) & 0xff)/255.0f;
1305 float g = ((argb>>8) & 0xff)/255.0f;
1306 float b = (argb & 0xff)/255.0f;
1308 return [NSColor colorWithCalibratedRed:r green:g blue:b alpha:a];
1311 @end // NSColor (MMProtocol)
1315 @implementation MMAlert
1318 [textField release];
1322 - (void)setTextFieldString:(NSString *)textFieldString
1324 [textField release];
1325 textField = [[NSTextField alloc] init];
1326 [textField setStringValue:textFieldString];
1329 - (NSTextField *)textField
1334 - (void)setInformativeText:(NSString *)text
1337 // HACK! Add some space for the text field.
1338 [super setInformativeText:[text stringByAppendingString:@"\n\n\n"]];
1340 [super setInformativeText:text];
1344 - (void)beginSheetModalForWindow:(NSWindow *)window
1345 modalDelegate:(id)delegate
1346 didEndSelector:(SEL)didEndSelector
1347 contextInfo:(void *)contextInfo
1349 [super beginSheetModalForWindow:window
1350 modalDelegate:delegate
1351 didEndSelector:didEndSelector
1352 contextInfo:contextInfo];
1354 // HACK! Place the input text field at the bottom of the informative text
1355 // (which has been made a bit larger by adding newline characters).
1356 NSView *contentView = [[self window] contentView];
1357 NSRect rect = [contentView frame];
1358 rect.origin.y = rect.size.height;
1360 NSArray *subviews = [contentView subviews];
1361 unsigned i, count = [subviews count];
1362 for (i = 0; i < count; ++i) {
1363 NSView *view = [subviews objectAtIndex:i];
1364 if ([view isKindOfClass:[NSTextField class]]
1365 && [view frame].origin.y < rect.origin.y) {
1366 // NOTE: The informative text field is the lowest NSTextField in
1367 // the alert dialog.
1368 rect = [view frame];
1372 rect.size.height = MMAlertTextFieldHeight;
1373 [textField setFrame:rect];
1374 [contentView addSubview:textField];
1375 [textField becomeFirstResponder];