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 #define MM_NO_REQUEST_TIMEOUT 1
21 static NSString *MMDefaultToolbarImageName = @"Attention";
22 static int MMAlertTextFieldHeight = 22;
24 #if MM_NO_REQUEST_TIMEOUT
25 // NOTE: By default a message sent to the backend will be dropped if it cannot
26 // be delivered instantly; otherwise there is a possibility that MacVim will
27 // 'beachball' while waiting to deliver DO messages to an unresponsive Vim
28 // process. This means that you cannot rely on any message sent with
29 // sendMessage:: to actually reach Vim.
30 static NSTimeInterval MMBackendProxyRequestTimeout = 0;
34 @interface MMAlert : NSAlert {
35 NSTextField *textField;
37 - (void)setTextFieldString:(NSString *)textFieldString;
38 - (NSTextField *)textField;
42 @interface MMVimController (Private)
43 - (void)handleMessage:(int)msgid data:(NSData *)data;
44 - (void)performBatchDrawWithData:(NSData *)data;
45 - (void)savePanelDidEnd:(NSSavePanel *)panel code:(int)code
46 context:(void *)context;
47 - (void)alertDidEnd:(MMAlert *)alert code:(int)code context:(void *)context;
48 - (NSMenuItem *)menuItemForTag:(int)tag;
49 - (NSMenu *)menuForTag:(int)tag;
50 - (NSMenu *)topLevelMenuForTitle:(NSString *)title;
51 - (void)addMenuWithTag:(int)tag parent:(int)parentTag title:(NSString *)title
53 - (void)addMenuItemWithTag:(int)tag parent:(NSMenu *)parent
54 title:(NSString *)title tip:(NSString *)tip
55 keyEquivalent:(int)key modifiers:(int)mask
56 action:(NSString *)action atIndex:(int)idx;
57 - (void)updateMainMenu;
58 - (NSToolbarItem *)toolbarItemForTag:(int)tag index:(int *)index;
59 - (void)addToolbarItemToDictionaryWithTag:(int)tag label:(NSString *)title
60 toolTip:(NSString *)tip icon:(NSString *)icon;
61 - (void)addToolbarItemWithTag:(int)tag label:(NSString *)label
62 tip:(NSString *)tip icon:(NSString *)icon
64 - (void)connectionDidDie:(NSNotification *)notification;
69 // TODO: Move to separate file
70 @interface NSColor (MMProtocol)
71 + (NSColor *)colorWithRgbInt:(int)rgb;
76 static NSMenuItem *findMenuItemWithTagInMenu(NSMenu *root, int tag)
79 NSMenuItem *item = [root itemWithTag:tag];
80 if (item) return item;
82 NSArray *items = [root itemArray];
83 unsigned i, count = [items count];
84 for (i = 0; i < count; ++i) {
85 item = [items objectAtIndex:i];
86 if ([item hasSubmenu]) {
87 item = findMenuItemWithTagInMenu([item submenu], tag);
88 if (item) return item;
98 @implementation MMVimController
100 - (id)initWithBackend:(id)backend pid:(int)processIdentifier
102 if ((self = [super init])) {
104 [[MMWindowController alloc] initWithVimController:self];
105 backendProxy = [backend retain];
106 sendQueue = [NSMutableArray new];
107 mainMenuItems = [[NSMutableArray alloc] init];
108 popupMenuItems = [[NSMutableArray alloc] init];
109 toolbarItemDict = [[NSMutableDictionary alloc] init];
110 pid = processIdentifier;
112 NSConnection *connection = [backendProxy connectionForProxy];
114 #if MM_NO_REQUEST_TIMEOUT
115 // TODO: Check that this will not set the timeout for the root proxy
116 // (in MMAppController).
117 [connection setRequestTimeout:MMBackendProxyRequestTimeout];
120 [[NSNotificationCenter defaultCenter] addObserver:self
121 selector:@selector(connectionDidDie:)
122 name:NSConnectionDidDieNotification object:connection];
125 NSWindow *win = [windowController window];
127 [[NSNotificationCenter defaultCenter]
129 selector:@selector(windowDidBecomeMain:)
130 name:NSWindowDidBecomeMainNotification
141 //NSLog(@"%@ %s", [self className], _cmd);
144 [backendProxy release]; backendProxy = nil;
145 [sendQueue release]; sendQueue = nil;
147 [toolbarItemDict release]; toolbarItemDict = nil;
148 [toolbar release]; toolbar = nil;
149 [popupMenuItems release]; popupMenuItems = nil;
150 [mainMenuItems release]; mainMenuItems = nil;
151 [windowController release]; windowController = nil;
156 - (MMWindowController *)windowController
158 return windowController;
166 - (void)dropFiles:(NSArray *)filenames
168 int i, numberOfFiles = [filenames count];
169 NSMutableData *data = [NSMutableData data];
171 [data appendBytes:&numberOfFiles length:sizeof(int)];
173 for (i = 0; i < numberOfFiles; ++i) {
174 NSString *file = [filenames objectAtIndex:i];
175 int len = [file lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
178 ++len; // append NUL as well
179 [data appendBytes:&len length:sizeof(int)];
180 [data appendBytes:[file UTF8String] length:len];
184 [self sendMessage:DropFilesMsgID data:data wait:NO];
187 - (void)dropString:(NSString *)string
189 int len = [string lengthOfBytesUsingEncoding:NSUTF8StringEncoding] + 1;
191 NSMutableData *data = [NSMutableData data];
193 [data appendBytes:&len length:sizeof(int)];
194 [data appendBytes:[string UTF8String] length:len];
196 [self sendMessage:DropStringMsgID data:data wait:NO];
200 - (void)sendMessage:(int)msgid data:(NSData *)data wait:(BOOL)wait
202 //NSLog(@"sendMessage:%s (isInitialized=%d inProcessCommandQueue=%d)",
203 // MessageStrings[msgid], isInitialized, inProcessCommandQueue);
205 if (!isInitialized) return;
207 if (inProcessCommandQueue) {
208 //NSLog(@"In process command queue; delaying message send.");
209 [sendQueue addObject:[NSNumber numberWithInt:msgid]];
211 [sendQueue addObject:data];
213 [sendQueue addObject:[NSNull null]];
217 #if MM_NO_REQUEST_TIMEOUT
219 [backendProxy processInput:msgid data:data];
221 @catch (NSException *e) {
222 //NSLog(@"%@ %s Exception caught during DO call: %@",
223 // [self className], _cmd, e);
228 [backendProxy processInput:msgid data:data];
230 @catch (NSException *e) {
231 NSLog(@"%@ %s Exception caught during DO call: %@",
232 [self className], _cmd, e);
235 // Do not wait for the message to be sent, i.e. drop the message if it
236 // can't be delivered immediately.
237 NSConnection *connection = [backendProxy connectionForProxy];
239 NSTimeInterval req = [connection requestTimeout];
240 [connection setRequestTimeout:0];
242 [backendProxy processInput:msgid data:data];
244 @catch (NSException *e) {
245 // Connection timed out, just ignore this.
246 //NSLog(@"WARNING! Connection timed out in %s", _cmd);
249 [connection setRequestTimeout:req];
263 //NSLog(@"%@ %s", [self className], _cmd);
264 if (!isInitialized) return;
267 [toolbar setDelegate:nil];
268 [[NSNotificationCenter defaultCenter] removeObserver:self];
269 [windowController cleanup];
272 - (oneway void)showSavePanelForDirectory:(in bycopy NSString *)dir
273 title:(in bycopy NSString *)title
276 if (!isInitialized) return;
279 [[NSSavePanel savePanel] beginSheetForDirectory:dir file:nil
280 modalForWindow:[windowController window]
282 didEndSelector:@selector(savePanelDidEnd:code:context:)
285 NSOpenPanel *panel = [NSOpenPanel openPanel];
286 [panel setAllowsMultipleSelection:NO];
287 [panel beginSheetForDirectory:dir file:nil types:nil
288 modalForWindow:[windowController window]
290 didEndSelector:@selector(savePanelDidEnd:code:context:)
295 - (oneway void)presentDialogWithStyle:(int)style
296 message:(in bycopy NSString *)message
297 informativeText:(in bycopy NSString *)text
298 buttonTitles:(in bycopy NSArray *)buttonTitles
299 textFieldString:(in bycopy NSString *)textFieldString
301 if (!(windowController && buttonTitles && [buttonTitles count])) return;
303 MMAlert *alert = [[MMAlert alloc] init];
305 // NOTE! This has to be done before setting the informative text.
307 [alert setTextFieldString:textFieldString];
309 [alert setAlertStyle:style];
312 [alert setMessageText:message];
314 // If no message text is specified 'Alert' is used, which we don't
315 // want, so set an empty string as message text.
316 [alert setMessageText:@""];
320 [alert setInformativeText:text];
321 } else if (textFieldString) {
322 // Make sure there is always room for the input text field.
323 [alert setInformativeText:@""];
326 unsigned i, count = [buttonTitles count];
327 for (i = 0; i < count; ++i) {
328 NSString *title = [buttonTitles objectAtIndex:i];
329 // NOTE: The title of the button may contain the character '&' to
330 // indicate that the following letter should be the key equivalent
331 // associated with the button. Extract this letter and lowercase it.
332 NSString *keyEquivalent = nil;
333 NSRange hotkeyRange = [title rangeOfString:@"&"];
334 if (NSNotFound != hotkeyRange.location) {
335 if ([title length] > NSMaxRange(hotkeyRange)) {
336 NSRange keyEquivRange = NSMakeRange(hotkeyRange.location+1, 1);
337 keyEquivalent = [[title substringWithRange:keyEquivRange]
341 NSMutableString *string = [NSMutableString stringWithString:title];
342 [string deleteCharactersInRange:hotkeyRange];
346 [alert addButtonWithTitle:title];
348 // Set key equivalent for the button, but only if NSAlert hasn't
349 // already done so. (Check the documentation for
350 // - [NSAlert addButtonWithTitle:] to see what key equivalents are
351 // automatically assigned.)
352 NSButton *btn = [[alert buttons] lastObject];
353 if ([[btn keyEquivalent] length] == 0 && keyEquivalent) {
354 [btn setKeyEquivalent:keyEquivalent];
358 [alert beginSheetModalForWindow:[windowController window]
360 didEndSelector:@selector(alertDidEnd:code:context:)
366 - (oneway void)processCommandQueue:(in NSArray *)queue
368 if (!isInitialized) return;
370 unsigned i, count = [queue count];
372 NSLog(@"WARNING: Uneven number of components (%d) in flush queue "
373 "message; ignoring this message.", count);
377 inProcessCommandQueue = YES;
379 //NSLog(@"======== %s BEGIN ========", _cmd);
380 for (i = 0; i < count; i += 2) {
381 NSData *value = [queue objectAtIndex:i];
382 NSData *data = [queue objectAtIndex:i+1];
384 int msgid = *((int*)[value bytes]);
386 if (msgid != EnableMenuItemMsgID && msgid != AddMenuItemMsgID
387 && msgid != AddMenuMsgID) {
388 NSLog(@"%s%s", _cmd, MessageStrings[msgid]);
392 [self handleMessage:msgid data:data];
394 //NSLog(@"======== %s END ========", _cmd);
396 if (shouldUpdateMainMenu) {
397 [self updateMainMenu];
400 [windowController processCommandQueueDidFinish];
402 inProcessCommandQueue = NO;
404 if ([sendQueue count] > 0) {
405 #if MM_NO_REQUEST_TIMEOUT
407 [backendProxy processInputAndData:sendQueue];
409 @catch (NSException *e) {
410 // Connection timed out, just ignore this.
411 //NSLog(@"WARNING! Connection timed out in %s", _cmd);
414 // Do not wait for the message to be sent, i.e. drop the message if it
415 // can't be delivered immediately.
416 NSConnection *connection = [backendProxy connectionForProxy];
418 NSTimeInterval req = [connection requestTimeout];
419 [connection setRequestTimeout:0];
421 [backendProxy processInputAndData:sendQueue];
423 @catch (NSException *e) {
424 // Connection timed out, just ignore this.
425 //NSLog(@"WARNING! Connection timed out in %s", _cmd);
428 [connection setRequestTimeout:req];
433 [sendQueue removeAllObjects];
437 - (void)windowDidBecomeMain:(NSNotification *)notification
440 [self updateMainMenu];
443 - (NSToolbarItem *)toolbar:(NSToolbar *)theToolbar
444 itemForItemIdentifier:(NSString *)itemId
445 willBeInsertedIntoToolbar:(BOOL)flag
447 NSToolbarItem *item = [toolbarItemDict objectForKey:itemId];
449 NSLog(@"WARNING: No toolbar item with id '%@'", itemId);
455 - (NSArray *)toolbarAllowedItemIdentifiers:(NSToolbar *)theToolbar
460 - (NSArray *)toolbarDefaultItemIdentifiers:(NSToolbar *)theToolbar
465 @end // MMVimController
469 @implementation MMVimController (Private)
471 - (void)handleMessage:(int)msgid data:(NSData *)data
473 //NSLog(@"%@ %s", [self className], _cmd);
475 if (OpenVimWindowMsgID == msgid) {
476 [windowController openWindow];
477 } else if (BatchDrawMsgID == msgid) {
478 [self performBatchDrawWithData:data];
479 } else if (SelectTabMsgID == msgid) {
480 #if 0 // NOTE: Tab selection is done inside updateTabsWithData:.
481 const void *bytes = [data bytes];
482 int idx = *((int*)bytes);
483 //NSLog(@"Selecting tab with index %d", idx);
484 [windowController selectTabWithIndex:idx];
486 } else if (UpdateTabBarMsgID == msgid) {
487 [windowController updateTabsWithData:data];
488 } else if (ShowTabBarMsgID == msgid) {
489 [windowController showTabBar:YES];
490 } else if (HideTabBarMsgID == msgid) {
491 [windowController showTabBar:NO];
492 } else if (SetTextDimensionsMsgID == msgid) {
493 const void *bytes = [data bytes];
494 int rows = *((int*)bytes); bytes += sizeof(int);
495 int cols = *((int*)bytes); bytes += sizeof(int);
497 [windowController setTextDimensionsWithRows:rows columns:cols];
498 } else if (SetVimWindowTitleMsgID == msgid) {
499 const void *bytes = [data bytes];
500 int len = *((int*)bytes); bytes += sizeof(int);
502 NSString *string = [[NSString alloc] initWithBytes:(void*)bytes
503 length:len encoding:NSUTF8StringEncoding];
505 [[windowController window] setTitle:string];
508 } else if (AddMenuMsgID == msgid) {
509 NSString *title = nil;
510 const void *bytes = [data bytes];
511 int tag = *((int*)bytes); bytes += sizeof(int);
512 int parentTag = *((int*)bytes); bytes += sizeof(int);
513 int len = *((int*)bytes); bytes += sizeof(int);
515 title = [[NSString alloc] initWithBytes:(void*)bytes length:len
516 encoding:NSUTF8StringEncoding];
519 int idx = *((int*)bytes); bytes += sizeof(int);
521 if (MenuToolbarType == parentTag) {
523 // NOTE! Each toolbar must have a unique identifier, else each
524 // window will have the same toolbar.
525 NSString *ident = [NSString stringWithFormat:@"%d.%d",
527 toolbar = [[NSToolbar alloc] initWithIdentifier:ident];
529 [toolbar setShowsBaselineSeparator:NO];
530 [toolbar setDelegate:self];
531 [toolbar setDisplayMode:NSToolbarDisplayModeIconOnly];
532 [toolbar setSizeMode:NSToolbarSizeModeSmall];
534 NSWindow *win = [windowController window];
535 [win setToolbar:toolbar];
537 // HACK! Redirect the pill button so that we can ask Vim to
539 NSButton *pillButton = [win
540 standardWindowButton:NSWindowToolbarButton];
542 [pillButton setAction:@selector(toggleToolbar:)];
543 [pillButton setTarget:windowController];
547 [self addMenuWithTag:tag parent:parentTag title:title atIndex:idx];
551 } else if (AddMenuItemMsgID == msgid) {
552 NSString *title = nil, *tip = nil, *icon = nil, *action = nil;
553 const void *bytes = [data bytes];
554 int tag = *((int*)bytes); bytes += sizeof(int);
555 int parentTag = *((int*)bytes); bytes += sizeof(int);
556 int namelen = *((int*)bytes); bytes += sizeof(int);
558 title = [[NSString alloc] initWithBytes:(void*)bytes length:namelen
559 encoding:NSUTF8StringEncoding];
562 int tiplen = *((int*)bytes); bytes += sizeof(int);
564 tip = [[NSString alloc] initWithBytes:(void*)bytes length:tiplen
565 encoding:NSUTF8StringEncoding];
568 int iconlen = *((int*)bytes); bytes += sizeof(int);
570 icon = [[NSString alloc] initWithBytes:(void*)bytes length:iconlen
571 encoding:NSUTF8StringEncoding];
574 int actionlen = *((int*)bytes); bytes += sizeof(int);
576 action = [[NSString alloc] initWithBytes:(void*)bytes
578 encoding:NSUTF8StringEncoding];
581 int idx = *((int*)bytes); bytes += sizeof(int);
582 if (idx < 0) idx = 0;
583 int key = *((int*)bytes); bytes += sizeof(int);
584 int mask = *((int*)bytes); bytes += sizeof(int);
586 NSString *ident = [NSString stringWithFormat:@"%d.%d",
587 (int)self, parentTag];
588 if (toolbar && [[toolbar identifier] isEqual:ident]) {
589 [self addToolbarItemWithTag:tag label:title tip:tip icon:icon
592 NSMenu *parent = [self menuForTag:parentTag];
593 [self addMenuItemWithTag:tag parent:parent title:title tip:tip
594 keyEquivalent:key modifiers:mask action:action
602 } else if (RemoveMenuItemMsgID == msgid) {
603 const void *bytes = [data bytes];
604 int tag = *((int*)bytes); bytes += sizeof(int);
608 if ((item = [self toolbarItemForTag:tag index:&idx])) {
609 [toolbar removeItemAtIndex:idx];
610 } else if ((item = [self menuItemForTag:tag])) {
613 if ([item menu] == [NSApp mainMenu] || ![item menu]) {
614 // NOTE: To be on the safe side we try to remove the item from
615 // both arrays (it is ok to call removeObject: even if an array
616 // does not contain the object to remove).
617 [mainMenuItems removeObject:item];
618 [popupMenuItems removeObject:item];
622 [[item menu] removeItem:item];
626 } else if (EnableMenuItemMsgID == msgid) {
627 const void *bytes = [data bytes];
628 int tag = *((int*)bytes); bytes += sizeof(int);
629 int state = *((int*)bytes); bytes += sizeof(int);
631 id item = [self toolbarItemForTag:tag index:NULL];
633 item = [self menuItemForTag:tag];
635 [item setEnabled:state];
636 } else if (ShowToolbarMsgID == msgid) {
637 const void *bytes = [data bytes];
638 int enable = *((int*)bytes); bytes += sizeof(int);
639 int flags = *((int*)bytes); bytes += sizeof(int);
641 int mode = NSToolbarDisplayModeDefault;
642 if (flags & ToolbarLabelFlag) {
643 mode = flags & ToolbarIconFlag ? NSToolbarDisplayModeIconAndLabel
644 : NSToolbarDisplayModeLabelOnly;
645 } else if (flags & ToolbarIconFlag) {
646 mode = NSToolbarDisplayModeIconOnly;
649 int size = flags & ToolbarSizeRegularFlag ? NSToolbarSizeModeRegular
650 : NSToolbarSizeModeSmall;
652 [windowController showToolbar:enable size:size mode:mode];
653 } else if (CreateScrollbarMsgID == msgid) {
654 const void *bytes = [data bytes];
655 long ident = *((long*)bytes); bytes += sizeof(long);
656 int type = *((int*)bytes); bytes += sizeof(int);
658 [windowController createScrollbarWithIdentifier:ident type:type];
659 } else if (DestroyScrollbarMsgID == msgid) {
660 const void *bytes = [data bytes];
661 long ident = *((long*)bytes); bytes += sizeof(long);
663 [windowController destroyScrollbarWithIdentifier:ident];
664 } else if (ShowScrollbarMsgID == msgid) {
665 const void *bytes = [data bytes];
666 long ident = *((long*)bytes); bytes += sizeof(long);
667 int visible = *((int*)bytes); bytes += sizeof(int);
669 [windowController showScrollbarWithIdentifier:ident state:visible];
670 } else if (SetScrollbarPositionMsgID == msgid) {
671 const void *bytes = [data bytes];
672 long ident = *((long*)bytes); bytes += sizeof(long);
673 int pos = *((int*)bytes); bytes += sizeof(int);
674 int len = *((int*)bytes); bytes += sizeof(int);
676 [windowController setScrollbarPosition:pos length:len
678 } else if (SetScrollbarThumbMsgID == msgid) {
679 const void *bytes = [data bytes];
680 long ident = *((long*)bytes); bytes += sizeof(long);
681 float val = *((float*)bytes); bytes += sizeof(float);
682 float prop = *((float*)bytes); bytes += sizeof(float);
684 [windowController setScrollbarThumbValue:val proportion:prop
686 } else if (SetFontMsgID == msgid) {
687 const void *bytes = [data bytes];
688 float size = *((float*)bytes); bytes += sizeof(float);
689 int len = *((int*)bytes); bytes += sizeof(int);
690 NSString *name = [[NSString alloc]
691 initWithBytes:(void*)bytes length:len
692 encoding:NSUTF8StringEncoding];
693 NSFont *font = [NSFont fontWithName:name size:size];
696 [windowController setFont:font];
699 } else if (SetDefaultColorsMsgID == msgid) {
700 const void *bytes = [data bytes];
701 int bg = *((int*)bytes); bytes += sizeof(int);
702 int fg = *((int*)bytes); bytes += sizeof(int);
703 NSColor *back = [NSColor colorWithRgbInt:bg];
704 NSColor *fore = [NSColor colorWithRgbInt:fg];
706 [windowController setDefaultColorsBackground:back foreground:fore];
707 } else if (ExecuteActionMsgID == msgid) {
708 const void *bytes = [data bytes];
709 int len = *((int*)bytes); bytes += sizeof(int);
710 NSString *actionName = [[NSString alloc]
711 initWithBytesNoCopy:(void*)bytes
713 encoding:NSUTF8StringEncoding
716 SEL sel = NSSelectorFromString(actionName);
717 [NSApp sendAction:sel to:nil from:self];
719 [actionName release];
720 } else if (ShowPopupMenuMsgID == msgid) {
721 const void *bytes = [data bytes];
722 int row = *((int*)bytes); bytes += sizeof(int);
723 int col = *((int*)bytes); bytes += sizeof(int);
724 int len = *((int*)bytes); bytes += sizeof(int);
725 NSString *title = [[NSString alloc]
726 initWithBytesNoCopy:(void*)bytes
728 encoding:NSUTF8StringEncoding
731 NSMenu *menu = [self topLevelMenuForTitle:title];
733 [windowController popupMenu:menu atRow:row column:col];
735 NSLog(@"WARNING: Cannot popup menu with title %@; no such menu.",
740 } else if (SetMouseShapeMsgID == msgid) {
741 const void *bytes = [data bytes];
742 int shape = *((int*)bytes); bytes += sizeof(int);
744 [windowController setMouseShape:shape];
746 NSLog(@"WARNING: Unknown message received (msgid=%d)", msgid);
750 - (void)performBatchDrawWithData:(NSData *)data
752 // TODO! Move to window controller.
753 MMTextStorage *textStorage = [windowController textStorage];
754 MMTextView *textView = [windowController textView];
755 if (!(textStorage && textView))
758 const void *bytes = [data bytes];
759 const void *end = bytes + [data length];
761 [textStorage beginEditing];
763 // TODO: Sanity check input
765 while (bytes < end) {
766 int type = *((int*)bytes); bytes += sizeof(int);
768 if (ClearAllDrawType == type) {
769 int color = *((int*)bytes); bytes += sizeof(int);
771 [textStorage clearAllWithColor:[NSColor colorWithRgbInt:color]];
772 } else if (ClearBlockDrawType == type) {
773 int color = *((int*)bytes); bytes += sizeof(int);
774 int row1 = *((int*)bytes); bytes += sizeof(int);
775 int col1 = *((int*)bytes); bytes += sizeof(int);
776 int row2 = *((int*)bytes); bytes += sizeof(int);
777 int col2 = *((int*)bytes); bytes += sizeof(int);
779 [textStorage clearBlockFromRow:row1 column:col1
780 toRow:row2 column:col2
781 color:[NSColor colorWithRgbInt:color]];
782 } else if (DeleteLinesDrawType == type) {
783 int color = *((int*)bytes); bytes += sizeof(int);
784 int row = *((int*)bytes); bytes += sizeof(int);
785 int count = *((int*)bytes); bytes += sizeof(int);
786 int bot = *((int*)bytes); bytes += sizeof(int);
787 int left = *((int*)bytes); bytes += sizeof(int);
788 int right = *((int*)bytes); bytes += sizeof(int);
790 [textStorage deleteLinesFromRow:row lineCount:count
791 scrollBottom:bot left:left right:right
792 color:[NSColor colorWithRgbInt:color]];
793 } else if (ReplaceStringDrawType == type) {
794 int bg = *((int*)bytes); bytes += sizeof(int);
795 int fg = *((int*)bytes); bytes += sizeof(int);
796 int sp = *((int*)bytes); bytes += sizeof(int);
797 int row = *((int*)bytes); bytes += sizeof(int);
798 int col = *((int*)bytes); bytes += sizeof(int);
799 int flags = *((int*)bytes); bytes += sizeof(int);
800 int len = *((int*)bytes); bytes += sizeof(int);
801 NSString *string = [[NSString alloc]
802 initWithBytesNoCopy:(void*)bytes
804 encoding:NSUTF8StringEncoding
808 [textStorage replaceString:string
811 foregroundColor:[NSColor colorWithRgbInt:fg]
812 backgroundColor:[NSColor colorWithRgbInt:bg]
813 specialColor:[NSColor colorWithRgbInt:sp]];
816 } else if (InsertLinesDrawType == type) {
817 int color = *((int*)bytes); bytes += sizeof(int);
818 int row = *((int*)bytes); bytes += sizeof(int);
819 int count = *((int*)bytes); bytes += sizeof(int);
820 int bot = *((int*)bytes); bytes += sizeof(int);
821 int left = *((int*)bytes); bytes += sizeof(int);
822 int right = *((int*)bytes); bytes += sizeof(int);
824 [textStorage insertLinesAtRow:row lineCount:count
825 scrollBottom:bot left:left right:right
826 color:[NSColor colorWithRgbInt:color]];
827 } else if (DrawCursorDrawType == type) {
828 int color = *((int*)bytes); bytes += sizeof(int);
829 int row = *((int*)bytes); bytes += sizeof(int);
830 int col = *((int*)bytes); bytes += sizeof(int);
831 int shape = *((int*)bytes); bytes += sizeof(int);
833 [textView drawInsertionPointAtRow:row column:col shape:shape
834 color:[NSColor colorWithRgbInt:color]];
836 NSLog(@"WARNING: Unknown draw type (type=%d)", type);
840 [textStorage endEditing];
843 - (void)savePanelDidEnd:(NSSavePanel *)panel code:(int)code
844 context:(void *)context
846 NSString *string = (code == NSOKButton) ? [panel filename] : nil;
848 [backendProxy setDialogReturn:string];
850 @catch (NSException *e) {
851 NSLog(@"Exception caught in %s %@", _cmd, e);
855 - (void)alertDidEnd:(MMAlert *)alert code:(int)code context:(void *)context
859 code = code - NSAlertFirstButtonReturn + 1;
861 if ([alert isKindOfClass:[MMAlert class]] && [alert textField]) {
862 ret = [NSArray arrayWithObjects:[NSNumber numberWithInt:code],
863 [[alert textField] stringValue], nil];
865 ret = [NSArray arrayWithObject:[NSNumber numberWithInt:code]];
869 [backendProxy setDialogReturn:ret];
871 @catch (NSException *e) {
872 NSLog(@"Exception caught in %s %@", _cmd, e);
876 - (NSMenuItem *)menuItemForTag:(int)tag
878 // Search the main menu.
879 int i, count = [mainMenuItems count];
880 for (i = 0; i < count; ++i) {
881 NSMenuItem *item = [mainMenuItems objectAtIndex:i];
882 if ([item tag] == tag) return item;
883 item = findMenuItemWithTagInMenu([item submenu], tag);
884 if (item) return item;
887 // Search the popup menus.
888 count = [popupMenuItems count];
889 for (i = 0; i < count; ++i) {
890 NSMenuItem *item = [popupMenuItems objectAtIndex:i];
891 if ([item tag] == tag) return item;
892 item = findMenuItemWithTagInMenu([item submenu], tag);
893 if (item) return item;
899 - (NSMenu *)menuForTag:(int)tag
901 return [[self menuItemForTag:tag] submenu];
904 - (NSMenu *)topLevelMenuForTitle:(NSString *)title
906 // Search only the top-level menus.
908 unsigned i, count = [popupMenuItems count];
909 for (i = 0; i < count; ++i) {
910 NSMenuItem *item = [popupMenuItems objectAtIndex:i];
911 if ([title isEqual:[item title]])
912 return [item submenu];
915 count = [mainMenuItems count];
916 for (i = 0; i < count; ++i) {
917 NSMenuItem *item = [mainMenuItems objectAtIndex:i];
918 if ([title isEqual:[item title]])
919 return [item submenu];
925 - (void)addMenuWithTag:(int)tag parent:(int)parentTag title:(NSString *)title
928 NSMenu *parent = [self menuForTag:parentTag];
929 NSMenuItem *item = [[NSMenuItem alloc] init];
930 NSMenu *menu = [[NSMenu alloc] initWithTitle:title];
932 [menu setAutoenablesItems:NO];
934 [item setTitle:title];
935 [item setSubmenu:menu];
938 if ([parent numberOfItems] <= idx) {
939 [parent addItem:item];
941 [parent insertItem:item atIndex:idx];
944 NSMutableArray *items = (MenuPopupType == parentTag)
945 ? popupMenuItems : mainMenuItems;
946 if ([items count] <= idx) {
947 [items addObject:item];
949 [items insertObject:item atIndex:idx];
952 shouldUpdateMainMenu = (MenuPopupType != parentTag);
959 - (void)addMenuItemWithTag:(int)tag parent:(NSMenu *)parent
960 title:(NSString *)title tip:(NSString *)tip
961 keyEquivalent:(int)key modifiers:(int)mask
962 action:(NSString *)action atIndex:(int)idx
965 NSMenuItem *item = nil;
966 if (!title || ([title hasPrefix:@"-"] && [title hasSuffix:@"-"])) {
967 item = [NSMenuItem separatorItem];
969 item = [[[NSMenuItem alloc] init] autorelease];
970 [item setTitle:title];
971 // TODO: Check that 'action' is a valid action (nothing will happen
972 // if it isn't, but it would be nice with a warning).
973 if (action) [item setAction:NSSelectorFromString(action)];
974 else [item setAction:@selector(vimMenuItemAction:)];
975 if (tip) [item setToolTip:tip];
978 NSString *keyString =
979 [NSString stringWithFormat:@"%C", key];
980 [item setKeyEquivalent:keyString];
981 [item setKeyEquivalentModifierMask:mask];
985 // NOTE! The tag is used to idenfity which menu items were
986 // added by Vim (tag != 0) and which were added by the AppKit
990 if ([parent numberOfItems] <= idx) {
991 [parent addItem:item];
993 [parent insertItem:item atIndex:idx];
996 NSLog(@"WARNING: Menu item '%@' (tag=%d) has no parent.", title, tag);
1000 - (void)updateMainMenu
1002 NSMenu *mainMenu = [NSApp mainMenu];
1004 // Stop NSApp from updating the Window menu.
1005 [NSApp setWindowsMenu:nil];
1007 // Remove all menus from main menu (except the MacVim menu).
1008 int i, count = [mainMenu numberOfItems];
1009 for (i = count-1; i > 0; --i) {
1010 [mainMenu removeItemAtIndex:i];
1013 // Add menus from 'mainMenuItems' to main menu.
1014 count = [mainMenuItems count];
1015 for (i = 0; i < count; ++i) {
1016 [mainMenu addItem:[mainMenuItems objectAtIndex:i]];
1019 // Set the new Window menu.
1020 // TODO! Need to look for 'Window' in all localized languages.
1021 NSMenu *windowMenu = [[mainMenu itemWithTitle:@"Window"] submenu];
1023 // Remove all AppKit owned menu items (tag == 0); they will be added
1024 // again when setWindowsMenu: is called.
1025 count = [windowMenu numberOfItems];
1026 for (i = count-1; i >= 0; --i) {
1027 NSMenuItem *item = [windowMenu itemAtIndex:i];
1029 [windowMenu removeItem:item];
1033 [NSApp setWindowsMenu:windowMenu];
1036 shouldUpdateMainMenu = NO;
1039 - (NSToolbarItem *)toolbarItemForTag:(int)tag index:(int *)index
1041 if (!toolbar) return nil;
1043 NSArray *items = [toolbar items];
1044 int i, count = [items count];
1045 for (i = 0; i < count; ++i) {
1046 NSToolbarItem *item = [items objectAtIndex:i];
1047 if ([item tag] == tag) {
1048 if (index) *index = i;
1056 - (void)addToolbarItemToDictionaryWithTag:(int)tag label:(NSString *)title
1057 toolTip:(NSString *)tip icon:(NSString *)icon
1059 // If the item corresponds to a separator then do nothing, since it is
1060 // already defined by Cocoa.
1061 if (!title || [title isEqual:NSToolbarSeparatorItemIdentifier]
1062 || [title isEqual:NSToolbarSpaceItemIdentifier]
1063 || [title isEqual:NSToolbarFlexibleSpaceItemIdentifier])
1066 NSToolbarItem *item = [[NSToolbarItem alloc] initWithItemIdentifier:title];
1068 [item setLabel:title];
1069 [item setToolTip:tip];
1070 [item setAction:@selector(vimMenuItemAction:)];
1071 [item setAutovalidates:NO];
1073 NSImage *img = [NSImage imageNamed:icon];
1075 NSLog(@"WARNING: Could not find image with name '%@' to use as toolbar"
1076 " image for identifier '%@';"
1077 " using default toolbar icon '%@' instead.",
1078 icon, title, MMDefaultToolbarImageName);
1080 img = [NSImage imageNamed:MMDefaultToolbarImageName];
1083 [item setImage:img];
1085 [toolbarItemDict setObject:item forKey:title];
1090 - (void)addToolbarItemWithTag:(int)tag label:(NSString *)label tip:(NSString
1091 *)tip icon:(NSString *)icon atIndex:(int)idx
1093 if (!toolbar) return;
1095 // Check for separator items.
1097 label = NSToolbarSeparatorItemIdentifier;
1098 } else if ([label length] >= 2 && [label hasPrefix:@"-"]
1099 && [label hasSuffix:@"-"]) {
1100 // The label begins and ends with '-'; decided which kind of separator
1101 // item it is by looking at the prefix.
1102 if ([label hasPrefix:@"-space"]) {
1103 label = NSToolbarSpaceItemIdentifier;
1104 } else if ([label hasPrefix:@"-flexspace"]) {
1105 label = NSToolbarFlexibleSpaceItemIdentifier;
1107 label = NSToolbarSeparatorItemIdentifier;
1111 [self addToolbarItemToDictionaryWithTag:tag label:label toolTip:tip
1114 int maxIdx = [[toolbar items] count];
1115 if (maxIdx < idx) idx = maxIdx;
1117 [toolbar insertItemWithItemIdentifier:label atIndex:idx];
1120 - (void)connectionDidDie:(NSNotification *)notification
1122 //NSLog(@"%@ %s%@", [self className], _cmd, notification);
1126 // NOTE! This causes the call to removeVimController: to be delayed.
1128 performSelectorOnMainThread:@selector(removeVimController:)
1129 withObject:self waitUntilDone:NO];
1132 - (NSString *)description
1134 return [NSString stringWithFormat:@"%@ : isInitialized=%d inProcessCommandQueue=%d mainMenuItems=%@ popupMenuItems=%@ toolbar=%@", [self className], isInitialized, inProcessCommandQueue, mainMenuItems, popupMenuItems, toolbar];
1137 @end // MMVimController (Private)
1141 @implementation NSColor (MMProtocol)
1143 + (NSColor *)colorWithRgbInt:(int)rgb
1145 float r = ((rgb>>16) & 0xff)/255.0f;
1146 float g = ((rgb>>8) & 0xff)/255.0f;
1147 float b = (rgb & 0xff)/255.0f;
1149 return [NSColor colorWithCalibratedRed:r green:g blue:b alpha:1.0f];
1152 @end // NSColor (MMProtocol)
1156 @implementation MMAlert
1159 [textField release];
1163 - (void)setTextFieldString:(NSString *)textFieldString
1165 [textField release];
1166 textField = [[NSTextField alloc] init];
1167 [textField setStringValue:textFieldString];
1170 - (NSTextField *)textField
1175 - (void)setInformativeText:(NSString *)text
1178 // HACK! Add some space for the text field.
1179 [super setInformativeText:[text stringByAppendingString:@"\n\n\n"]];
1181 [super setInformativeText:text];
1185 - (void)beginSheetModalForWindow:(NSWindow *)window
1186 modalDelegate:(id)delegate
1187 didEndSelector:(SEL)didEndSelector
1188 contextInfo:(void *)contextInfo
1190 [super beginSheetModalForWindow:window
1191 modalDelegate:delegate
1192 didEndSelector:didEndSelector
1193 contextInfo:contextInfo];
1195 // HACK! Place the input text field at the bottom of the informative text
1196 // (which has been made a bit larger by adding newline characters).
1197 NSView *contentView = [[self window] contentView];
1198 NSRect rect = [contentView frame];
1199 rect.origin.y = rect.size.height;
1201 NSArray *subviews = [contentView subviews];
1202 unsigned i, count = [subviews count];
1203 for (i = 0; i < count; ++i) {
1204 NSView *view = [subviews objectAtIndex:i];
1205 if ([view isKindOfClass:[NSTextField class]]
1206 && [view frame].origin.y < rect.origin.y) {
1207 // NOTE: The informative text field is the lowest NSTextField in
1208 // the alert dialog.
1209 rect = [view frame];
1213 rect.size.height = MMAlertTextFieldHeight;
1214 [textField setFrame:rect];
1215 [contentView addSubview:textField];
1216 [textField becomeFirstResponder];