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
20 // This is taken from gui.h
21 #define DRAW_CURSOR 0x20
23 static NSString *MMDefaultToolbarImageName = @"Attention";
24 static int MMAlertTextFieldHeight = 22;
26 #if MM_NO_REQUEST_TIMEOUT
27 // NOTE: By default a message sent to the backend will be dropped if it cannot
28 // be delivered instantly; otherwise there is a possibility that MacVim will
29 // 'beachball' while waiting to deliver DO messages to an unresponsive Vim
30 // process. This means that you cannot rely on any message sent with
31 // sendMessage:: to actually reach Vim.
32 static NSTimeInterval MMBackendProxyRequestTimeout = 0;
36 @interface MMAlert : NSAlert {
37 NSTextField *textField;
39 - (void)setTextFieldString:(NSString *)textFieldString;
40 - (NSTextField *)textField;
44 @interface MMVimController (Private)
45 - (void)handleMessage:(int)msgid data:(NSData *)data;
46 - (void)performBatchDrawWithData:(NSData *)data;
47 - (void)savePanelDidEnd:(NSSavePanel *)panel code:(int)code
48 context:(void *)context;
49 - (void)alertDidEnd:(MMAlert *)alert code:(int)code context:(void *)context;
50 - (NSMenuItem *)menuItemForTag:(int)tag;
51 - (NSMenu *)menuForTag:(int)tag;
52 - (NSMenu *)topLevelMenuForTitle:(NSString *)title;
53 - (void)addMenuWithTag:(int)tag parent:(int)parentTag title:(NSString *)title
55 - (void)addMenuItemWithTag:(int)tag parent:(NSMenu *)parent
56 title:(NSString *)title tip:(NSString *)tip
57 keyEquivalent:(int)key modifiers:(int)mask
58 action:(NSString *)action atIndex:(int)idx;
59 - (void)updateMainMenu;
60 - (NSToolbarItem *)toolbarItemForTag:(int)tag index:(int *)index;
61 - (void)addToolbarItemToDictionaryWithTag:(int)tag label:(NSString *)title
62 toolTip:(NSString *)tip icon:(NSString *)icon;
63 - (void)addToolbarItemWithTag:(int)tag label:(NSString *)label
64 tip:(NSString *)tip icon:(NSString *)icon
66 - (void)connectionDidDie:(NSNotification *)notification;
71 // TODO: Move to separate file
72 @interface NSColor (MMProtocol)
73 + (NSColor *)colorWithRgbInt:(int)rgb;
78 static NSMenuItem *findMenuItemWithTagInMenu(NSMenu *root, int tag)
81 NSMenuItem *item = [root itemWithTag:tag];
82 if (item) return item;
84 NSArray *items = [root itemArray];
85 unsigned i, count = [items count];
86 for (i = 0; i < count; ++i) {
87 item = [items objectAtIndex:i];
88 if ([item hasSubmenu]) {
89 item = findMenuItemWithTagInMenu([item submenu], tag);
90 if (item) return item;
100 @implementation MMVimController
102 - (id)initWithBackend:(id)backend pid:(int)processIdentifier
104 if ((self = [super init])) {
106 [[MMWindowController alloc] initWithVimController:self];
107 backendProxy = [backend retain];
108 sendQueue = [NSMutableArray new];
109 mainMenuItems = [[NSMutableArray alloc] init];
110 popupMenuItems = [[NSMutableArray alloc] init];
111 toolbarItemDict = [[NSMutableDictionary alloc] init];
112 pid = processIdentifier;
114 NSConnection *connection = [backendProxy connectionForProxy];
116 #if MM_NO_REQUEST_TIMEOUT
117 // TODO: Check that this will not set the timeout for the root proxy
118 // (in MMAppController).
119 [connection setRequestTimeout:MMBackendProxyRequestTimeout];
122 [[NSNotificationCenter defaultCenter] addObserver:self
123 selector:@selector(connectionDidDie:)
124 name:NSConnectionDidDieNotification object:connection];
127 NSWindow *win = [windowController window];
129 [[NSNotificationCenter defaultCenter]
131 selector:@selector(windowDidBecomeMain:)
132 name:NSWindowDidBecomeMainNotification
143 //NSLog(@"%@ %s", [self className], _cmd);
146 [serverName release]; serverName = nil;
147 [backendProxy release]; backendProxy = nil;
148 [sendQueue release]; sendQueue = nil;
150 [toolbarItemDict release]; toolbarItemDict = nil;
151 [toolbar release]; toolbar = nil;
152 [popupMenuItems release]; popupMenuItems = nil;
153 [mainMenuItems release]; mainMenuItems = nil;
154 [windowController release]; windowController = nil;
159 - (MMWindowController *)windowController
161 return windowController;
164 - (void)setServerName:(NSString *)name
166 if (name != serverName) {
167 [serverName release];
168 serverName = [name copy];
172 - (NSString *)serverName
182 - (void)dropFiles:(NSArray *)filenames
184 int i, numberOfFiles = [filenames count];
185 NSMutableData *data = [NSMutableData data];
187 [data appendBytes:&numberOfFiles length:sizeof(int)];
189 for (i = 0; i < numberOfFiles; ++i) {
190 NSString *file = [filenames objectAtIndex:i];
191 int len = [file lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
194 ++len; // append NUL as well
195 [data appendBytes:&len length:sizeof(int)];
196 [data appendBytes:[file UTF8String] length:len];
200 [self sendMessage:DropFilesMsgID data:data wait:NO];
203 - (void)dropString:(NSString *)string
205 int len = [string lengthOfBytesUsingEncoding:NSUTF8StringEncoding] + 1;
207 NSMutableData *data = [NSMutableData data];
209 [data appendBytes:&len length:sizeof(int)];
210 [data appendBytes:[string UTF8String] length:len];
212 [self sendMessage:DropStringMsgID data:data wait:NO];
216 - (void)sendMessage:(int)msgid data:(NSData *)data wait:(BOOL)wait
218 //NSLog(@"sendMessage:%s (isInitialized=%d inProcessCommandQueue=%d)",
219 // MessageStrings[msgid], isInitialized, inProcessCommandQueue);
221 if (!isInitialized) return;
223 if (inProcessCommandQueue) {
224 //NSLog(@"In process command queue; delaying message send.");
225 [sendQueue addObject:[NSNumber numberWithInt:msgid]];
227 [sendQueue addObject:data];
229 [sendQueue addObject:[NSNull null]];
233 #if MM_NO_REQUEST_TIMEOUT
235 [backendProxy processInput:msgid data:data];
237 @catch (NSException *e) {
238 //NSLog(@"%@ %s Exception caught during DO call: %@",
239 // [self className], _cmd, e);
244 [backendProxy processInput:msgid data:data];
246 @catch (NSException *e) {
247 NSLog(@"%@ %s Exception caught during DO call: %@",
248 [self className], _cmd, e);
251 // Do not wait for the message to be sent, i.e. drop the message if it
252 // can't be delivered immediately.
253 NSConnection *connection = [backendProxy connectionForProxy];
255 NSTimeInterval req = [connection requestTimeout];
256 [connection setRequestTimeout:0];
258 [backendProxy processInput:msgid data:data];
260 @catch (NSException *e) {
261 // Connection timed out, just ignore this.
262 //NSLog(@"WARNING! Connection timed out in %s", _cmd);
265 [connection setRequestTimeout:req];
279 //NSLog(@"%@ %s", [self className], _cmd);
280 if (!isInitialized) return;
283 [toolbar setDelegate:nil];
284 [[NSNotificationCenter defaultCenter] removeObserver:self];
285 [windowController cleanup];
288 - (oneway void)showSavePanelForDirectory:(in bycopy NSString *)dir
289 title:(in bycopy NSString *)title
292 if (!isInitialized) return;
295 [[NSSavePanel savePanel] beginSheetForDirectory:dir file:nil
296 modalForWindow:[windowController window]
298 didEndSelector:@selector(savePanelDidEnd:code:context:)
301 NSOpenPanel *panel = [NSOpenPanel openPanel];
302 [panel setAllowsMultipleSelection:NO];
303 [panel beginSheetForDirectory:dir file:nil types:nil
304 modalForWindow:[windowController window]
306 didEndSelector:@selector(savePanelDidEnd:code:context:)
311 - (oneway void)presentDialogWithStyle:(int)style
312 message:(in bycopy NSString *)message
313 informativeText:(in bycopy NSString *)text
314 buttonTitles:(in bycopy NSArray *)buttonTitles
315 textFieldString:(in bycopy NSString *)textFieldString
317 if (!(windowController && buttonTitles && [buttonTitles count])) return;
319 MMAlert *alert = [[MMAlert alloc] init];
321 // NOTE! This has to be done before setting the informative text.
323 [alert setTextFieldString:textFieldString];
325 [alert setAlertStyle:style];
328 [alert setMessageText:message];
330 // If no message text is specified 'Alert' is used, which we don't
331 // want, so set an empty string as message text.
332 [alert setMessageText:@""];
336 [alert setInformativeText:text];
337 } else if (textFieldString) {
338 // Make sure there is always room for the input text field.
339 [alert setInformativeText:@""];
342 unsigned i, count = [buttonTitles count];
343 for (i = 0; i < count; ++i) {
344 NSString *title = [buttonTitles objectAtIndex:i];
345 // NOTE: The title of the button may contain the character '&' to
346 // indicate that the following letter should be the key equivalent
347 // associated with the button. Extract this letter and lowercase it.
348 NSString *keyEquivalent = nil;
349 NSRange hotkeyRange = [title rangeOfString:@"&"];
350 if (NSNotFound != hotkeyRange.location) {
351 if ([title length] > NSMaxRange(hotkeyRange)) {
352 NSRange keyEquivRange = NSMakeRange(hotkeyRange.location+1, 1);
353 keyEquivalent = [[title substringWithRange:keyEquivRange]
357 NSMutableString *string = [NSMutableString stringWithString:title];
358 [string deleteCharactersInRange:hotkeyRange];
362 [alert addButtonWithTitle:title];
364 // Set key equivalent for the button, but only if NSAlert hasn't
365 // already done so. (Check the documentation for
366 // - [NSAlert addButtonWithTitle:] to see what key equivalents are
367 // automatically assigned.)
368 NSButton *btn = [[alert buttons] lastObject];
369 if ([[btn keyEquivalent] length] == 0 && keyEquivalent) {
370 [btn setKeyEquivalent:keyEquivalent];
374 [alert beginSheetModalForWindow:[windowController window]
376 didEndSelector:@selector(alertDidEnd:code:context:)
382 - (oneway void)processCommandQueue:(in NSArray *)queue
384 if (!isInitialized) return;
386 unsigned i, count = [queue count];
388 NSLog(@"WARNING: Uneven number of components (%d) in flush queue "
389 "message; ignoring this message.", count);
393 inProcessCommandQueue = YES;
395 //NSLog(@"======== %s BEGIN ========", _cmd);
396 for (i = 0; i < count; i += 2) {
397 NSData *value = [queue objectAtIndex:i];
398 NSData *data = [queue objectAtIndex:i+1];
400 int msgid = *((int*)[value bytes]);
402 if (msgid != EnableMenuItemMsgID && msgid != AddMenuItemMsgID
403 && msgid != AddMenuMsgID) {
404 NSLog(@"%s%s", _cmd, MessageStrings[msgid]);
408 [self handleMessage:msgid data:data];
410 //NSLog(@"======== %s END ========", _cmd);
412 if (shouldUpdateMainMenu) {
413 [self updateMainMenu];
416 [windowController processCommandQueueDidFinish];
418 inProcessCommandQueue = NO;
420 if ([sendQueue count] > 0) {
421 #if MM_NO_REQUEST_TIMEOUT
423 [backendProxy processInputAndData:sendQueue];
425 @catch (NSException *e) {
426 // Connection timed out, just ignore this.
427 //NSLog(@"WARNING! Connection timed out in %s", _cmd);
430 // Do not wait for the message to be sent, i.e. drop the message if it
431 // can't be delivered immediately.
432 NSConnection *connection = [backendProxy connectionForProxy];
434 NSTimeInterval req = [connection requestTimeout];
435 [connection setRequestTimeout:0];
437 [backendProxy processInputAndData:sendQueue];
439 @catch (NSException *e) {
440 // Connection timed out, just ignore this.
441 //NSLog(@"WARNING! Connection timed out in %s", _cmd);
444 [connection setRequestTimeout:req];
449 [sendQueue removeAllObjects];
453 - (void)windowDidBecomeMain:(NSNotification *)notification
456 [self updateMainMenu];
459 - (NSToolbarItem *)toolbar:(NSToolbar *)theToolbar
460 itemForItemIdentifier:(NSString *)itemId
461 willBeInsertedIntoToolbar:(BOOL)flag
463 NSToolbarItem *item = [toolbarItemDict objectForKey:itemId];
465 NSLog(@"WARNING: No toolbar item with id '%@'", itemId);
471 - (NSArray *)toolbarAllowedItemIdentifiers:(NSToolbar *)theToolbar
476 - (NSArray *)toolbarDefaultItemIdentifiers:(NSToolbar *)theToolbar
481 @end // MMVimController
485 @implementation MMVimController (Private)
487 - (void)handleMessage:(int)msgid data:(NSData *)data
489 //NSLog(@"%@ %s", [self className], _cmd);
491 if (OpenVimWindowMsgID == msgid) {
492 [windowController openWindow];
493 } else if (BatchDrawMsgID == msgid) {
494 [self performBatchDrawWithData:data];
495 } else if (SelectTabMsgID == msgid) {
496 #if 0 // NOTE: Tab selection is done inside updateTabsWithData:.
497 const void *bytes = [data bytes];
498 int idx = *((int*)bytes);
499 //NSLog(@"Selecting tab with index %d", idx);
500 [windowController selectTabWithIndex:idx];
502 } else if (UpdateTabBarMsgID == msgid) {
503 [windowController updateTabsWithData:data];
504 } else if (ShowTabBarMsgID == msgid) {
505 [windowController showTabBar:YES];
506 } else if (HideTabBarMsgID == msgid) {
507 [windowController showTabBar:NO];
508 } else if (SetTextDimensionsMsgID == msgid) {
509 const void *bytes = [data bytes];
510 int rows = *((int*)bytes); bytes += sizeof(int);
511 int cols = *((int*)bytes); bytes += sizeof(int);
513 [windowController setTextDimensionsWithRows:rows columns:cols];
514 } else if (SetVimWindowTitleMsgID == msgid) {
515 const void *bytes = [data bytes];
516 int len = *((int*)bytes); bytes += sizeof(int);
518 NSString *string = [[NSString alloc] initWithBytes:(void*)bytes
519 length:len encoding:NSUTF8StringEncoding];
521 [[windowController window] setTitle:string];
524 } else if (AddMenuMsgID == msgid) {
525 NSString *title = nil;
526 const void *bytes = [data bytes];
527 int tag = *((int*)bytes); bytes += sizeof(int);
528 int parentTag = *((int*)bytes); bytes += sizeof(int);
529 int len = *((int*)bytes); bytes += sizeof(int);
531 title = [[NSString alloc] initWithBytes:(void*)bytes length:len
532 encoding:NSUTF8StringEncoding];
535 int idx = *((int*)bytes); bytes += sizeof(int);
537 if (MenuToolbarType == parentTag) {
539 // NOTE! Each toolbar must have a unique identifier, else each
540 // window will have the same toolbar.
541 NSString *ident = [NSString stringWithFormat:@"%d.%d",
543 toolbar = [[NSToolbar alloc] initWithIdentifier:ident];
545 [toolbar setShowsBaselineSeparator:NO];
546 [toolbar setDelegate:self];
547 [toolbar setDisplayMode:NSToolbarDisplayModeIconOnly];
548 [toolbar setSizeMode:NSToolbarSizeModeSmall];
550 NSWindow *win = [windowController window];
551 [win setToolbar:toolbar];
553 // HACK! Redirect the pill button so that we can ask Vim to
555 NSButton *pillButton = [win
556 standardWindowButton:NSWindowToolbarButton];
558 [pillButton setAction:@selector(toggleToolbar:)];
559 [pillButton setTarget:windowController];
563 [self addMenuWithTag:tag parent:parentTag title:title atIndex:idx];
567 } else if (AddMenuItemMsgID == msgid) {
568 NSString *title = nil, *tip = nil, *icon = nil, *action = nil;
569 const void *bytes = [data bytes];
570 int tag = *((int*)bytes); bytes += sizeof(int);
571 int parentTag = *((int*)bytes); bytes += sizeof(int);
572 int namelen = *((int*)bytes); bytes += sizeof(int);
574 title = [[NSString alloc] initWithBytes:(void*)bytes length:namelen
575 encoding:NSUTF8StringEncoding];
578 int tiplen = *((int*)bytes); bytes += sizeof(int);
580 tip = [[NSString alloc] initWithBytes:(void*)bytes length:tiplen
581 encoding:NSUTF8StringEncoding];
584 int iconlen = *((int*)bytes); bytes += sizeof(int);
586 icon = [[NSString alloc] initWithBytes:(void*)bytes length:iconlen
587 encoding:NSUTF8StringEncoding];
590 int actionlen = *((int*)bytes); bytes += sizeof(int);
592 action = [[NSString alloc] initWithBytes:(void*)bytes
594 encoding:NSUTF8StringEncoding];
597 int idx = *((int*)bytes); bytes += sizeof(int);
598 if (idx < 0) idx = 0;
599 int key = *((int*)bytes); bytes += sizeof(int);
600 int mask = *((int*)bytes); bytes += sizeof(int);
602 NSString *ident = [NSString stringWithFormat:@"%d.%d",
603 (int)self, parentTag];
604 if (toolbar && [[toolbar identifier] isEqual:ident]) {
605 [self addToolbarItemWithTag:tag label:title tip:tip icon:icon
608 NSMenu *parent = [self menuForTag:parentTag];
609 [self addMenuItemWithTag:tag parent:parent title:title tip:tip
610 keyEquivalent:key modifiers:mask action:action
618 } else if (RemoveMenuItemMsgID == msgid) {
619 const void *bytes = [data bytes];
620 int tag = *((int*)bytes); bytes += sizeof(int);
624 if ((item = [self toolbarItemForTag:tag index:&idx])) {
625 [toolbar removeItemAtIndex:idx];
626 } else if ((item = [self menuItemForTag:tag])) {
629 if ([item menu] == [NSApp mainMenu] || ![item menu]) {
630 // NOTE: To be on the safe side we try to remove the item from
631 // both arrays (it is ok to call removeObject: even if an array
632 // does not contain the object to remove).
633 [mainMenuItems removeObject:item];
634 [popupMenuItems removeObject:item];
638 [[item menu] removeItem:item];
642 } else if (EnableMenuItemMsgID == msgid) {
643 const void *bytes = [data bytes];
644 int tag = *((int*)bytes); bytes += sizeof(int);
645 int state = *((int*)bytes); bytes += sizeof(int);
647 id item = [self toolbarItemForTag:tag index:NULL];
649 item = [self menuItemForTag:tag];
651 [item setEnabled:state];
652 } else if (ShowToolbarMsgID == msgid) {
653 const void *bytes = [data bytes];
654 int enable = *((int*)bytes); bytes += sizeof(int);
655 int flags = *((int*)bytes); bytes += sizeof(int);
657 int mode = NSToolbarDisplayModeDefault;
658 if (flags & ToolbarLabelFlag) {
659 mode = flags & ToolbarIconFlag ? NSToolbarDisplayModeIconAndLabel
660 : NSToolbarDisplayModeLabelOnly;
661 } else if (flags & ToolbarIconFlag) {
662 mode = NSToolbarDisplayModeIconOnly;
665 int size = flags & ToolbarSizeRegularFlag ? NSToolbarSizeModeRegular
666 : NSToolbarSizeModeSmall;
668 [windowController showToolbar:enable size:size mode:mode];
669 } else if (CreateScrollbarMsgID == msgid) {
670 const void *bytes = [data bytes];
671 long ident = *((long*)bytes); bytes += sizeof(long);
672 int type = *((int*)bytes); bytes += sizeof(int);
674 [windowController createScrollbarWithIdentifier:ident type:type];
675 } else if (DestroyScrollbarMsgID == msgid) {
676 const void *bytes = [data bytes];
677 long ident = *((long*)bytes); bytes += sizeof(long);
679 [windowController destroyScrollbarWithIdentifier:ident];
680 } else if (ShowScrollbarMsgID == msgid) {
681 const void *bytes = [data bytes];
682 long ident = *((long*)bytes); bytes += sizeof(long);
683 int visible = *((int*)bytes); bytes += sizeof(int);
685 [windowController showScrollbarWithIdentifier:ident state:visible];
686 } else if (SetScrollbarPositionMsgID == msgid) {
687 const void *bytes = [data bytes];
688 long ident = *((long*)bytes); bytes += sizeof(long);
689 int pos = *((int*)bytes); bytes += sizeof(int);
690 int len = *((int*)bytes); bytes += sizeof(int);
692 [windowController setScrollbarPosition:pos length:len
694 } else if (SetScrollbarThumbMsgID == msgid) {
695 const void *bytes = [data bytes];
696 long ident = *((long*)bytes); bytes += sizeof(long);
697 float val = *((float*)bytes); bytes += sizeof(float);
698 float prop = *((float*)bytes); bytes += sizeof(float);
700 [windowController setScrollbarThumbValue:val proportion:prop
702 } else if (SetFontMsgID == msgid) {
703 const void *bytes = [data bytes];
704 float size = *((float*)bytes); bytes += sizeof(float);
705 int len = *((int*)bytes); bytes += sizeof(int);
706 NSString *name = [[NSString alloc]
707 initWithBytes:(void*)bytes length:len
708 encoding:NSUTF8StringEncoding];
709 NSFont *font = [NSFont fontWithName:name size:size];
712 [windowController setFont:font];
715 } else if (SetDefaultColorsMsgID == msgid) {
716 const void *bytes = [data bytes];
717 int bg = *((int*)bytes); bytes += sizeof(int);
718 int fg = *((int*)bytes); bytes += sizeof(int);
719 NSColor *back = [NSColor colorWithRgbInt:bg];
720 NSColor *fore = [NSColor colorWithRgbInt:fg];
722 [windowController setDefaultColorsBackground:back foreground:fore];
723 } else if (ExecuteActionMsgID == msgid) {
724 const void *bytes = [data bytes];
725 int len = *((int*)bytes); bytes += sizeof(int);
726 NSString *actionName = [[NSString alloc]
727 initWithBytesNoCopy:(void*)bytes
729 encoding:NSUTF8StringEncoding
732 SEL sel = NSSelectorFromString(actionName);
733 [NSApp sendAction:sel to:nil from:self];
735 [actionName release];
736 } else if (ShowPopupMenuMsgID == msgid) {
737 const void *bytes = [data bytes];
738 int row = *((int*)bytes); bytes += sizeof(int);
739 int col = *((int*)bytes); bytes += sizeof(int);
740 int len = *((int*)bytes); bytes += sizeof(int);
741 NSString *title = [[NSString alloc]
742 initWithBytesNoCopy:(void*)bytes
744 encoding:NSUTF8StringEncoding
747 NSMenu *menu = [self topLevelMenuForTitle:title];
749 [windowController popupMenu:menu atRow:row column:col];
751 NSLog(@"WARNING: Cannot popup menu with title %@; no such menu.",
756 } else if (SetMouseShapeMsgID == msgid) {
757 const void *bytes = [data bytes];
758 int shape = *((int*)bytes); bytes += sizeof(int);
760 [windowController setMouseShape:shape];
761 } else if (AdjustLinespaceMsgID == msgid) {
762 const void *bytes = [data bytes];
763 int linespace = *((int*)bytes); bytes += sizeof(int);
765 [windowController adjustLinespace:linespace];
766 } else if (ActivateMsgID == msgid) {
767 [NSApp activateIgnoringOtherApps:YES];
768 [[windowController window] makeKeyAndOrderFront:self];
769 } else if (SetServerNameMsgID == msgid) {
770 NSString *name = [[NSString alloc] initWithData:data
771 encoding:NSUTF8StringEncoding];
772 [self setServerName:name];
775 NSLog(@"WARNING: Unknown message received (msgid=%d)", msgid);
780 #define MM_DEBUG_DRAWING 0
782 - (void)performBatchDrawWithData:(NSData *)data
784 // TODO! Move to window controller.
785 MMTextStorage *textStorage = [windowController textStorage];
786 MMTextView *textView = [windowController textView];
787 if (!(textStorage && textView))
790 const void *bytes = [data bytes];
791 const void *end = bytes + [data length];
794 NSLog(@"====> BEGIN %s", _cmd);
796 [textStorage beginEditing];
798 // TODO: Sanity check input
800 while (bytes < end) {
801 int type = *((int*)bytes); bytes += sizeof(int);
803 if (ClearAllDrawType == type) {
804 int color = *((int*)bytes); bytes += sizeof(int);
807 NSLog(@" Clear all");
809 [textStorage clearAllWithColor:[NSColor colorWithRgbInt:color]];
810 } else if (ClearBlockDrawType == type) {
811 int color = *((int*)bytes); bytes += sizeof(int);
812 int row1 = *((int*)bytes); bytes += sizeof(int);
813 int col1 = *((int*)bytes); bytes += sizeof(int);
814 int row2 = *((int*)bytes); bytes += sizeof(int);
815 int col2 = *((int*)bytes); bytes += sizeof(int);
818 NSLog(@" Clear block (%d,%d) -> (%d,%d)", row1, col1,
821 [textStorage clearBlockFromRow:row1 column:col1
822 toRow:row2 column:col2
823 color:[NSColor colorWithRgbInt:color]];
824 } else if (DeleteLinesDrawType == type) {
825 int color = *((int*)bytes); bytes += sizeof(int);
826 int row = *((int*)bytes); bytes += sizeof(int);
827 int count = *((int*)bytes); bytes += sizeof(int);
828 int bot = *((int*)bytes); bytes += sizeof(int);
829 int left = *((int*)bytes); bytes += sizeof(int);
830 int right = *((int*)bytes); bytes += sizeof(int);
833 NSLog(@" Delete %d line(s) from %d", count, row);
835 [textStorage deleteLinesFromRow:row lineCount:count
836 scrollBottom:bot left:left right:right
837 color:[NSColor colorWithRgbInt:color]];
838 } else if (ReplaceStringDrawType == type) {
839 int bg = *((int*)bytes); bytes += sizeof(int);
840 int fg = *((int*)bytes); bytes += sizeof(int);
841 int sp = *((int*)bytes); bytes += sizeof(int);
842 int row = *((int*)bytes); bytes += sizeof(int);
843 int col = *((int*)bytes); bytes += sizeof(int);
844 int flags = *((int*)bytes); bytes += sizeof(int);
845 int len = *((int*)bytes); bytes += sizeof(int);
846 NSString *string = [[NSString alloc]
847 initWithBytesNoCopy:(void*)bytes
849 encoding:NSUTF8StringEncoding
854 NSLog(@" Draw string at (%d,%d) length=%d flags=%d fg=0x%x "
855 "bg=0x%x sp=0x%x (%@)", row, col, len, flags, fg, bg, sp,
856 len > 0 ? [string substringToIndex:1] : @"");
858 // NOTE: If this is a call to draw the (block) cursor, then cancel
859 // any previous request to draw the insertion point, or it might
860 // get drawn as well.
861 if (flags & DRAW_CURSOR) {
862 [textView setShouldDrawInsertionPoint:NO];
863 //[textView drawInsertionPointAtRow:row column:col
864 // shape:MMInsertionPointBlock
865 // color:[NSColor colorWithRgbInt:bg]];
867 [textStorage replaceString:string
870 foregroundColor:[NSColor colorWithRgbInt:fg]
871 backgroundColor:[NSColor colorWithRgbInt:bg]
872 specialColor:[NSColor colorWithRgbInt:sp]];
875 } else if (InsertLinesDrawType == type) {
876 int color = *((int*)bytes); bytes += sizeof(int);
877 int row = *((int*)bytes); bytes += sizeof(int);
878 int count = *((int*)bytes); bytes += sizeof(int);
879 int bot = *((int*)bytes); bytes += sizeof(int);
880 int left = *((int*)bytes); bytes += sizeof(int);
881 int right = *((int*)bytes); bytes += sizeof(int);
884 NSLog(@" Insert %d line(s) at row %d", count, row);
886 [textStorage insertLinesAtRow:row lineCount:count
887 scrollBottom:bot left:left right:right
888 color:[NSColor colorWithRgbInt:color]];
889 } else if (DrawCursorDrawType == type) {
890 int color = *((int*)bytes); bytes += sizeof(int);
891 int row = *((int*)bytes); bytes += sizeof(int);
892 int col = *((int*)bytes); bytes += sizeof(int);
893 int shape = *((int*)bytes); bytes += sizeof(int);
894 int percent = *((int*)bytes); bytes += sizeof(int);
897 NSLog(@" Draw cursor at (%d,%d)", row, col);
899 [textView drawInsertionPointAtRow:row column:col shape:shape
901 color:[NSColor colorWithRgbInt:color]];
903 NSLog(@"WARNING: Unknown draw type (type=%d)", type);
907 [textStorage endEditing];
909 NSLog(@"<==== END %s", _cmd);
913 - (void)savePanelDidEnd:(NSSavePanel *)panel code:(int)code
914 context:(void *)context
916 NSString *string = (code == NSOKButton) ? [panel filename] : nil;
918 [backendProxy setDialogReturn:string];
920 @catch (NSException *e) {
921 NSLog(@"Exception caught in %s %@", _cmd, e);
925 - (void)alertDidEnd:(MMAlert *)alert code:(int)code context:(void *)context
929 code = code - NSAlertFirstButtonReturn + 1;
931 if ([alert isKindOfClass:[MMAlert class]] && [alert textField]) {
932 ret = [NSArray arrayWithObjects:[NSNumber numberWithInt:code],
933 [[alert textField] stringValue], nil];
935 ret = [NSArray arrayWithObject:[NSNumber numberWithInt:code]];
939 [backendProxy setDialogReturn:ret];
941 @catch (NSException *e) {
942 NSLog(@"Exception caught in %s %@", _cmd, e);
946 - (NSMenuItem *)menuItemForTag:(int)tag
948 // Search the main menu.
949 int i, count = [mainMenuItems count];
950 for (i = 0; i < count; ++i) {
951 NSMenuItem *item = [mainMenuItems objectAtIndex:i];
952 if ([item tag] == tag) return item;
953 item = findMenuItemWithTagInMenu([item submenu], tag);
954 if (item) return item;
957 // Search the popup menus.
958 count = [popupMenuItems count];
959 for (i = 0; i < count; ++i) {
960 NSMenuItem *item = [popupMenuItems objectAtIndex:i];
961 if ([item tag] == tag) return item;
962 item = findMenuItemWithTagInMenu([item submenu], tag);
963 if (item) return item;
969 - (NSMenu *)menuForTag:(int)tag
971 return [[self menuItemForTag:tag] submenu];
974 - (NSMenu *)topLevelMenuForTitle:(NSString *)title
976 // Search only the top-level menus.
978 unsigned i, count = [popupMenuItems count];
979 for (i = 0; i < count; ++i) {
980 NSMenuItem *item = [popupMenuItems objectAtIndex:i];
981 if ([title isEqual:[item title]])
982 return [item submenu];
985 count = [mainMenuItems count];
986 for (i = 0; i < count; ++i) {
987 NSMenuItem *item = [mainMenuItems objectAtIndex:i];
988 if ([title isEqual:[item title]])
989 return [item submenu];
995 - (void)addMenuWithTag:(int)tag parent:(int)parentTag title:(NSString *)title
998 NSMenu *parent = [self menuForTag:parentTag];
999 NSMenuItem *item = [[NSMenuItem alloc] init];
1000 NSMenu *menu = [[NSMenu alloc] initWithTitle:title];
1002 [menu setAutoenablesItems:NO];
1004 [item setTitle:title];
1005 [item setSubmenu:menu];
1008 if ([parent numberOfItems] <= idx) {
1009 [parent addItem:item];
1011 [parent insertItem:item atIndex:idx];
1014 NSMutableArray *items = (MenuPopupType == parentTag)
1015 ? popupMenuItems : mainMenuItems;
1016 if ([items count] <= idx) {
1017 [items addObject:item];
1019 [items insertObject:item atIndex:idx];
1022 shouldUpdateMainMenu = (MenuPopupType != parentTag);
1029 - (void)addMenuItemWithTag:(int)tag parent:(NSMenu *)parent
1030 title:(NSString *)title tip:(NSString *)tip
1031 keyEquivalent:(int)key modifiers:(int)mask
1032 action:(NSString *)action atIndex:(int)idx
1035 NSMenuItem *item = nil;
1036 if (!title || ([title hasPrefix:@"-"] && [title hasSuffix:@"-"])) {
1037 item = [NSMenuItem separatorItem];
1039 item = [[[NSMenuItem alloc] init] autorelease];
1040 [item setTitle:title];
1041 // TODO: Check that 'action' is a valid action (nothing will happen
1042 // if it isn't, but it would be nice with a warning).
1043 if (action) [item setAction:NSSelectorFromString(action)];
1044 else [item setAction:@selector(vimMenuItemAction:)];
1045 if (tip) [item setToolTip:tip];
1048 NSString *keyString =
1049 [NSString stringWithFormat:@"%C", key];
1050 [item setKeyEquivalent:keyString];
1051 [item setKeyEquivalentModifierMask:mask];
1055 // NOTE! The tag is used to idenfity which menu items were
1056 // added by Vim (tag != 0) and which were added by the AppKit
1060 if ([parent numberOfItems] <= idx) {
1061 [parent addItem:item];
1063 [parent insertItem:item atIndex:idx];
1066 NSLog(@"WARNING: Menu item '%@' (tag=%d) has no parent.", title, tag);
1070 - (void)updateMainMenu
1072 NSMenu *mainMenu = [NSApp mainMenu];
1074 // Stop NSApp from updating the Window menu.
1075 [NSApp setWindowsMenu:nil];
1077 // Remove all menus from main menu (except the MacVim menu).
1078 int i, count = [mainMenu numberOfItems];
1079 for (i = count-1; i > 0; --i) {
1080 [mainMenu removeItemAtIndex:i];
1083 // Add menus from 'mainMenuItems' to main menu.
1084 count = [mainMenuItems count];
1085 for (i = 0; i < count; ++i) {
1086 [mainMenu addItem:[mainMenuItems objectAtIndex:i]];
1089 // Set the new Window menu.
1090 // TODO! Need to look for 'Window' in all localized languages.
1091 NSMenu *windowMenu = [[mainMenu itemWithTitle:@"Window"] submenu];
1093 // Remove all AppKit owned menu items (tag == 0); they will be added
1094 // again when setWindowsMenu: is called.
1095 count = [windowMenu numberOfItems];
1096 for (i = count-1; i >= 0; --i) {
1097 NSMenuItem *item = [windowMenu itemAtIndex:i];
1099 [windowMenu removeItem:item];
1103 [NSApp setWindowsMenu:windowMenu];
1106 shouldUpdateMainMenu = NO;
1109 - (NSToolbarItem *)toolbarItemForTag:(int)tag index:(int *)index
1111 if (!toolbar) return nil;
1113 NSArray *items = [toolbar items];
1114 int i, count = [items count];
1115 for (i = 0; i < count; ++i) {
1116 NSToolbarItem *item = [items objectAtIndex:i];
1117 if ([item tag] == tag) {
1118 if (index) *index = i;
1126 - (void)addToolbarItemToDictionaryWithTag:(int)tag label:(NSString *)title
1127 toolTip:(NSString *)tip icon:(NSString *)icon
1129 // If the item corresponds to a separator then do nothing, since it is
1130 // already defined by Cocoa.
1131 if (!title || [title isEqual:NSToolbarSeparatorItemIdentifier]
1132 || [title isEqual:NSToolbarSpaceItemIdentifier]
1133 || [title isEqual:NSToolbarFlexibleSpaceItemIdentifier])
1136 NSToolbarItem *item = [[NSToolbarItem alloc] initWithItemIdentifier:title];
1138 [item setLabel:title];
1139 [item setToolTip:tip];
1140 [item setAction:@selector(vimMenuItemAction:)];
1141 [item setAutovalidates:NO];
1143 NSImage *img = [NSImage imageNamed:icon];
1145 NSLog(@"WARNING: Could not find image with name '%@' to use as toolbar"
1146 " image for identifier '%@';"
1147 " using default toolbar icon '%@' instead.",
1148 icon, title, MMDefaultToolbarImageName);
1150 img = [NSImage imageNamed:MMDefaultToolbarImageName];
1153 [item setImage:img];
1155 [toolbarItemDict setObject:item forKey:title];
1160 - (void)addToolbarItemWithTag:(int)tag label:(NSString *)label tip:(NSString
1161 *)tip icon:(NSString *)icon atIndex:(int)idx
1163 if (!toolbar) return;
1165 // Check for separator items.
1167 label = NSToolbarSeparatorItemIdentifier;
1168 } else if ([label length] >= 2 && [label hasPrefix:@"-"]
1169 && [label hasSuffix:@"-"]) {
1170 // The label begins and ends with '-'; decided which kind of separator
1171 // item it is by looking at the prefix.
1172 if ([label hasPrefix:@"-space"]) {
1173 label = NSToolbarSpaceItemIdentifier;
1174 } else if ([label hasPrefix:@"-flexspace"]) {
1175 label = NSToolbarFlexibleSpaceItemIdentifier;
1177 label = NSToolbarSeparatorItemIdentifier;
1181 [self addToolbarItemToDictionaryWithTag:tag label:label toolTip:tip
1184 int maxIdx = [[toolbar items] count];
1185 if (maxIdx < idx) idx = maxIdx;
1187 [toolbar insertItemWithItemIdentifier:label atIndex:idx];
1190 - (void)connectionDidDie:(NSNotification *)notification
1192 //NSLog(@"%@ %s%@", [self className], _cmd, notification);
1196 // NOTE! This causes the call to removeVimController: to be delayed.
1198 performSelectorOnMainThread:@selector(removeVimController:)
1199 withObject:self waitUntilDone:NO];
1202 - (NSString *)description
1204 return [NSString stringWithFormat:@"%@ : isInitialized=%d inProcessCommandQueue=%d mainMenuItems=%@ popupMenuItems=%@ toolbar=%@", [self className], isInitialized, inProcessCommandQueue, mainMenuItems, popupMenuItems, toolbar];
1207 @end // MMVimController (Private)
1211 @implementation NSColor (MMProtocol)
1213 + (NSColor *)colorWithRgbInt:(int)rgb
1215 float r = ((rgb>>16) & 0xff)/255.0f;
1216 float g = ((rgb>>8) & 0xff)/255.0f;
1217 float b = (rgb & 0xff)/255.0f;
1219 return [NSColor colorWithCalibratedRed:r green:g blue:b alpha:1.0f];
1222 @end // NSColor (MMProtocol)
1226 @implementation MMAlert
1229 [textField release];
1233 - (void)setTextFieldString:(NSString *)textFieldString
1235 [textField release];
1236 textField = [[NSTextField alloc] init];
1237 [textField setStringValue:textFieldString];
1240 - (NSTextField *)textField
1245 - (void)setInformativeText:(NSString *)text
1248 // HACK! Add some space for the text field.
1249 [super setInformativeText:[text stringByAppendingString:@"\n\n\n"]];
1251 [super setInformativeText:text];
1255 - (void)beginSheetModalForWindow:(NSWindow *)window
1256 modalDelegate:(id)delegate
1257 didEndSelector:(SEL)didEndSelector
1258 contextInfo:(void *)contextInfo
1260 [super beginSheetModalForWindow:window
1261 modalDelegate:delegate
1262 didEndSelector:didEndSelector
1263 contextInfo:contextInfo];
1265 // HACK! Place the input text field at the bottom of the informative text
1266 // (which has been made a bit larger by adding newline characters).
1267 NSView *contentView = [[self window] contentView];
1268 NSRect rect = [contentView frame];
1269 rect.origin.y = rect.size.height;
1271 NSArray *subviews = [contentView subviews];
1272 unsigned i, count = [subviews count];
1273 for (i = 0; i < count; ++i) {
1274 NSView *view = [subviews objectAtIndex:i];
1275 if ([view isKindOfClass:[NSTextField class]]
1276 && [view frame].origin.y < rect.origin.y) {
1277 // NOTE: The informative text field is the lowest NSTextField in
1278 // the alert dialog.
1279 rect = [view frame];
1283 rect.size.height = MMAlertTextFieldHeight;
1284 [textField setFrame:rect];
1285 [contentView addSubview:textField];
1286 [textField becomeFirstResponder];