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 *)menuItemForTag:(int)tag;
53 - (NSMenu *)menuForTag:(int)tag;
54 - (NSMenu *)topLevelMenuForTitle:(NSString *)title;
55 - (void)addMenuWithTag:(int)tag parent:(int)parentTag title:(NSString *)title
57 - (void)addMenuItemWithTag:(int)tag parent:(NSMenu *)parent
58 title:(NSString *)title tip:(NSString *)tip
59 keyEquivalent:(int)key modifiers:(int)mask
60 action:(NSString *)action atIndex:(int)idx;
61 - (void)updateMainMenu;
62 - (NSToolbarItem *)toolbarItemForTag:(int)tag index:(int *)index;
63 - (void)addToolbarItemToDictionaryWithTag:(int)tag label:(NSString *)title
64 toolTip:(NSString *)tip icon:(NSString *)icon;
65 - (void)addToolbarItemWithTag:(int)tag label:(NSString *)label
66 tip:(NSString *)tip icon:(NSString *)icon
68 - (void)connectionDidDie:(NSNotification *)notification;
69 #if MM_RESEND_LAST_FAILURE
70 - (void)resendTimerFired:(NSTimer *)timer;
76 // TODO: Move to separate file
77 @interface NSColor (MMProtocol)
78 + (NSColor *)colorWithRgbInt:(unsigned)rgb;
79 + (NSColor *)colorWithArgbInt:(unsigned)argb;
84 static NSMenuItem *findMenuItemWithTagInMenu(NSMenu *root, int tag)
87 NSMenuItem *item = [root itemWithTag:tag];
88 if (item) return item;
90 NSArray *items = [root itemArray];
91 unsigned i, count = [items count];
92 for (i = 0; i < count; ++i) {
93 item = [items objectAtIndex:i];
94 if ([item hasSubmenu]) {
95 item = findMenuItemWithTagInMenu([item submenu], tag);
96 if (item) return item;
106 @implementation MMVimController
108 - (id)initWithBackend:(id)backend pid:(int)processIdentifier
110 if ((self = [super init])) {
112 [[MMWindowController alloc] initWithVimController:self];
113 backendProxy = [backend retain];
114 sendQueue = [NSMutableArray new];
115 mainMenuItems = [[NSMutableArray alloc] init];
116 popupMenuItems = [[NSMutableArray alloc] init];
117 toolbarItemDict = [[NSMutableDictionary alloc] init];
118 pid = processIdentifier;
120 NSConnection *connection = [backendProxy connectionForProxy];
122 // TODO: Check that this will not set the timeout for the root proxy
123 // (in MMAppController).
124 [connection setRequestTimeout:MMBackendProxyRequestTimeout];
126 [[NSNotificationCenter defaultCenter] addObserver:self
127 selector:@selector(connectionDidDie:)
128 name:NSConnectionDidDieNotification object:connection];
131 NSWindow *win = [windowController window];
133 [[NSNotificationCenter defaultCenter]
135 selector:@selector(windowDidBecomeMain:)
136 name:NSWindowDidBecomeMainNotification
147 //NSLog(@"%@ %s", [self className], _cmd);
150 #if MM_RESEND_LAST_FAILURE
151 [resendData release]; resendData = nil;
154 [serverName release]; serverName = nil;
155 [backendProxy release]; backendProxy = nil;
156 [sendQueue release]; sendQueue = nil;
158 [toolbarItemDict release]; toolbarItemDict = nil;
159 [toolbar release]; toolbar = nil;
160 [popupMenuItems release]; popupMenuItems = nil;
161 [mainMenuItems release]; mainMenuItems = nil;
162 [windowController release]; windowController = nil;
167 - (MMWindowController *)windowController
169 return windowController;
172 - (void)setServerName:(NSString *)name
174 if (name != serverName) {
175 [serverName release];
176 serverName = [name copy];
180 - (NSString *)serverName
190 - (void)dropFiles:(NSArray *)filenames
192 int i, numberOfFiles = [filenames count];
193 NSMutableData *data = [NSMutableData data];
195 [data appendBytes:&numberOfFiles length:sizeof(int)];
197 for (i = 0; i < numberOfFiles; ++i) {
198 NSString *file = [filenames objectAtIndex:i];
199 int len = [file lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
202 ++len; // include NUL as well
203 [data appendBytes:&len length:sizeof(int)];
204 [data appendBytes:[file UTF8String] length:len];
208 [self sendMessage:DropFilesMsgID data:data];
211 - (void)dropString:(NSString *)string
213 int len = [string lengthOfBytesUsingEncoding:NSUTF8StringEncoding] + 1;
215 NSMutableData *data = [NSMutableData data];
217 [data appendBytes:&len length:sizeof(int)];
218 [data appendBytes:[string UTF8String] length:len];
220 [self sendMessage:DropStringMsgID data:data];
224 - (void)sendMessage:(int)msgid data:(NSData *)data
226 //NSLog(@"sendMessage:%s (isInitialized=%d inProcessCommandQueue=%d)",
227 // MessageStrings[msgid], isInitialized, inProcessCommandQueue);
229 if (!isInitialized) return;
231 if (inProcessCommandQueue) {
232 //NSLog(@"In process command queue; delaying message send.");
233 [sendQueue addObject:[NSNumber numberWithInt:msgid]];
235 [sendQueue addObject:data];
237 [sendQueue addObject:[NSNull null]];
241 #if MM_RESEND_LAST_FAILURE
243 //NSLog(@"cancelling scheduled resend of %s",
244 // MessageStrings[resendMsgid]);
246 [resendTimer invalidate];
247 [resendTimer release];
252 [resendData release];
258 [backendProxy processInput:msgid data:data];
260 @catch (NSException *e) {
261 //NSLog(@"%@ %s Exception caught during DO call: %@",
262 // [self className], _cmd, e);
263 #if MM_RESEND_LAST_FAILURE
264 //NSLog(@"%s failed, scheduling message %s for resend", _cmd,
265 // MessageStrings[msgid]);
268 resendData = [data retain];
269 resendTimer = [NSTimer
270 scheduledTimerWithTimeInterval:MMResendInterval
272 selector:@selector(resendTimerFired:)
275 [resendTimer retain];
280 - (BOOL)sendMessageNow:(int)msgid data:(NSData *)data
281 timeout:(NSTimeInterval)timeout
283 if (!isInitialized || inProcessCommandQueue)
286 if (timeout < 0) timeout = 0;
289 NSConnection *conn = [backendProxy connectionForProxy];
290 NSTimeInterval oldTimeout = [conn requestTimeout];
292 [conn setRequestTimeout:timeout];
295 [backendProxy processInput:msgid data:data];
297 @catch (NSException *e) {
301 [conn setRequestTimeout:oldTimeout];
314 //NSLog(@"%@ %s", [self className], _cmd);
315 if (!isInitialized) return;
318 [toolbar setDelegate:nil];
319 [[NSNotificationCenter defaultCenter] removeObserver:self];
320 [windowController cleanup];
323 - (oneway void)showSavePanelForDirectory:(in bycopy NSString *)dir
324 title:(in bycopy NSString *)title
327 if (!isInitialized) return;
330 [[NSSavePanel savePanel] beginSheetForDirectory:dir file:nil
331 modalForWindow:[windowController window]
333 didEndSelector:@selector(savePanelDidEnd:code:context:)
336 NSOpenPanel *panel = [NSOpenPanel openPanel];
337 [panel setAllowsMultipleSelection:NO];
338 [panel beginSheetForDirectory:dir file:nil types:nil
339 modalForWindow:[windowController window]
341 didEndSelector:@selector(savePanelDidEnd:code:context:)
346 - (oneway void)presentDialogWithStyle:(int)style
347 message:(in bycopy NSString *)message
348 informativeText:(in bycopy NSString *)text
349 buttonTitles:(in bycopy NSArray *)buttonTitles
350 textFieldString:(in bycopy NSString *)textFieldString
352 if (!(windowController && buttonTitles && [buttonTitles count])) return;
354 MMAlert *alert = [[MMAlert alloc] init];
356 // NOTE! This has to be done before setting the informative text.
358 [alert setTextFieldString:textFieldString];
360 [alert setAlertStyle:style];
363 [alert setMessageText:message];
365 // If no message text is specified 'Alert' is used, which we don't
366 // want, so set an empty string as message text.
367 [alert setMessageText:@""];
371 [alert setInformativeText:text];
372 } else if (textFieldString) {
373 // Make sure there is always room for the input text field.
374 [alert setInformativeText:@""];
377 unsigned i, count = [buttonTitles count];
378 for (i = 0; i < count; ++i) {
379 NSString *title = [buttonTitles objectAtIndex:i];
380 // NOTE: The title of the button may contain the character '&' to
381 // indicate that the following letter should be the key equivalent
382 // associated with the button. Extract this letter and lowercase it.
383 NSString *keyEquivalent = nil;
384 NSRange hotkeyRange = [title rangeOfString:@"&"];
385 if (NSNotFound != hotkeyRange.location) {
386 if ([title length] > NSMaxRange(hotkeyRange)) {
387 NSRange keyEquivRange = NSMakeRange(hotkeyRange.location+1, 1);
388 keyEquivalent = [[title substringWithRange:keyEquivRange]
392 NSMutableString *string = [NSMutableString stringWithString:title];
393 [string deleteCharactersInRange:hotkeyRange];
397 [alert addButtonWithTitle:title];
399 // Set key equivalent for the button, but only if NSAlert hasn't
400 // already done so. (Check the documentation for
401 // - [NSAlert addButtonWithTitle:] to see what key equivalents are
402 // automatically assigned.)
403 NSButton *btn = [[alert buttons] lastObject];
404 if ([[btn keyEquivalent] length] == 0 && keyEquivalent) {
405 [btn setKeyEquivalent:keyEquivalent];
409 [alert beginSheetModalForWindow:[windowController window]
411 didEndSelector:@selector(alertDidEnd:code:context:)
417 - (oneway void)processCommandQueue:(in bycopy NSArray *)queue
419 if (!isInitialized) return;
421 unsigned i, count = [queue count];
423 NSLog(@"WARNING: Uneven number of components (%d) in flush queue "
424 "message; ignoring this message.", count);
428 inProcessCommandQueue = YES;
430 //NSLog(@"======== %s BEGIN ========", _cmd);
431 for (i = 0; i < count; i += 2) {
432 NSData *value = [queue objectAtIndex:i];
433 NSData *data = [queue objectAtIndex:i+1];
435 int msgid = *((int*)[value bytes]);
437 if (msgid != EnableMenuItemMsgID && msgid != AddMenuItemMsgID
438 && msgid != AddMenuMsgID) {
439 NSLog(@"%s%s", _cmd, MessageStrings[msgid]);
443 [self handleMessage:msgid data:data];
445 //NSLog(@"======== %s END ========", _cmd);
447 if (shouldUpdateMainMenu) {
448 [self updateMainMenu];
451 [windowController processCommandQueueDidFinish];
453 inProcessCommandQueue = NO;
455 if ([sendQueue count] > 0) {
457 [backendProxy processInputAndData:sendQueue];
459 @catch (NSException *e) {
460 // Connection timed out, just ignore this.
461 //NSLog(@"WARNING! Connection timed out in %s", _cmd);
464 [sendQueue removeAllObjects];
468 - (void)windowDidBecomeMain:(NSNotification *)notification
471 [self updateMainMenu];
474 - (NSToolbarItem *)toolbar:(NSToolbar *)theToolbar
475 itemForItemIdentifier:(NSString *)itemId
476 willBeInsertedIntoToolbar:(BOOL)flag
478 NSToolbarItem *item = [toolbarItemDict objectForKey:itemId];
480 NSLog(@"WARNING: No toolbar item with id '%@'", itemId);
486 - (NSArray *)toolbarAllowedItemIdentifiers:(NSToolbar *)theToolbar
491 - (NSArray *)toolbarDefaultItemIdentifiers:(NSToolbar *)theToolbar
496 @end // MMVimController
500 @implementation MMVimController (Private)
502 - (void)handleMessage:(int)msgid data:(NSData *)data
504 //NSLog(@"%@ %s", [self className], _cmd);
506 if (OpenVimWindowMsgID == msgid) {
507 [windowController openWindow];
508 } else if (BatchDrawMsgID == msgid) {
509 [self performBatchDrawWithData:data];
510 } else if (SelectTabMsgID == msgid) {
511 #if 0 // NOTE: Tab selection is done inside updateTabsWithData:.
512 const void *bytes = [data bytes];
513 int idx = *((int*)bytes);
514 //NSLog(@"Selecting tab with index %d", idx);
515 [windowController selectTabWithIndex:idx];
517 } else if (UpdateTabBarMsgID == msgid) {
518 [windowController updateTabsWithData:data];
519 } else if (ShowTabBarMsgID == msgid) {
520 [windowController showTabBar:YES];
521 } else if (HideTabBarMsgID == msgid) {
522 [windowController showTabBar:NO];
523 } else if (SetTextDimensionsMsgID == msgid) {
524 const void *bytes = [data bytes];
525 int rows = *((int*)bytes); bytes += sizeof(int);
526 int cols = *((int*)bytes); bytes += sizeof(int);
528 [windowController setTextDimensionsWithRows:rows columns:cols];
529 } else if (SetWindowTitleMsgID == msgid) {
530 const void *bytes = [data bytes];
531 int len = *((int*)bytes); bytes += sizeof(int);
533 NSString *string = [[NSString alloc] initWithBytes:(void*)bytes
534 length:len encoding:NSUTF8StringEncoding];
536 [[windowController window] setTitle:string];
539 } else if (AddMenuMsgID == msgid) {
540 NSString *title = nil;
541 const void *bytes = [data bytes];
542 int tag = *((int*)bytes); bytes += sizeof(int);
543 int parentTag = *((int*)bytes); bytes += sizeof(int);
544 int len = *((int*)bytes); bytes += sizeof(int);
546 title = [[NSString alloc] initWithBytes:(void*)bytes length:len
547 encoding:NSUTF8StringEncoding];
550 int idx = *((int*)bytes); bytes += sizeof(int);
552 if (MenuToolbarType == parentTag) {
554 // NOTE! Each toolbar must have a unique identifier, else each
555 // window will have the same toolbar.
556 NSString *ident = [NSString stringWithFormat:@"%d.%d",
558 toolbar = [[NSToolbar alloc] initWithIdentifier:ident];
560 [toolbar setShowsBaselineSeparator:NO];
561 [toolbar setDelegate:self];
562 [toolbar setDisplayMode:NSToolbarDisplayModeIconOnly];
563 [toolbar setSizeMode:NSToolbarSizeModeSmall];
565 NSWindow *win = [windowController window];
566 [win setToolbar:toolbar];
568 // HACK! Redirect the pill button so that we can ask Vim to
570 NSButton *pillButton = [win
571 standardWindowButton:NSWindowToolbarButton];
573 [pillButton setAction:@selector(toggleToolbar:)];
574 [pillButton setTarget:windowController];
578 [self addMenuWithTag:tag parent:parentTag title:title atIndex:idx];
582 } else if (AddMenuItemMsgID == msgid) {
583 NSString *title = nil, *tip = nil, *icon = nil, *action = nil;
584 const void *bytes = [data bytes];
585 int tag = *((int*)bytes); bytes += sizeof(int);
586 int parentTag = *((int*)bytes); bytes += sizeof(int);
587 int namelen = *((int*)bytes); bytes += sizeof(int);
589 title = [[NSString alloc] initWithBytes:(void*)bytes length:namelen
590 encoding:NSUTF8StringEncoding];
593 int tiplen = *((int*)bytes); bytes += sizeof(int);
595 tip = [[NSString alloc] initWithBytes:(void*)bytes length:tiplen
596 encoding:NSUTF8StringEncoding];
599 int iconlen = *((int*)bytes); bytes += sizeof(int);
601 icon = [[NSString alloc] initWithBytes:(void*)bytes length:iconlen
602 encoding:NSUTF8StringEncoding];
605 int actionlen = *((int*)bytes); bytes += sizeof(int);
607 action = [[NSString alloc] initWithBytes:(void*)bytes
609 encoding:NSUTF8StringEncoding];
612 int idx = *((int*)bytes); bytes += sizeof(int);
613 if (idx < 0) idx = 0;
614 int key = *((int*)bytes); bytes += sizeof(int);
615 int mask = *((int*)bytes); bytes += sizeof(int);
617 NSString *ident = [NSString stringWithFormat:@"%d.%d",
618 (int)self, parentTag];
619 if (toolbar && [[toolbar identifier] isEqual:ident]) {
620 [self addToolbarItemWithTag:tag label:title tip:tip icon:icon
623 NSMenu *parent = [self menuForTag:parentTag];
624 [self addMenuItemWithTag:tag parent:parent title:title tip:tip
625 keyEquivalent:key modifiers:mask action:action
633 } else if (RemoveMenuItemMsgID == msgid) {
634 const void *bytes = [data bytes];
635 int tag = *((int*)bytes); bytes += sizeof(int);
639 if ((item = [self toolbarItemForTag:tag index:&idx])) {
640 [toolbar removeItemAtIndex:idx];
641 } else if ((item = [self menuItemForTag:tag])) {
644 if ([item menu] == [NSApp mainMenu] || ![item menu]) {
645 // NOTE: To be on the safe side we try to remove the item from
646 // both arrays (it is ok to call removeObject: even if an array
647 // does not contain the object to remove).
648 [mainMenuItems removeObject:item];
649 [popupMenuItems removeObject:item];
653 [[item menu] removeItem:item];
657 } else if (EnableMenuItemMsgID == msgid) {
658 const void *bytes = [data bytes];
659 int tag = *((int*)bytes); bytes += sizeof(int);
660 int state = *((int*)bytes); bytes += sizeof(int);
662 id item = [self toolbarItemForTag:tag index:NULL];
664 item = [self menuItemForTag:tag];
666 [item setEnabled:state];
667 } else if (ShowToolbarMsgID == msgid) {
668 const void *bytes = [data bytes];
669 int enable = *((int*)bytes); bytes += sizeof(int);
670 int flags = *((int*)bytes); bytes += sizeof(int);
672 int mode = NSToolbarDisplayModeDefault;
673 if (flags & ToolbarLabelFlag) {
674 mode = flags & ToolbarIconFlag ? NSToolbarDisplayModeIconAndLabel
675 : NSToolbarDisplayModeLabelOnly;
676 } else if (flags & ToolbarIconFlag) {
677 mode = NSToolbarDisplayModeIconOnly;
680 int size = flags & ToolbarSizeRegularFlag ? NSToolbarSizeModeRegular
681 : NSToolbarSizeModeSmall;
683 [windowController showToolbar:enable size:size mode:mode];
684 } else if (CreateScrollbarMsgID == msgid) {
685 const void *bytes = [data bytes];
686 long ident = *((long*)bytes); bytes += sizeof(long);
687 int type = *((int*)bytes); bytes += sizeof(int);
689 [windowController createScrollbarWithIdentifier:ident type:type];
690 } else if (DestroyScrollbarMsgID == msgid) {
691 const void *bytes = [data bytes];
692 long ident = *((long*)bytes); bytes += sizeof(long);
694 [windowController destroyScrollbarWithIdentifier:ident];
695 } else if (ShowScrollbarMsgID == msgid) {
696 const void *bytes = [data bytes];
697 long ident = *((long*)bytes); bytes += sizeof(long);
698 int visible = *((int*)bytes); bytes += sizeof(int);
700 [windowController showScrollbarWithIdentifier:ident state:visible];
701 } else if (SetScrollbarPositionMsgID == msgid) {
702 const void *bytes = [data bytes];
703 long ident = *((long*)bytes); bytes += sizeof(long);
704 int pos = *((int*)bytes); bytes += sizeof(int);
705 int len = *((int*)bytes); bytes += sizeof(int);
707 [windowController setScrollbarPosition:pos length:len
709 } else if (SetScrollbarThumbMsgID == msgid) {
710 const void *bytes = [data bytes];
711 long ident = *((long*)bytes); bytes += sizeof(long);
712 float val = *((float*)bytes); bytes += sizeof(float);
713 float prop = *((float*)bytes); bytes += sizeof(float);
715 [windowController setScrollbarThumbValue:val proportion:prop
717 } else if (SetFontMsgID == msgid) {
718 const void *bytes = [data bytes];
719 float size = *((float*)bytes); bytes += sizeof(float);
720 int len = *((int*)bytes); bytes += sizeof(int);
721 NSString *name = [[NSString alloc]
722 initWithBytes:(void*)bytes length:len
723 encoding:NSUTF8StringEncoding];
724 NSFont *font = [NSFont fontWithName:name size:size];
727 [windowController setFont:font];
730 } else if (SetDefaultColorsMsgID == msgid) {
731 const void *bytes = [data bytes];
732 unsigned bg = *((unsigned*)bytes); bytes += sizeof(unsigned);
733 unsigned fg = *((unsigned*)bytes); bytes += sizeof(unsigned);
734 NSColor *back = [NSColor colorWithArgbInt:bg];
735 NSColor *fore = [NSColor colorWithRgbInt:fg];
737 [windowController setDefaultColorsBackground:back foreground:fore];
738 } else if (ExecuteActionMsgID == msgid) {
739 const void *bytes = [data bytes];
740 int len = *((int*)bytes); bytes += sizeof(int);
741 NSString *actionName = [[NSString alloc]
742 initWithBytesNoCopy:(void*)bytes
744 encoding:NSUTF8StringEncoding
747 SEL sel = NSSelectorFromString(actionName);
748 [NSApp sendAction:sel to:nil from:self];
750 [actionName release];
751 } else if (ShowPopupMenuMsgID == msgid) {
752 const void *bytes = [data bytes];
753 int row = *((int*)bytes); bytes += sizeof(int);
754 int col = *((int*)bytes); bytes += sizeof(int);
755 int len = *((int*)bytes); bytes += sizeof(int);
756 NSString *title = [[NSString alloc]
757 initWithBytesNoCopy:(void*)bytes
759 encoding:NSUTF8StringEncoding
762 NSMenu *menu = [self topLevelMenuForTitle:title];
764 [windowController popupMenu:menu atRow:row column:col];
766 NSLog(@"WARNING: Cannot popup menu with title %@; no such menu.",
771 } else if (SetMouseShapeMsgID == msgid) {
772 const void *bytes = [data bytes];
773 int shape = *((int*)bytes); bytes += sizeof(int);
775 [windowController setMouseShape:shape];
776 } else if (AdjustLinespaceMsgID == msgid) {
777 const void *bytes = [data bytes];
778 int linespace = *((int*)bytes); bytes += sizeof(int);
780 [windowController adjustLinespace:linespace];
781 } else if (ActivateMsgID == msgid) {
782 [NSApp activateIgnoringOtherApps:YES];
783 [[windowController window] makeKeyAndOrderFront:self];
784 } else if (SetServerNameMsgID == msgid) {
785 NSString *name = [[NSString alloc] initWithData:data
786 encoding:NSUTF8StringEncoding];
787 [self setServerName:name];
789 } else if (EnterFullscreenMsgID == msgid) {
790 [windowController enterFullscreen];
791 } else if (LeaveFullscreenMsgID == msgid) {
792 [windowController leaveFullscreen];
793 } else if (BuffersNotModifiedMsgID == msgid) {
794 [[windowController window] setDocumentEdited:NO];
795 } else if (BuffersModifiedMsgID == msgid) {
796 [[windowController window] setDocumentEdited:YES];
798 NSLog(@"WARNING: Unknown message received (msgid=%d)", msgid);
803 #define MM_DEBUG_DRAWING 0
805 - (void)performBatchDrawWithData:(NSData *)data
807 // TODO! Move to window controller.
808 MMTextStorage *textStorage = [windowController textStorage];
809 MMTextView *textView = [windowController textView];
810 if (!(textStorage && textView))
813 const void *bytes = [data bytes];
814 const void *end = bytes + [data length];
817 NSLog(@"====> BEGIN %s", _cmd);
819 [textStorage beginEditing];
821 // TODO: Sanity check input
823 while (bytes < end) {
824 int type = *((int*)bytes); bytes += sizeof(int);
826 if (ClearAllDrawType == type) {
828 NSLog(@" Clear all");
830 [textStorage clearAll];
831 } else if (ClearBlockDrawType == type) {
832 unsigned color = *((unsigned*)bytes); bytes += sizeof(unsigned);
833 int row1 = *((int*)bytes); bytes += sizeof(int);
834 int col1 = *((int*)bytes); bytes += sizeof(int);
835 int row2 = *((int*)bytes); bytes += sizeof(int);
836 int col2 = *((int*)bytes); bytes += sizeof(int);
839 NSLog(@" Clear block (%d,%d) -> (%d,%d)", row1, col1,
842 [textStorage clearBlockFromRow:row1 column:col1
843 toRow:row2 column:col2
844 color:[NSColor colorWithArgbInt:color]];
845 } else if (DeleteLinesDrawType == type) {
846 unsigned color = *((unsigned*)bytes); bytes += sizeof(unsigned);
847 int row = *((int*)bytes); bytes += sizeof(int);
848 int count = *((int*)bytes); bytes += sizeof(int);
849 int bot = *((int*)bytes); bytes += sizeof(int);
850 int left = *((int*)bytes); bytes += sizeof(int);
851 int right = *((int*)bytes); bytes += sizeof(int);
854 NSLog(@" Delete %d line(s) from %d", count, row);
856 [textStorage deleteLinesFromRow:row lineCount:count
857 scrollBottom:bot left:left right:right
858 color:[NSColor colorWithArgbInt:color]];
859 } else if (ReplaceStringDrawType == type) {
860 int bg = *((int*)bytes); bytes += sizeof(int);
861 int fg = *((int*)bytes); bytes += sizeof(int);
862 int sp = *((int*)bytes); bytes += sizeof(int);
863 int row = *((int*)bytes); bytes += sizeof(int);
864 int col = *((int*)bytes); bytes += sizeof(int);
865 int flags = *((int*)bytes); bytes += sizeof(int);
866 int len = *((int*)bytes); bytes += sizeof(int);
867 NSString *string = [[NSString alloc]
868 initWithBytesNoCopy:(void*)bytes
870 encoding:NSUTF8StringEncoding
875 NSLog(@" Draw string at (%d,%d) length=%d flags=%d fg=0x%x "
876 "bg=0x%x sp=0x%x (%@)", row, col, len, flags, fg, bg, sp,
877 len > 0 ? [string substringToIndex:1] : @"");
879 // NOTE: If this is a call to draw the (block) cursor, then cancel
880 // any previous request to draw the insertion point, or it might
881 // get drawn as well.
882 if (flags & DRAW_CURSOR) {
883 [textView setShouldDrawInsertionPoint:NO];
884 //NSColor *color = [NSColor colorWithRgbInt:bg];
885 //[textView drawInsertionPointAtRow:row column:col
886 // shape:MMInsertionPointBlock
889 [textStorage replaceString:string
892 foregroundColor:[NSColor colorWithRgbInt:fg]
893 backgroundColor:[NSColor colorWithArgbInt:bg]
894 specialColor:[NSColor colorWithRgbInt:sp]];
897 } else if (InsertLinesDrawType == type) {
898 unsigned color = *((unsigned*)bytes); bytes += sizeof(unsigned);
899 int row = *((int*)bytes); bytes += sizeof(int);
900 int count = *((int*)bytes); bytes += sizeof(int);
901 int bot = *((int*)bytes); bytes += sizeof(int);
902 int left = *((int*)bytes); bytes += sizeof(int);
903 int right = *((int*)bytes); bytes += sizeof(int);
906 NSLog(@" Insert %d line(s) at row %d", count, row);
908 [textStorage insertLinesAtRow:row lineCount:count
909 scrollBottom:bot left:left right:right
910 color:[NSColor colorWithArgbInt:color]];
911 } else if (DrawCursorDrawType == type) {
912 unsigned color = *((unsigned*)bytes); bytes += sizeof(unsigned);
913 int row = *((int*)bytes); bytes += sizeof(int);
914 int col = *((int*)bytes); bytes += sizeof(int);
915 int shape = *((int*)bytes); bytes += sizeof(int);
916 int percent = *((int*)bytes); bytes += sizeof(int);
919 NSLog(@" Draw cursor at (%d,%d)", row, col);
921 [textView drawInsertionPointAtRow:row column:col shape:shape
923 color:[NSColor colorWithRgbInt:color]];
925 NSLog(@"WARNING: Unknown draw type (type=%d)", type);
929 [textStorage endEditing];
931 NSLog(@"<==== END %s", _cmd);
935 - (void)savePanelDidEnd:(NSSavePanel *)panel code:(int)code
936 context:(void *)context
938 NSString *string = (code == NSOKButton) ? [panel filename] : nil;
940 [backendProxy setDialogReturn:string];
942 @catch (NSException *e) {
943 NSLog(@"Exception caught in %s %@", _cmd, e);
947 - (void)alertDidEnd:(MMAlert *)alert code:(int)code context:(void *)context
951 code = code - NSAlertFirstButtonReturn + 1;
953 if ([alert isKindOfClass:[MMAlert class]] && [alert textField]) {
954 ret = [NSArray arrayWithObjects:[NSNumber numberWithInt:code],
955 [[alert textField] stringValue], nil];
957 ret = [NSArray arrayWithObject:[NSNumber numberWithInt:code]];
961 [backendProxy setDialogReturn:ret];
963 @catch (NSException *e) {
964 NSLog(@"Exception caught in %s %@", _cmd, e);
968 - (NSMenuItem *)menuItemForTag:(int)tag
970 // Search the main menu.
971 int i, count = [mainMenuItems count];
972 for (i = 0; i < count; ++i) {
973 NSMenuItem *item = [mainMenuItems objectAtIndex:i];
974 if ([item tag] == tag) return item;
975 item = findMenuItemWithTagInMenu([item submenu], tag);
976 if (item) return item;
979 // Search the popup menus.
980 count = [popupMenuItems count];
981 for (i = 0; i < count; ++i) {
982 NSMenuItem *item = [popupMenuItems objectAtIndex:i];
983 if ([item tag] == tag) return item;
984 item = findMenuItemWithTagInMenu([item submenu], tag);
985 if (item) return item;
991 - (NSMenu *)menuForTag:(int)tag
993 return [[self menuItemForTag:tag] submenu];
996 - (NSMenu *)topLevelMenuForTitle:(NSString *)title
998 // Search only the top-level menus.
1000 unsigned i, count = [popupMenuItems count];
1001 for (i = 0; i < count; ++i) {
1002 NSMenuItem *item = [popupMenuItems objectAtIndex:i];
1003 if ([title isEqual:[item title]])
1004 return [item submenu];
1007 count = [mainMenuItems count];
1008 for (i = 0; i < count; ++i) {
1009 NSMenuItem *item = [mainMenuItems objectAtIndex:i];
1010 if ([title isEqual:[item title]])
1011 return [item submenu];
1017 - (void)addMenuWithTag:(int)tag parent:(int)parentTag title:(NSString *)title
1020 NSMenu *parent = [self menuForTag:parentTag];
1021 NSMenuItem *item = [[NSMenuItem alloc] init];
1022 NSMenu *menu = [[NSMenu alloc] initWithTitle:title];
1024 [menu setAutoenablesItems:NO];
1026 [item setTitle:title];
1027 [item setSubmenu:menu];
1030 if ([parent numberOfItems] <= idx) {
1031 [parent addItem:item];
1033 [parent insertItem:item atIndex:idx];
1036 NSMutableArray *items = (MenuPopupType == parentTag)
1037 ? popupMenuItems : mainMenuItems;
1038 if ([items count] <= idx) {
1039 [items addObject:item];
1041 [items insertObject:item atIndex:idx];
1044 shouldUpdateMainMenu = (MenuPopupType != parentTag);
1051 - (void)addMenuItemWithTag:(int)tag parent:(NSMenu *)parent
1052 title:(NSString *)title tip:(NSString *)tip
1053 keyEquivalent:(int)key modifiers:(int)mask
1054 action:(NSString *)action atIndex:(int)idx
1057 NSMenuItem *item = nil;
1058 if (!title || ([title hasPrefix:@"-"] && [title hasSuffix:@"-"])) {
1059 item = [NSMenuItem separatorItem];
1061 item = [[[NSMenuItem alloc] init] autorelease];
1062 [item setTitle:title];
1063 // TODO: Check that 'action' is a valid action (nothing will happen
1064 // if it isn't, but it would be nice with a warning).
1065 if (action) [item setAction:NSSelectorFromString(action)];
1066 else [item setAction:@selector(vimMenuItemAction:)];
1067 if (tip) [item setToolTip:tip];
1070 NSString *keyString =
1071 [NSString stringWithFormat:@"%C", key];
1072 [item setKeyEquivalent:keyString];
1073 [item setKeyEquivalentModifierMask:mask];
1077 // NOTE! The tag is used to idenfity which menu items were
1078 // added by Vim (tag != 0) and which were added by the AppKit
1082 if ([parent numberOfItems] <= idx) {
1083 [parent addItem:item];
1085 [parent insertItem:item atIndex:idx];
1088 NSLog(@"WARNING: Menu item '%@' (tag=%d) has no parent.", title, tag);
1092 - (void)updateMainMenu
1094 NSMenu *mainMenu = [NSApp mainMenu];
1096 // Stop NSApp from updating the Window menu.
1097 [NSApp setWindowsMenu:nil];
1099 // Remove all menus from main menu (except the MacVim menu).
1100 int i, count = [mainMenu numberOfItems];
1101 for (i = count-1; i > 0; --i) {
1102 [mainMenu removeItemAtIndex:i];
1105 // Add menus from 'mainMenuItems' to main menu.
1106 count = [mainMenuItems count];
1107 for (i = 0; i < count; ++i) {
1108 [mainMenu addItem:[mainMenuItems objectAtIndex:i]];
1111 // Set the new Window menu.
1112 // TODO! Need to look for 'Window' in all localized languages.
1113 NSMenu *windowMenu = [[mainMenu itemWithTitle:@"Window"] submenu];
1115 // Remove all AppKit owned menu items (tag == 0); they will be added
1116 // again when setWindowsMenu: is called.
1117 count = [windowMenu numberOfItems];
1118 for (i = count-1; i >= 0; --i) {
1119 NSMenuItem *item = [windowMenu itemAtIndex:i];
1121 [windowMenu removeItem:item];
1125 [NSApp setWindowsMenu:windowMenu];
1128 shouldUpdateMainMenu = NO;
1131 - (NSToolbarItem *)toolbarItemForTag:(int)tag index:(int *)index
1133 if (!toolbar) return nil;
1135 NSArray *items = [toolbar items];
1136 int i, count = [items count];
1137 for (i = 0; i < count; ++i) {
1138 NSToolbarItem *item = [items objectAtIndex:i];
1139 if ([item tag] == tag) {
1140 if (index) *index = i;
1148 - (void)addToolbarItemToDictionaryWithTag:(int)tag label:(NSString *)title
1149 toolTip:(NSString *)tip icon:(NSString *)icon
1151 // If the item corresponds to a separator then do nothing, since it is
1152 // already defined by Cocoa.
1153 if (!title || [title isEqual:NSToolbarSeparatorItemIdentifier]
1154 || [title isEqual:NSToolbarSpaceItemIdentifier]
1155 || [title isEqual:NSToolbarFlexibleSpaceItemIdentifier])
1158 NSToolbarItem *item = [[NSToolbarItem alloc] initWithItemIdentifier:title];
1160 [item setLabel:title];
1161 [item setToolTip:tip];
1162 [item setAction:@selector(vimMenuItemAction:)];
1163 [item setAutovalidates:NO];
1165 NSImage *img = [NSImage imageNamed:icon];
1167 NSLog(@"WARNING: Could not find image with name '%@' to use as toolbar"
1168 " image for identifier '%@';"
1169 " using default toolbar icon '%@' instead.",
1170 icon, title, MMDefaultToolbarImageName);
1172 img = [NSImage imageNamed:MMDefaultToolbarImageName];
1175 [item setImage:img];
1177 [toolbarItemDict setObject:item forKey:title];
1182 - (void)addToolbarItemWithTag:(int)tag label:(NSString *)label tip:(NSString
1183 *)tip icon:(NSString *)icon atIndex:(int)idx
1185 if (!toolbar) return;
1187 // Check for separator items.
1189 label = NSToolbarSeparatorItemIdentifier;
1190 } else if ([label length] >= 2 && [label hasPrefix:@"-"]
1191 && [label hasSuffix:@"-"]) {
1192 // The label begins and ends with '-'; decided which kind of separator
1193 // item it is by looking at the prefix.
1194 if ([label hasPrefix:@"-space"]) {
1195 label = NSToolbarSpaceItemIdentifier;
1196 } else if ([label hasPrefix:@"-flexspace"]) {
1197 label = NSToolbarFlexibleSpaceItemIdentifier;
1199 label = NSToolbarSeparatorItemIdentifier;
1203 [self addToolbarItemToDictionaryWithTag:tag label:label toolTip:tip
1206 int maxIdx = [[toolbar items] count];
1207 if (maxIdx < idx) idx = maxIdx;
1209 [toolbar insertItemWithItemIdentifier:label atIndex:idx];
1212 - (void)connectionDidDie:(NSNotification *)notification
1214 //NSLog(@"%@ %s%@", [self className], _cmd, notification);
1218 // NOTE! This causes the call to removeVimController: to be delayed.
1220 performSelectorOnMainThread:@selector(removeVimController:)
1221 withObject:self waitUntilDone:NO];
1224 - (NSString *)description
1226 return [NSString stringWithFormat:@"%@ : isInitialized=%d inProcessCommandQueue=%d mainMenuItems=%@ popupMenuItems=%@ toolbar=%@", [self className], isInitialized, inProcessCommandQueue, mainMenuItems, popupMenuItems, toolbar];
1229 #if MM_RESEND_LAST_FAILURE
1230 - (void)resendTimerFired:(NSTimer *)timer
1232 int msgid = resendMsgid;
1235 [resendTimer release];
1242 data = [resendData copy];
1244 //NSLog(@"Resending message: %s", MessageStrings[msgid]);
1245 [self sendMessage:msgid data:data];
1249 @end // MMVimController (Private)
1253 @implementation NSColor (MMProtocol)
1255 + (NSColor *)colorWithRgbInt:(unsigned)rgb
1257 float r = ((rgb>>16) & 0xff)/255.0f;
1258 float g = ((rgb>>8) & 0xff)/255.0f;
1259 float b = (rgb & 0xff)/255.0f;
1261 return [NSColor colorWithCalibratedRed:r green:g blue:b alpha:1.0f];
1264 + (NSColor *)colorWithArgbInt:(unsigned)argb
1266 float a = ((argb>>24) & 0xff)/255.0f;
1267 float r = ((argb>>16) & 0xff)/255.0f;
1268 float g = ((argb>>8) & 0xff)/255.0f;
1269 float b = (argb & 0xff)/255.0f;
1271 return [NSColor colorWithCalibratedRed:r green:g blue:b alpha:a];
1274 @end // NSColor (MMProtocol)
1278 @implementation MMAlert
1281 [textField release];
1285 - (void)setTextFieldString:(NSString *)textFieldString
1287 [textField release];
1288 textField = [[NSTextField alloc] init];
1289 [textField setStringValue:textFieldString];
1292 - (NSTextField *)textField
1297 - (void)setInformativeText:(NSString *)text
1300 // HACK! Add some space for the text field.
1301 [super setInformativeText:[text stringByAppendingString:@"\n\n\n"]];
1303 [super setInformativeText:text];
1307 - (void)beginSheetModalForWindow:(NSWindow *)window
1308 modalDelegate:(id)delegate
1309 didEndSelector:(SEL)didEndSelector
1310 contextInfo:(void *)contextInfo
1312 [super beginSheetModalForWindow:window
1313 modalDelegate:delegate
1314 didEndSelector:didEndSelector
1315 contextInfo:contextInfo];
1317 // HACK! Place the input text field at the bottom of the informative text
1318 // (which has been made a bit larger by adding newline characters).
1319 NSView *contentView = [[self window] contentView];
1320 NSRect rect = [contentView frame];
1321 rect.origin.y = rect.size.height;
1323 NSArray *subviews = [contentView subviews];
1324 unsigned i, count = [subviews count];
1325 for (i = 0; i < count; ++i) {
1326 NSView *view = [subviews objectAtIndex:i];
1327 if ([view isKindOfClass:[NSTextField class]]
1328 && [view frame].origin.y < rect.origin.y) {
1329 // NOTE: The informative text field is the lowest NSTextField in
1330 // the alert dialog.
1331 rect = [view frame];
1335 rect.size.height = MMAlertTextFieldHeight;
1336 [textField setFrame:rect];
1337 [contentView addSubview:textField];
1338 [textField becomeFirstResponder];