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:(int)rgb;
83 static NSMenuItem *findMenuItemWithTagInMenu(NSMenu *root, int tag)
86 NSMenuItem *item = [root itemWithTag:tag];
87 if (item) return item;
89 NSArray *items = [root itemArray];
90 unsigned i, count = [items count];
91 for (i = 0; i < count; ++i) {
92 item = [items objectAtIndex:i];
93 if ([item hasSubmenu]) {
94 item = findMenuItemWithTagInMenu([item submenu], tag);
95 if (item) return item;
105 @implementation MMVimController
107 - (id)initWithBackend:(id)backend pid:(int)processIdentifier
109 if ((self = [super init])) {
111 [[MMWindowController alloc] initWithVimController:self];
112 backendProxy = [backend retain];
113 sendQueue = [NSMutableArray new];
114 mainMenuItems = [[NSMutableArray alloc] init];
115 popupMenuItems = [[NSMutableArray alloc] init];
116 toolbarItemDict = [[NSMutableDictionary alloc] init];
117 pid = processIdentifier;
119 NSConnection *connection = [backendProxy connectionForProxy];
121 // TODO: Check that this will not set the timeout for the root proxy
122 // (in MMAppController).
123 [connection setRequestTimeout:MMBackendProxyRequestTimeout];
125 [[NSNotificationCenter defaultCenter] addObserver:self
126 selector:@selector(connectionDidDie:)
127 name:NSConnectionDidDieNotification object:connection];
130 NSWindow *win = [windowController window];
132 [[NSNotificationCenter defaultCenter]
134 selector:@selector(windowDidBecomeMain:)
135 name:NSWindowDidBecomeMainNotification
146 //NSLog(@"%@ %s", [self className], _cmd);
149 #if MM_RESEND_LAST_FAILURE
150 [resendData release]; resendData = nil;
153 [serverName release]; serverName = nil;
154 [backendProxy release]; backendProxy = nil;
155 [sendQueue release]; sendQueue = nil;
157 [toolbarItemDict release]; toolbarItemDict = nil;
158 [toolbar release]; toolbar = nil;
159 [popupMenuItems release]; popupMenuItems = nil;
160 [mainMenuItems release]; mainMenuItems = nil;
161 [windowController release]; windowController = nil;
166 - (MMWindowController *)windowController
168 return windowController;
171 - (void)setServerName:(NSString *)name
173 if (name != serverName) {
174 [serverName release];
175 serverName = [name copy];
179 - (NSString *)serverName
189 - (void)dropFiles:(NSArray *)filenames
191 int i, numberOfFiles = [filenames count];
192 NSMutableData *data = [NSMutableData data];
194 [data appendBytes:&numberOfFiles length:sizeof(int)];
196 for (i = 0; i < numberOfFiles; ++i) {
197 NSString *file = [filenames objectAtIndex:i];
198 int len = [file lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
201 ++len; // append NUL as well
202 [data appendBytes:&len length:sizeof(int)];
203 [data appendBytes:[file UTF8String] length:len];
207 [self sendMessage:DropFilesMsgID data:data];
210 - (void)dropString:(NSString *)string
212 int len = [string lengthOfBytesUsingEncoding:NSUTF8StringEncoding] + 1;
214 NSMutableData *data = [NSMutableData data];
216 [data appendBytes:&len length:sizeof(int)];
217 [data appendBytes:[string UTF8String] length:len];
219 [self sendMessage:DropStringMsgID data:data];
223 - (void)sendMessage:(int)msgid data:(NSData *)data
225 //NSLog(@"sendMessage:%s (isInitialized=%d inProcessCommandQueue=%d)",
226 // MessageStrings[msgid], isInitialized, inProcessCommandQueue);
228 if (!isInitialized) return;
230 if (inProcessCommandQueue) {
231 //NSLog(@"In process command queue; delaying message send.");
232 [sendQueue addObject:[NSNumber numberWithInt:msgid]];
234 [sendQueue addObject:data];
236 [sendQueue addObject:[NSNull null]];
240 #if MM_RESEND_LAST_FAILURE
242 //NSLog(@"cancelling scheduled resend of %s",
243 // MessageStrings[resendMsgid]);
245 [resendTimer invalidate];
246 [resendTimer release];
251 [resendData release];
257 [backendProxy processInput:msgid data:data];
259 @catch (NSException *e) {
260 //NSLog(@"%@ %s Exception caught during DO call: %@",
261 // [self className], _cmd, e);
262 #if MM_RESEND_LAST_FAILURE
263 //NSLog(@"%s failed, scheduling message %s for resend", _cmd,
264 // MessageStrings[msgid]);
267 resendData = [data retain];
268 resendTimer = [NSTimer
269 scheduledTimerWithTimeInterval:MMResendInterval
271 selector:@selector(resendTimerFired:)
274 [resendTimer retain];
279 - (BOOL)sendMessageNow:(int)msgid data:(NSData *)data
280 timeout:(NSTimeInterval)timeout
282 if (!isInitialized || inProcessCommandQueue)
285 if (timeout < 0) timeout = 0;
288 NSConnection *conn = [backendProxy connectionForProxy];
289 NSTimeInterval oldTimeout = [conn requestTimeout];
291 [conn setRequestTimeout:timeout];
294 [backendProxy processInput:msgid data:data];
296 @catch (NSException *e) {
300 [conn setRequestTimeout:oldTimeout];
313 //NSLog(@"%@ %s", [self className], _cmd);
314 if (!isInitialized) return;
317 [toolbar setDelegate:nil];
318 [[NSNotificationCenter defaultCenter] removeObserver:self];
319 [windowController cleanup];
322 - (oneway void)showSavePanelForDirectory:(in bycopy NSString *)dir
323 title:(in bycopy NSString *)title
326 if (!isInitialized) return;
329 [[NSSavePanel savePanel] beginSheetForDirectory:dir file:nil
330 modalForWindow:[windowController window]
332 didEndSelector:@selector(savePanelDidEnd:code:context:)
335 NSOpenPanel *panel = [NSOpenPanel openPanel];
336 [panel setAllowsMultipleSelection:NO];
337 [panel beginSheetForDirectory:dir file:nil types:nil
338 modalForWindow:[windowController window]
340 didEndSelector:@selector(savePanelDidEnd:code:context:)
345 - (oneway void)presentDialogWithStyle:(int)style
346 message:(in bycopy NSString *)message
347 informativeText:(in bycopy NSString *)text
348 buttonTitles:(in bycopy NSArray *)buttonTitles
349 textFieldString:(in bycopy NSString *)textFieldString
351 if (!(windowController && buttonTitles && [buttonTitles count])) return;
353 MMAlert *alert = [[MMAlert alloc] init];
355 // NOTE! This has to be done before setting the informative text.
357 [alert setTextFieldString:textFieldString];
359 [alert setAlertStyle:style];
362 [alert setMessageText:message];
364 // If no message text is specified 'Alert' is used, which we don't
365 // want, so set an empty string as message text.
366 [alert setMessageText:@""];
370 [alert setInformativeText:text];
371 } else if (textFieldString) {
372 // Make sure there is always room for the input text field.
373 [alert setInformativeText:@""];
376 unsigned i, count = [buttonTitles count];
377 for (i = 0; i < count; ++i) {
378 NSString *title = [buttonTitles objectAtIndex:i];
379 // NOTE: The title of the button may contain the character '&' to
380 // indicate that the following letter should be the key equivalent
381 // associated with the button. Extract this letter and lowercase it.
382 NSString *keyEquivalent = nil;
383 NSRange hotkeyRange = [title rangeOfString:@"&"];
384 if (NSNotFound != hotkeyRange.location) {
385 if ([title length] > NSMaxRange(hotkeyRange)) {
386 NSRange keyEquivRange = NSMakeRange(hotkeyRange.location+1, 1);
387 keyEquivalent = [[title substringWithRange:keyEquivRange]
391 NSMutableString *string = [NSMutableString stringWithString:title];
392 [string deleteCharactersInRange:hotkeyRange];
396 [alert addButtonWithTitle:title];
398 // Set key equivalent for the button, but only if NSAlert hasn't
399 // already done so. (Check the documentation for
400 // - [NSAlert addButtonWithTitle:] to see what key equivalents are
401 // automatically assigned.)
402 NSButton *btn = [[alert buttons] lastObject];
403 if ([[btn keyEquivalent] length] == 0 && keyEquivalent) {
404 [btn setKeyEquivalent:keyEquivalent];
408 [alert beginSheetModalForWindow:[windowController window]
410 didEndSelector:@selector(alertDidEnd:code:context:)
416 - (oneway void)processCommandQueue:(in NSArray *)queue
418 if (!isInitialized) return;
420 unsigned i, count = [queue count];
422 NSLog(@"WARNING: Uneven number of components (%d) in flush queue "
423 "message; ignoring this message.", count);
427 inProcessCommandQueue = YES;
429 //NSLog(@"======== %s BEGIN ========", _cmd);
430 for (i = 0; i < count; i += 2) {
431 NSData *value = [queue objectAtIndex:i];
432 NSData *data = [queue objectAtIndex:i+1];
434 int msgid = *((int*)[value bytes]);
436 if (msgid != EnableMenuItemMsgID && msgid != AddMenuItemMsgID
437 && msgid != AddMenuMsgID) {
438 NSLog(@"%s%s", _cmd, MessageStrings[msgid]);
442 [self handleMessage:msgid data:data];
444 //NSLog(@"======== %s END ========", _cmd);
446 if (shouldUpdateMainMenu) {
447 [self updateMainMenu];
450 [windowController processCommandQueueDidFinish];
452 inProcessCommandQueue = NO;
454 if ([sendQueue count] > 0) {
456 [backendProxy processInputAndData:sendQueue];
458 @catch (NSException *e) {
459 // Connection timed out, just ignore this.
460 //NSLog(@"WARNING! Connection timed out in %s", _cmd);
463 [sendQueue removeAllObjects];
467 - (void)windowDidBecomeMain:(NSNotification *)notification
470 [self updateMainMenu];
473 - (NSToolbarItem *)toolbar:(NSToolbar *)theToolbar
474 itemForItemIdentifier:(NSString *)itemId
475 willBeInsertedIntoToolbar:(BOOL)flag
477 NSToolbarItem *item = [toolbarItemDict objectForKey:itemId];
479 NSLog(@"WARNING: No toolbar item with id '%@'", itemId);
485 - (NSArray *)toolbarAllowedItemIdentifiers:(NSToolbar *)theToolbar
490 - (NSArray *)toolbarDefaultItemIdentifiers:(NSToolbar *)theToolbar
495 @end // MMVimController
499 @implementation MMVimController (Private)
501 - (void)handleMessage:(int)msgid data:(NSData *)data
503 //NSLog(@"%@ %s", [self className], _cmd);
505 if (OpenVimWindowMsgID == msgid) {
506 [windowController openWindow];
507 } else if (BatchDrawMsgID == msgid) {
508 [self performBatchDrawWithData:data];
509 } else if (SelectTabMsgID == msgid) {
510 #if 0 // NOTE: Tab selection is done inside updateTabsWithData:.
511 const void *bytes = [data bytes];
512 int idx = *((int*)bytes);
513 //NSLog(@"Selecting tab with index %d", idx);
514 [windowController selectTabWithIndex:idx];
516 } else if (UpdateTabBarMsgID == msgid) {
517 [windowController updateTabsWithData:data];
518 } else if (ShowTabBarMsgID == msgid) {
519 [windowController showTabBar:YES];
520 } else if (HideTabBarMsgID == msgid) {
521 [windowController showTabBar:NO];
522 } else if (SetTextDimensionsMsgID == msgid) {
523 const void *bytes = [data bytes];
524 int rows = *((int*)bytes); bytes += sizeof(int);
525 int cols = *((int*)bytes); bytes += sizeof(int);
527 [windowController setTextDimensionsWithRows:rows columns:cols];
528 } else if (SetVimWindowTitleMsgID == msgid) {
529 const void *bytes = [data bytes];
530 int len = *((int*)bytes); bytes += sizeof(int);
532 NSString *string = [[NSString alloc] initWithBytes:(void*)bytes
533 length:len encoding:NSUTF8StringEncoding];
535 [[windowController window] setTitle:string];
538 } else if (AddMenuMsgID == msgid) {
539 NSString *title = nil;
540 const void *bytes = [data bytes];
541 int tag = *((int*)bytes); bytes += sizeof(int);
542 int parentTag = *((int*)bytes); bytes += sizeof(int);
543 int len = *((int*)bytes); bytes += sizeof(int);
545 title = [[NSString alloc] initWithBytes:(void*)bytes length:len
546 encoding:NSUTF8StringEncoding];
549 int idx = *((int*)bytes); bytes += sizeof(int);
551 if (MenuToolbarType == parentTag) {
553 // NOTE! Each toolbar must have a unique identifier, else each
554 // window will have the same toolbar.
555 NSString *ident = [NSString stringWithFormat:@"%d.%d",
557 toolbar = [[NSToolbar alloc] initWithIdentifier:ident];
559 [toolbar setShowsBaselineSeparator:NO];
560 [toolbar setDelegate:self];
561 [toolbar setDisplayMode:NSToolbarDisplayModeIconOnly];
562 [toolbar setSizeMode:NSToolbarSizeModeSmall];
564 NSWindow *win = [windowController window];
565 [win setToolbar:toolbar];
567 // HACK! Redirect the pill button so that we can ask Vim to
569 NSButton *pillButton = [win
570 standardWindowButton:NSWindowToolbarButton];
572 [pillButton setAction:@selector(toggleToolbar:)];
573 [pillButton setTarget:windowController];
577 [self addMenuWithTag:tag parent:parentTag title:title atIndex:idx];
581 } else if (AddMenuItemMsgID == msgid) {
582 NSString *title = nil, *tip = nil, *icon = nil, *action = nil;
583 const void *bytes = [data bytes];
584 int tag = *((int*)bytes); bytes += sizeof(int);
585 int parentTag = *((int*)bytes); bytes += sizeof(int);
586 int namelen = *((int*)bytes); bytes += sizeof(int);
588 title = [[NSString alloc] initWithBytes:(void*)bytes length:namelen
589 encoding:NSUTF8StringEncoding];
592 int tiplen = *((int*)bytes); bytes += sizeof(int);
594 tip = [[NSString alloc] initWithBytes:(void*)bytes length:tiplen
595 encoding:NSUTF8StringEncoding];
598 int iconlen = *((int*)bytes); bytes += sizeof(int);
600 icon = [[NSString alloc] initWithBytes:(void*)bytes length:iconlen
601 encoding:NSUTF8StringEncoding];
604 int actionlen = *((int*)bytes); bytes += sizeof(int);
606 action = [[NSString alloc] initWithBytes:(void*)bytes
608 encoding:NSUTF8StringEncoding];
611 int idx = *((int*)bytes); bytes += sizeof(int);
612 if (idx < 0) idx = 0;
613 int key = *((int*)bytes); bytes += sizeof(int);
614 int mask = *((int*)bytes); bytes += sizeof(int);
616 NSString *ident = [NSString stringWithFormat:@"%d.%d",
617 (int)self, parentTag];
618 if (toolbar && [[toolbar identifier] isEqual:ident]) {
619 [self addToolbarItemWithTag:tag label:title tip:tip icon:icon
622 NSMenu *parent = [self menuForTag:parentTag];
623 [self addMenuItemWithTag:tag parent:parent title:title tip:tip
624 keyEquivalent:key modifiers:mask action:action
632 } else if (RemoveMenuItemMsgID == msgid) {
633 const void *bytes = [data bytes];
634 int tag = *((int*)bytes); bytes += sizeof(int);
638 if ((item = [self toolbarItemForTag:tag index:&idx])) {
639 [toolbar removeItemAtIndex:idx];
640 } else if ((item = [self menuItemForTag:tag])) {
643 if ([item menu] == [NSApp mainMenu] || ![item menu]) {
644 // NOTE: To be on the safe side we try to remove the item from
645 // both arrays (it is ok to call removeObject: even if an array
646 // does not contain the object to remove).
647 [mainMenuItems removeObject:item];
648 [popupMenuItems removeObject:item];
652 [[item menu] removeItem:item];
656 } else if (EnableMenuItemMsgID == msgid) {
657 const void *bytes = [data bytes];
658 int tag = *((int*)bytes); bytes += sizeof(int);
659 int state = *((int*)bytes); bytes += sizeof(int);
661 id item = [self toolbarItemForTag:tag index:NULL];
663 item = [self menuItemForTag:tag];
665 [item setEnabled:state];
666 } else if (ShowToolbarMsgID == msgid) {
667 const void *bytes = [data bytes];
668 int enable = *((int*)bytes); bytes += sizeof(int);
669 int flags = *((int*)bytes); bytes += sizeof(int);
671 int mode = NSToolbarDisplayModeDefault;
672 if (flags & ToolbarLabelFlag) {
673 mode = flags & ToolbarIconFlag ? NSToolbarDisplayModeIconAndLabel
674 : NSToolbarDisplayModeLabelOnly;
675 } else if (flags & ToolbarIconFlag) {
676 mode = NSToolbarDisplayModeIconOnly;
679 int size = flags & ToolbarSizeRegularFlag ? NSToolbarSizeModeRegular
680 : NSToolbarSizeModeSmall;
682 [windowController showToolbar:enable size:size mode:mode];
683 } else if (CreateScrollbarMsgID == msgid) {
684 const void *bytes = [data bytes];
685 long ident = *((long*)bytes); bytes += sizeof(long);
686 int type = *((int*)bytes); bytes += sizeof(int);
688 [windowController createScrollbarWithIdentifier:ident type:type];
689 } else if (DestroyScrollbarMsgID == msgid) {
690 const void *bytes = [data bytes];
691 long ident = *((long*)bytes); bytes += sizeof(long);
693 [windowController destroyScrollbarWithIdentifier:ident];
694 } else if (ShowScrollbarMsgID == msgid) {
695 const void *bytes = [data bytes];
696 long ident = *((long*)bytes); bytes += sizeof(long);
697 int visible = *((int*)bytes); bytes += sizeof(int);
699 [windowController showScrollbarWithIdentifier:ident state:visible];
700 } else if (SetScrollbarPositionMsgID == msgid) {
701 const void *bytes = [data bytes];
702 long ident = *((long*)bytes); bytes += sizeof(long);
703 int pos = *((int*)bytes); bytes += sizeof(int);
704 int len = *((int*)bytes); bytes += sizeof(int);
706 [windowController setScrollbarPosition:pos length:len
708 } else if (SetScrollbarThumbMsgID == msgid) {
709 const void *bytes = [data bytes];
710 long ident = *((long*)bytes); bytes += sizeof(long);
711 float val = *((float*)bytes); bytes += sizeof(float);
712 float prop = *((float*)bytes); bytes += sizeof(float);
714 [windowController setScrollbarThumbValue:val proportion:prop
716 } else if (SetFontMsgID == msgid) {
717 const void *bytes = [data bytes];
718 float size = *((float*)bytes); bytes += sizeof(float);
719 int len = *((int*)bytes); bytes += sizeof(int);
720 NSString *name = [[NSString alloc]
721 initWithBytes:(void*)bytes length:len
722 encoding:NSUTF8StringEncoding];
723 NSFont *font = [NSFont fontWithName:name size:size];
726 [windowController setFont:font];
729 } else if (SetDefaultColorsMsgID == msgid) {
730 const void *bytes = [data bytes];
731 int bg = *((int*)bytes); bytes += sizeof(int);
732 int fg = *((int*)bytes); bytes += sizeof(int);
733 NSColor *back = [NSColor colorWithRgbInt:bg];
734 NSColor *fore = [NSColor colorWithRgbInt:fg];
736 [windowController setDefaultColorsBackground:back foreground:fore];
737 } else if (ExecuteActionMsgID == msgid) {
738 const void *bytes = [data bytes];
739 int len = *((int*)bytes); bytes += sizeof(int);
740 NSString *actionName = [[NSString alloc]
741 initWithBytesNoCopy:(void*)bytes
743 encoding:NSUTF8StringEncoding
746 SEL sel = NSSelectorFromString(actionName);
747 [NSApp sendAction:sel to:nil from:self];
749 [actionName release];
750 } else if (ShowPopupMenuMsgID == msgid) {
751 const void *bytes = [data bytes];
752 int row = *((int*)bytes); bytes += sizeof(int);
753 int col = *((int*)bytes); bytes += sizeof(int);
754 int len = *((int*)bytes); bytes += sizeof(int);
755 NSString *title = [[NSString alloc]
756 initWithBytesNoCopy:(void*)bytes
758 encoding:NSUTF8StringEncoding
761 NSMenu *menu = [self topLevelMenuForTitle:title];
763 [windowController popupMenu:menu atRow:row column:col];
765 NSLog(@"WARNING: Cannot popup menu with title %@; no such menu.",
770 } else if (SetMouseShapeMsgID == msgid) {
771 const void *bytes = [data bytes];
772 int shape = *((int*)bytes); bytes += sizeof(int);
774 [windowController setMouseShape:shape];
775 } else if (AdjustLinespaceMsgID == msgid) {
776 const void *bytes = [data bytes];
777 int linespace = *((int*)bytes); bytes += sizeof(int);
779 [windowController adjustLinespace:linespace];
780 } else if (ActivateMsgID == msgid) {
781 [NSApp activateIgnoringOtherApps:YES];
782 [[windowController window] makeKeyAndOrderFront:self];
783 } else if (SetServerNameMsgID == msgid) {
784 NSString *name = [[NSString alloc] initWithData:data
785 encoding:NSUTF8StringEncoding];
786 [self setServerName:name];
789 NSLog(@"WARNING: Unknown message received (msgid=%d)", msgid);
794 #define MM_DEBUG_DRAWING 0
796 - (void)performBatchDrawWithData:(NSData *)data
798 // TODO! Move to window controller.
799 MMTextStorage *textStorage = [windowController textStorage];
800 MMTextView *textView = [windowController textView];
801 if (!(textStorage && textView))
804 const void *bytes = [data bytes];
805 const void *end = bytes + [data length];
808 NSLog(@"====> BEGIN %s", _cmd);
810 [textStorage beginEditing];
812 // TODO: Sanity check input
814 while (bytes < end) {
815 int type = *((int*)bytes); bytes += sizeof(int);
817 if (ClearAllDrawType == type) {
818 int color = *((int*)bytes); bytes += sizeof(int);
821 NSLog(@" Clear all");
823 [textStorage clearAllWithColor:[NSColor colorWithRgbInt:color]];
824 } else if (ClearBlockDrawType == type) {
825 int color = *((int*)bytes); bytes += sizeof(int);
826 int row1 = *((int*)bytes); bytes += sizeof(int);
827 int col1 = *((int*)bytes); bytes += sizeof(int);
828 int row2 = *((int*)bytes); bytes += sizeof(int);
829 int col2 = *((int*)bytes); bytes += sizeof(int);
832 NSLog(@" Clear block (%d,%d) -> (%d,%d)", row1, col1,
835 [textStorage clearBlockFromRow:row1 column:col1
836 toRow:row2 column:col2
837 color:[NSColor colorWithRgbInt:color]];
838 } else if (DeleteLinesDrawType == type) {
839 int color = *((int*)bytes); bytes += sizeof(int);
840 int row = *((int*)bytes); bytes += sizeof(int);
841 int count = *((int*)bytes); bytes += sizeof(int);
842 int bot = *((int*)bytes); bytes += sizeof(int);
843 int left = *((int*)bytes); bytes += sizeof(int);
844 int right = *((int*)bytes); bytes += sizeof(int);
847 NSLog(@" Delete %d line(s) from %d", count, row);
849 [textStorage deleteLinesFromRow:row lineCount:count
850 scrollBottom:bot left:left right:right
851 color:[NSColor colorWithRgbInt:color]];
852 } else if (ReplaceStringDrawType == type) {
853 int bg = *((int*)bytes); bytes += sizeof(int);
854 int fg = *((int*)bytes); bytes += sizeof(int);
855 int sp = *((int*)bytes); bytes += sizeof(int);
856 int row = *((int*)bytes); bytes += sizeof(int);
857 int col = *((int*)bytes); bytes += sizeof(int);
858 int flags = *((int*)bytes); bytes += sizeof(int);
859 int len = *((int*)bytes); bytes += sizeof(int);
860 NSString *string = [[NSString alloc]
861 initWithBytesNoCopy:(void*)bytes
863 encoding:NSUTF8StringEncoding
868 NSLog(@" Draw string at (%d,%d) length=%d flags=%d fg=0x%x "
869 "bg=0x%x sp=0x%x (%@)", row, col, len, flags, fg, bg, sp,
870 len > 0 ? [string substringToIndex:1] : @"");
872 // NOTE: If this is a call to draw the (block) cursor, then cancel
873 // any previous request to draw the insertion point, or it might
874 // get drawn as well.
875 if (flags & DRAW_CURSOR) {
876 [textView setShouldDrawInsertionPoint:NO];
877 //[textView drawInsertionPointAtRow:row column:col
878 // shape:MMInsertionPointBlock
879 // color:[NSColor colorWithRgbInt:bg]];
881 [textStorage replaceString:string
884 foregroundColor:[NSColor colorWithRgbInt:fg]
885 backgroundColor:[NSColor colorWithRgbInt:bg]
886 specialColor:[NSColor colorWithRgbInt:sp]];
889 } else if (InsertLinesDrawType == type) {
890 int color = *((int*)bytes); bytes += sizeof(int);
891 int row = *((int*)bytes); bytes += sizeof(int);
892 int count = *((int*)bytes); bytes += sizeof(int);
893 int bot = *((int*)bytes); bytes += sizeof(int);
894 int left = *((int*)bytes); bytes += sizeof(int);
895 int right = *((int*)bytes); bytes += sizeof(int);
898 NSLog(@" Insert %d line(s) at row %d", count, row);
900 [textStorage insertLinesAtRow:row lineCount:count
901 scrollBottom:bot left:left right:right
902 color:[NSColor colorWithRgbInt:color]];
903 } else if (DrawCursorDrawType == type) {
904 int color = *((int*)bytes); bytes += sizeof(int);
905 int row = *((int*)bytes); bytes += sizeof(int);
906 int col = *((int*)bytes); bytes += sizeof(int);
907 int shape = *((int*)bytes); bytes += sizeof(int);
908 int percent = *((int*)bytes); bytes += sizeof(int);
911 NSLog(@" Draw cursor at (%d,%d)", row, col);
913 [textView drawInsertionPointAtRow:row column:col shape:shape
915 color:[NSColor colorWithRgbInt:color]];
917 NSLog(@"WARNING: Unknown draw type (type=%d)", type);
921 [textStorage endEditing];
923 NSLog(@"<==== END %s", _cmd);
927 - (void)savePanelDidEnd:(NSSavePanel *)panel code:(int)code
928 context:(void *)context
930 NSString *string = (code == NSOKButton) ? [panel filename] : nil;
932 [backendProxy setDialogReturn:string];
934 @catch (NSException *e) {
935 NSLog(@"Exception caught in %s %@", _cmd, e);
939 - (void)alertDidEnd:(MMAlert *)alert code:(int)code context:(void *)context
943 code = code - NSAlertFirstButtonReturn + 1;
945 if ([alert isKindOfClass:[MMAlert class]] && [alert textField]) {
946 ret = [NSArray arrayWithObjects:[NSNumber numberWithInt:code],
947 [[alert textField] stringValue], nil];
949 ret = [NSArray arrayWithObject:[NSNumber numberWithInt:code]];
953 [backendProxy setDialogReturn:ret];
955 @catch (NSException *e) {
956 NSLog(@"Exception caught in %s %@", _cmd, e);
960 - (NSMenuItem *)menuItemForTag:(int)tag
962 // Search the main menu.
963 int i, count = [mainMenuItems count];
964 for (i = 0; i < count; ++i) {
965 NSMenuItem *item = [mainMenuItems objectAtIndex:i];
966 if ([item tag] == tag) return item;
967 item = findMenuItemWithTagInMenu([item submenu], tag);
968 if (item) return item;
971 // Search the popup menus.
972 count = [popupMenuItems count];
973 for (i = 0; i < count; ++i) {
974 NSMenuItem *item = [popupMenuItems objectAtIndex:i];
975 if ([item tag] == tag) return item;
976 item = findMenuItemWithTagInMenu([item submenu], tag);
977 if (item) return item;
983 - (NSMenu *)menuForTag:(int)tag
985 return [[self menuItemForTag:tag] submenu];
988 - (NSMenu *)topLevelMenuForTitle:(NSString *)title
990 // Search only the top-level menus.
992 unsigned i, count = [popupMenuItems count];
993 for (i = 0; i < count; ++i) {
994 NSMenuItem *item = [popupMenuItems objectAtIndex:i];
995 if ([title isEqual:[item title]])
996 return [item submenu];
999 count = [mainMenuItems count];
1000 for (i = 0; i < count; ++i) {
1001 NSMenuItem *item = [mainMenuItems objectAtIndex:i];
1002 if ([title isEqual:[item title]])
1003 return [item submenu];
1009 - (void)addMenuWithTag:(int)tag parent:(int)parentTag title:(NSString *)title
1012 NSMenu *parent = [self menuForTag:parentTag];
1013 NSMenuItem *item = [[NSMenuItem alloc] init];
1014 NSMenu *menu = [[NSMenu alloc] initWithTitle:title];
1016 [menu setAutoenablesItems:NO];
1018 [item setTitle:title];
1019 [item setSubmenu:menu];
1022 if ([parent numberOfItems] <= idx) {
1023 [parent addItem:item];
1025 [parent insertItem:item atIndex:idx];
1028 NSMutableArray *items = (MenuPopupType == parentTag)
1029 ? popupMenuItems : mainMenuItems;
1030 if ([items count] <= idx) {
1031 [items addObject:item];
1033 [items insertObject:item atIndex:idx];
1036 shouldUpdateMainMenu = (MenuPopupType != parentTag);
1043 - (void)addMenuItemWithTag:(int)tag parent:(NSMenu *)parent
1044 title:(NSString *)title tip:(NSString *)tip
1045 keyEquivalent:(int)key modifiers:(int)mask
1046 action:(NSString *)action atIndex:(int)idx
1049 NSMenuItem *item = nil;
1050 if (!title || ([title hasPrefix:@"-"] && [title hasSuffix:@"-"])) {
1051 item = [NSMenuItem separatorItem];
1053 item = [[[NSMenuItem alloc] init] autorelease];
1054 [item setTitle:title];
1055 // TODO: Check that 'action' is a valid action (nothing will happen
1056 // if it isn't, but it would be nice with a warning).
1057 if (action) [item setAction:NSSelectorFromString(action)];
1058 else [item setAction:@selector(vimMenuItemAction:)];
1059 if (tip) [item setToolTip:tip];
1062 NSString *keyString =
1063 [NSString stringWithFormat:@"%C", key];
1064 [item setKeyEquivalent:keyString];
1065 [item setKeyEquivalentModifierMask:mask];
1069 // NOTE! The tag is used to idenfity which menu items were
1070 // added by Vim (tag != 0) and which were added by the AppKit
1074 if ([parent numberOfItems] <= idx) {
1075 [parent addItem:item];
1077 [parent insertItem:item atIndex:idx];
1080 NSLog(@"WARNING: Menu item '%@' (tag=%d) has no parent.", title, tag);
1084 - (void)updateMainMenu
1086 NSMenu *mainMenu = [NSApp mainMenu];
1088 // Stop NSApp from updating the Window menu.
1089 [NSApp setWindowsMenu:nil];
1091 // Remove all menus from main menu (except the MacVim menu).
1092 int i, count = [mainMenu numberOfItems];
1093 for (i = count-1; i > 0; --i) {
1094 [mainMenu removeItemAtIndex:i];
1097 // Add menus from 'mainMenuItems' to main menu.
1098 count = [mainMenuItems count];
1099 for (i = 0; i < count; ++i) {
1100 [mainMenu addItem:[mainMenuItems objectAtIndex:i]];
1103 // Set the new Window menu.
1104 // TODO! Need to look for 'Window' in all localized languages.
1105 NSMenu *windowMenu = [[mainMenu itemWithTitle:@"Window"] submenu];
1107 // Remove all AppKit owned menu items (tag == 0); they will be added
1108 // again when setWindowsMenu: is called.
1109 count = [windowMenu numberOfItems];
1110 for (i = count-1; i >= 0; --i) {
1111 NSMenuItem *item = [windowMenu itemAtIndex:i];
1113 [windowMenu removeItem:item];
1117 [NSApp setWindowsMenu:windowMenu];
1120 shouldUpdateMainMenu = NO;
1123 - (NSToolbarItem *)toolbarItemForTag:(int)tag index:(int *)index
1125 if (!toolbar) return nil;
1127 NSArray *items = [toolbar items];
1128 int i, count = [items count];
1129 for (i = 0; i < count; ++i) {
1130 NSToolbarItem *item = [items objectAtIndex:i];
1131 if ([item tag] == tag) {
1132 if (index) *index = i;
1140 - (void)addToolbarItemToDictionaryWithTag:(int)tag label:(NSString *)title
1141 toolTip:(NSString *)tip icon:(NSString *)icon
1143 // If the item corresponds to a separator then do nothing, since it is
1144 // already defined by Cocoa.
1145 if (!title || [title isEqual:NSToolbarSeparatorItemIdentifier]
1146 || [title isEqual:NSToolbarSpaceItemIdentifier]
1147 || [title isEqual:NSToolbarFlexibleSpaceItemIdentifier])
1150 NSToolbarItem *item = [[NSToolbarItem alloc] initWithItemIdentifier:title];
1152 [item setLabel:title];
1153 [item setToolTip:tip];
1154 [item setAction:@selector(vimMenuItemAction:)];
1155 [item setAutovalidates:NO];
1157 NSImage *img = [NSImage imageNamed:icon];
1159 NSLog(@"WARNING: Could not find image with name '%@' to use as toolbar"
1160 " image for identifier '%@';"
1161 " using default toolbar icon '%@' instead.",
1162 icon, title, MMDefaultToolbarImageName);
1164 img = [NSImage imageNamed:MMDefaultToolbarImageName];
1167 [item setImage:img];
1169 [toolbarItemDict setObject:item forKey:title];
1174 - (void)addToolbarItemWithTag:(int)tag label:(NSString *)label tip:(NSString
1175 *)tip icon:(NSString *)icon atIndex:(int)idx
1177 if (!toolbar) return;
1179 // Check for separator items.
1181 label = NSToolbarSeparatorItemIdentifier;
1182 } else if ([label length] >= 2 && [label hasPrefix:@"-"]
1183 && [label hasSuffix:@"-"]) {
1184 // The label begins and ends with '-'; decided which kind of separator
1185 // item it is by looking at the prefix.
1186 if ([label hasPrefix:@"-space"]) {
1187 label = NSToolbarSpaceItemIdentifier;
1188 } else if ([label hasPrefix:@"-flexspace"]) {
1189 label = NSToolbarFlexibleSpaceItemIdentifier;
1191 label = NSToolbarSeparatorItemIdentifier;
1195 [self addToolbarItemToDictionaryWithTag:tag label:label toolTip:tip
1198 int maxIdx = [[toolbar items] count];
1199 if (maxIdx < idx) idx = maxIdx;
1201 [toolbar insertItemWithItemIdentifier:label atIndex:idx];
1204 - (void)connectionDidDie:(NSNotification *)notification
1206 //NSLog(@"%@ %s%@", [self className], _cmd, notification);
1210 // NOTE! This causes the call to removeVimController: to be delayed.
1212 performSelectorOnMainThread:@selector(removeVimController:)
1213 withObject:self waitUntilDone:NO];
1216 - (NSString *)description
1218 return [NSString stringWithFormat:@"%@ : isInitialized=%d inProcessCommandQueue=%d mainMenuItems=%@ popupMenuItems=%@ toolbar=%@", [self className], isInitialized, inProcessCommandQueue, mainMenuItems, popupMenuItems, toolbar];
1221 #if MM_RESEND_LAST_FAILURE
1222 - (void)resendTimerFired:(NSTimer *)timer
1224 int msgid = resendMsgid;
1227 [resendTimer release];
1234 data = [resendData copy];
1236 //NSLog(@"Resending message: %s", MessageStrings[msgid]);
1237 [self sendMessage:msgid data:data];
1241 @end // MMVimController (Private)
1245 @implementation NSColor (MMProtocol)
1247 + (NSColor *)colorWithRgbInt:(int)rgb
1249 float r = ((rgb>>16) & 0xff)/255.0f;
1250 float g = ((rgb>>8) & 0xff)/255.0f;
1251 float b = (rgb & 0xff)/255.0f;
1253 return [NSColor colorWithCalibratedRed:r green:g blue:b alpha:1.0f];
1256 @end // NSColor (MMProtocol)
1260 @implementation MMAlert
1263 [textField release];
1267 - (void)setTextFieldString:(NSString *)textFieldString
1269 [textField release];
1270 textField = [[NSTextField alloc] init];
1271 [textField setStringValue:textFieldString];
1274 - (NSTextField *)textField
1279 - (void)setInformativeText:(NSString *)text
1282 // HACK! Add some space for the text field.
1283 [super setInformativeText:[text stringByAppendingString:@"\n\n\n"]];
1285 [super setInformativeText:text];
1289 - (void)beginSheetModalForWindow:(NSWindow *)window
1290 modalDelegate:(id)delegate
1291 didEndSelector:(SEL)didEndSelector
1292 contextInfo:(void *)contextInfo
1294 [super beginSheetModalForWindow:window
1295 modalDelegate:delegate
1296 didEndSelector:didEndSelector
1297 contextInfo:contextInfo];
1299 // HACK! Place the input text field at the bottom of the informative text
1300 // (which has been made a bit larger by adding newline characters).
1301 NSView *contentView = [[self window] contentView];
1302 NSRect rect = [contentView frame];
1303 rect.origin.y = rect.size.height;
1305 NSArray *subviews = [contentView subviews];
1306 unsigned i, count = [subviews count];
1307 for (i = 0; i < count; ++i) {
1308 NSView *view = [subviews objectAtIndex:i];
1309 if ([view isKindOfClass:[NSTextField class]]
1310 && [view frame].origin.y < rect.origin.y) {
1311 // NOTE: The informative text field is the lowest NSTextField in
1312 // the alert dialog.
1313 rect = [view frame];
1317 rect.size.height = MMAlertTextFieldHeight;
1318 [textField setFrame:rect];
1319 [contentView addSubview:textField];
1320 [textField becomeFirstResponder];