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.
16 static float MMFlushTimeoutInterval = 0.1f;
19 // TODO: Move to separate file.
20 static int eventModifierFlagsToVimModMask(int modifierFlags);
21 static int vimModMaskToEventModifierFlags(int mods);
22 static int eventModifierFlagsToVimMouseModMask(int modifierFlags);
23 static int eventButtonNumberToVimMouseButton(int buttonNumber);
24 static int specialKeyToNSKey(int key);
27 @interface MMBackend (Private)
28 - (void)handleMessage:(int)msgid data:(NSData *)data;
29 + (NSDictionary *)specialKeys;
30 - (void)handleKeyDown:(NSString *)key modifiers:(int)mods;
31 - (void)queueMessage:(int)msgid data:(NSData *)data;
32 - (void)connectionDidDie:(NSNotification *)notification;
37 @implementation MMBackend
39 + (MMBackend *)sharedInstance
41 static MMBackend *singleton = nil;
42 return singleton ? singleton : (singleton = [MMBackend new]);
47 if ((self = [super init])) {
48 queue = [[NSMutableArray alloc] init];
49 drawData = [[NSMutableData alloc] initWithCapacity:1024];
50 NSString *path = [[NSBundle mainBundle] pathForResource:@"Colors"
52 colorDict = [[NSDictionary dictionaryWithContentsOfFile:path] retain];
60 [[NSNotificationCenter defaultCenter] removeObserver:self];
64 [frontendProxy release];
71 - (void)setBackgroundColor:(int)color
73 backgroundColor = color;
76 - (void)setForegroundColor:(int)color
78 foregroundColor = color;
81 - (void)setDefaultColorsBackground:(int)bg foreground:(int)fg
83 defaultBackgroundColor = bg;
84 defaultForegroundColor = fg;
86 NSMutableData *data = [NSMutableData data];
88 [data appendBytes:&bg length:sizeof(int)];
89 [data appendBytes:&fg length:sizeof(int)];
91 [self queueMessage:SetDefaultColorsMsgID data:data];
96 NSBundle *mainBundle = [NSBundle mainBundle];
98 // NOTE! If the name of the connection changes here it must also be
99 // updated in MMAppController.m.
100 NSString *name = [NSString stringWithFormat:@"%@-connection",
101 [mainBundle bundleIdentifier]];
102 connection = [NSConnection connectionWithRegisteredName:name host:nil];
105 NSString *path = [mainBundle bundlePath];
106 if (![[NSWorkspace sharedWorkspace] launchApplication:path]) {
107 NSLog(@"WARNING: Failed to launch GUI with path %@", path);
111 // HACK! It would be preferable to launch the GUI using NSWorkspace,
112 // however I have not managed to figure out how to pass arguments using
115 // NOTE! Using NSTask to launch the GUI has the negative side-effect
116 // that the GUI won't be activated (or raised) so there is a hack in
117 // MMWindowController which always raises the app when a new window is
119 NSMutableArray *args = [NSMutableArray arrayWithObjects:
120 [NSString stringWithFormat:@"-%@", MMNoWindowKey], @"yes", nil];
121 NSString *exeName = [[mainBundle infoDictionary]
122 objectForKey:@"CFBundleExecutable"];
123 NSString *path = [mainBundle pathForAuxiliaryExecutable:exeName];
125 NSLog(@"ERROR: Could not find MacVim executable in bundle");
129 [NSTask launchedTaskWithLaunchPath:path arguments:args];
132 // HACK! The NSWorkspaceDidLaunchApplicationNotification does not work
133 // for tasks like this, so poll the mach bootstrap server until it
134 // returns a valid connection. Also set a time-out date so that we
135 // don't get stuck doing this forever.
136 NSDate *timeOutDate = [NSDate dateWithTimeIntervalSinceNow:15];
137 while (!connection &&
138 NSOrderedDescending == [timeOutDate compare:[NSDate date]])
140 [[NSRunLoop currentRunLoop]
141 runMode:NSDefaultRunLoopMode
142 beforeDate:[NSDate dateWithTimeIntervalSinceNow:1]];
144 connection = [NSConnection connectionWithRegisteredName:name
149 NSLog(@"WARNING: Timed-out waiting for GUI to launch.");
154 id proxy = [connection rootProxy];
155 [proxy setProtocolForProxy:@protocol(MMAppProtocol)];
157 [[NSNotificationCenter defaultCenter] addObserver:self
158 selector:@selector(connectionDidDie:)
159 name:NSConnectionDidDieNotification object:connection];
161 frontendProxy = [(NSDistantObject*)[proxy connectBackend:self] retain];
163 [frontendProxy setProtocolForProxy:@protocol(MMAppProtocol)];
166 return connection && frontendProxy;
169 - (BOOL)openVimWindow
171 [self queueMessage:OpenVimWindowMsgID data:nil];
177 int type = ClearAllDrawType;
179 // Any draw commands in queue are effectively obsolete since this clearAll
180 // will negate any effect they have, therefore we may as well clear the
182 [drawData setLength:0];
184 [drawData appendBytes:&type length:sizeof(int)];
186 [drawData appendBytes:&defaultBackgroundColor length:sizeof(int)];
189 - (void)clearBlockFromRow:(int)row1 column:(int)col1
190 toRow:(int)row2 column:(int)col2
192 int type = ClearBlockDrawType;
194 [drawData appendBytes:&type length:sizeof(int)];
196 [drawData appendBytes:&defaultBackgroundColor length:sizeof(int)];
197 [drawData appendBytes:&row1 length:sizeof(int)];
198 [drawData appendBytes:&col1 length:sizeof(int)];
199 [drawData appendBytes:&row2 length:sizeof(int)];
200 [drawData appendBytes:&col2 length:sizeof(int)];
203 - (void)deleteLinesFromRow:(int)row count:(int)count
204 scrollBottom:(int)bottom left:(int)left right:(int)right
206 int type = DeleteLinesDrawType;
208 [drawData appendBytes:&type length:sizeof(int)];
210 [drawData appendBytes:&defaultBackgroundColor length:sizeof(int)];
211 [drawData appendBytes:&row length:sizeof(int)];
212 [drawData appendBytes:&count length:sizeof(int)];
213 [drawData appendBytes:&bottom length:sizeof(int)];
214 [drawData appendBytes:&left length:sizeof(int)];
215 [drawData appendBytes:&right length:sizeof(int)];
218 - (void)replaceString:(char*)s length:(int)len row:(int)row column:(int)col
221 int type = ReplaceStringDrawType;
223 [drawData appendBytes:&type length:sizeof(int)];
225 [drawData appendBytes:&backgroundColor length:sizeof(int)];
226 [drawData appendBytes:&foregroundColor length:sizeof(int)];
227 [drawData appendBytes:&row length:sizeof(int)];
228 [drawData appendBytes:&col length:sizeof(int)];
229 [drawData appendBytes:&flags length:sizeof(int)];
230 [drawData appendBytes:&len length:sizeof(int)];
231 [drawData appendBytes:s length:len];
234 - (void)insertLinesFromRow:(int)row count:(int)count
235 scrollBottom:(int)bottom left:(int)left right:(int)right
237 int type = InsertLinesDrawType;
239 [drawData appendBytes:&type length:sizeof(int)];
241 [drawData appendBytes:&defaultBackgroundColor length:sizeof(int)];
242 [drawData appendBytes:&row length:sizeof(int)];
243 [drawData appendBytes:&count length:sizeof(int)];
244 [drawData appendBytes:&bottom length:sizeof(int)];
245 [drawData appendBytes:&left length:sizeof(int)];
246 [drawData appendBytes:&right length:sizeof(int)];
249 - (void)flushQueue:(BOOL)force
251 // NOTE! This method gets called a lot; if we were to flush every time it
252 // was called MacVim would feel unresponsive. So there is a time out which
253 // ensures that the queue isn't flushed too often.
254 if (!force && lastFlushDate && -[lastFlushDate timeIntervalSinceNow]
255 < MMFlushTimeoutInterval)
258 if ([drawData length] > 0) {
259 [self queueMessage:BatchDrawMsgID data:[drawData copy]];
260 [drawData setLength:0];
263 if ([queue count] > 0) {
264 // TODO: Come up with a better way to handle the insertion point.
265 [self updateInsertionPoint];
267 [frontendProxy processCommandQueue:queue];
268 [queue removeAllObjects];
270 [lastFlushDate release];
271 lastFlushDate = [[NSDate date] retain];
275 - (BOOL)waitForInput:(int)milliseconds
277 NSDate *date = milliseconds > 0 ?
278 [NSDate dateWithTimeIntervalSinceNow:.001*milliseconds] :
279 [NSDate distantFuture];
281 [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:date];
283 // I know of no way to figure out if the run loop exited because input was
284 // found or because of a time out, so I need to manually indicate when
285 // input was received in processInput:data: and then reset it every time
287 BOOL yn = inputReceived;
295 // By invalidating the NSConnection the MMWindowController immediately
296 // finds out that the connection is down and as a result
297 // [MMWindowController connectionDidDie:] is invoked.
298 [[NSNotificationCenter defaultCenter] removeObserver:self];
299 [connection invalidate];
302 - (void)selectTab:(int)index
304 //NSLog(@"%s%d", _cmd, index);
307 NSData *data = [NSData dataWithBytes:&index length:sizeof(int)];
308 [self queueMessage:SelectTabMsgID data:data];
313 //NSLog(@"%s", _cmd);
315 NSMutableData *data = [NSMutableData data];
317 int idx = tabpage_index(curtab) - 1;
318 [data appendBytes:&idx length:sizeof(int)];
321 for (tp = first_tabpage; tp != NULL; tp = tp->tp_next) {
322 // This function puts the label of the tab in the global 'NameBuff'.
323 get_tabline_label(tp, FALSE);
324 int len = strlen((char*)NameBuff);
326 // Count the number of windows in the tabpage.
327 //win_T *wp = tp->tp_firstwin;
329 //for (wincount = 0; wp != NULL; wp = wp->w_next, ++wincount);
331 //[data appendBytes:&wincount length:sizeof(int)];
332 [data appendBytes:&len length:sizeof(int)];
333 [data appendBytes:NameBuff length:len];
336 [self queueMessage:UpdateTabBarMsgID data:data];
339 - (BOOL)tabBarVisible
341 return tabBarVisible;
344 - (void)showTabBar:(BOOL)enable
346 tabBarVisible = enable;
348 int msgid = enable ? ShowTabBarMsgID : HideTabBarMsgID;
349 [self queueMessage:msgid data:nil];
352 - (void)setRows:(int)rows columns:(int)cols
354 //NSLog(@"[VimTask] setRows:%d columns:%d", rows, cols);
356 int dim[] = { rows, cols };
357 NSData *data = [NSData dataWithBytes:&dim length:2*sizeof(int)];
359 [self queueMessage:SetTextDimensionsMsgID data:data];
362 - (void)setVimWindowTitle:(char *)title
364 NSMutableData *data = [NSMutableData data];
365 int len = strlen(title);
367 [data appendBytes:&len length:sizeof(int)];
368 [data appendBytes:title length:len];
370 [self queueMessage:SetVimWindowTitleMsgID data:data];
373 - (oneway void)setBrowseForFileString:(in bycopy NSString *)string
375 // NOTE: This is called by [MMVimController panelDidEnd:::] to indicate
376 // that the save/open panel has finished. If 'string == nil' that means
377 // the user pressed cancel.
378 browseForFileString = string ? [string copy] : nil;
381 - (char *)browseForFileInDirectory:(char *)dir title:(char *)title
384 //NSLog(@"browseForFileInDirectory:%s title:%s saving:%d", dir, title,
388 ? [NSString stringWithCString:dir encoding:NSUTF8StringEncoding]
391 ? [NSString stringWithCString:title encoding:NSUTF8StringEncoding]
393 [frontendProxy showSavePanelForDirectory:ds title:ts saving:saving];
395 // Wait until a reply is sent from MMVimController.
396 [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
397 beforeDate:[NSDate distantFuture]];
398 if (!browseForFileString)
401 char_u *s = vim_strsave((char_u*)[browseForFileString UTF8String]);
402 [browseForFileString release]; browseForFileString = nil;
407 - (void)updateInsertionPoint
409 NSMutableData *data = [NSMutableData data];
411 int state = get_shape_idx(FALSE);
412 state = (state == SHAPE_IDX_I) || (state == SHAPE_IDX_CI);
414 [data appendBytes:&defaultForegroundColor length:sizeof(int)];
415 [data appendBytes:&gui.row length:sizeof(int)];
416 [data appendBytes:&gui.col length:sizeof(int)];
417 [data appendBytes:&state length:sizeof(int)];
419 [self queueMessage:UpdateInsertionPointMsgID data:data];
422 - (void)addMenuWithTag:(int)tag parent:(int)parentTag name:(char *)name
425 //NSLog(@"addMenuWithTag:%d parent:%d name:%s atIndex:%d", tag, parentTag,
428 int namelen = name ? strlen(name) : 0;
429 NSMutableData *data = [NSMutableData data];
431 [data appendBytes:&tag length:sizeof(int)];
432 [data appendBytes:&parentTag length:sizeof(int)];
433 [data appendBytes:&namelen length:sizeof(int)];
434 if (namelen > 0) [data appendBytes:name length:namelen];
435 [data appendBytes:&index length:sizeof(int)];
437 [self queueMessage:AddMenuMsgID data:data];
440 - (void)addMenuItemWithTag:(int)tag parent:(int)parentTag name:(char *)name
441 tip:(char *)tip icon:(char *)icon
442 keyEquivalent:(int)key modifiers:(int)mods
443 action:(NSString *)action atIndex:(int)index
445 //NSLog(@"addMenuItemWithTag:%d parent:%d name:%s tip:%s atIndex:%d", tag,
446 // parentTag, name, tip, index);
448 int namelen = name ? strlen(name) : 0;
449 int tiplen = tip ? strlen(tip) : 0;
450 int iconlen = icon ? strlen(icon) : 0;
451 int eventFlags = vimModMaskToEventModifierFlags(mods);
452 int actionlen = [action lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
453 NSMutableData *data = [NSMutableData data];
455 key = specialKeyToNSKey(key);
457 [data appendBytes:&tag length:sizeof(int)];
458 [data appendBytes:&parentTag length:sizeof(int)];
459 [data appendBytes:&namelen length:sizeof(int)];
460 if (namelen > 0) [data appendBytes:name length:namelen];
461 [data appendBytes:&tiplen length:sizeof(int)];
462 if (tiplen > 0) [data appendBytes:tip length:tiplen];
463 [data appendBytes:&iconlen length:sizeof(int)];
464 if (iconlen > 0) [data appendBytes:icon length:iconlen];
465 [data appendBytes:&actionlen length:sizeof(int)];
466 if (actionlen > 0) [data appendBytes:[action UTF8String] length:actionlen];
467 [data appendBytes:&index length:sizeof(int)];
468 [data appendBytes:&key length:sizeof(int)];
469 [data appendBytes:&eventFlags length:sizeof(int)];
471 [self queueMessage:AddMenuItemMsgID data:data];
474 - (void)removeMenuItemWithTag:(int)tag
476 NSMutableData *data = [NSMutableData data];
477 [data appendBytes:&tag length:sizeof(int)];
479 [self queueMessage:RemoveMenuItemMsgID data:data];
482 - (void)enableMenuItemWithTag:(int)tag state:(int)enabled
484 NSMutableData *data = [NSMutableData data];
486 [data appendBytes:&tag length:sizeof(int)];
487 [data appendBytes:&enabled length:sizeof(int)];
489 [self queueMessage:EnableMenuItemMsgID data:data];
492 - (void)showPopupMenuWithName:(char *)name atMouseLocation:(BOOL)mouse
494 NSMutableData *data = [NSMutableData data];
495 int len = strlen(name);
496 int row = -1, col = -1;
498 if (!mouse && curwin) {
499 row = curwin->w_wrow;
500 col = curwin->w_wcol;
503 [data appendBytes:&row length:sizeof(int)];
504 [data appendBytes:&col length:sizeof(int)];
505 [data appendBytes:&len length:sizeof(int)];
506 [data appendBytes:name length:len];
508 [self queueMessage:ShowPopupMenuMsgID data:data];
511 - (void)showToolbar:(int)enable flags:(int)flags
513 NSMutableData *data = [NSMutableData data];
515 [data appendBytes:&enable length:sizeof(int)];
516 [data appendBytes:&flags length:sizeof(int)];
518 [self queueMessage:ShowToolbarMsgID data:data];
521 - (void)createScrollbarWithIdentifier:(long)ident type:(int)type
523 NSMutableData *data = [NSMutableData data];
525 [data appendBytes:&ident length:sizeof(long)];
526 [data appendBytes:&type length:sizeof(int)];
528 [self queueMessage:CreateScrollbarMsgID data:data];
531 - (void)destroyScrollbarWithIdentifier:(long)ident
533 NSMutableData *data = [NSMutableData data];
534 [data appendBytes:&ident length:sizeof(long)];
536 [self queueMessage:DestroyScrollbarMsgID data:data];
539 - (void)showScrollbarWithIdentifier:(long)ident state:(int)visible
541 NSMutableData *data = [NSMutableData data];
543 [data appendBytes:&ident length:sizeof(long)];
544 [data appendBytes:&visible length:sizeof(int)];
546 [self queueMessage:ShowScrollbarMsgID data:data];
549 - (void)setScrollbarPosition:(int)pos length:(int)len identifier:(long)ident
551 NSMutableData *data = [NSMutableData data];
553 [data appendBytes:&ident length:sizeof(long)];
554 [data appendBytes:&pos length:sizeof(int)];
555 [data appendBytes:&len length:sizeof(int)];
557 [self queueMessage:SetScrollbarPositionMsgID data:data];
560 - (void)setScrollbarThumbValue:(long)val size:(long)size max:(long)max
561 identifier:(long)ident
563 float fval = max-size+1 > 0 ? (float)val/(max-size+1) : 0;
564 float prop = (float)size/(max+1);
565 if (fval < 0) fval = 0;
566 else if (fval > 1.0f) fval = 1.0f;
567 if (prop < 0) prop = 0;
568 else if (prop > 1.0f) prop = 1.0f;
570 NSMutableData *data = [NSMutableData data];
572 [data appendBytes:&ident length:sizeof(long)];
573 [data appendBytes:&fval length:sizeof(float)];
574 [data appendBytes:&prop length:sizeof(float)];
576 [self queueMessage:SetScrollbarThumbMsgID data:data];
579 - (BOOL)setFontWithName:(char *)name
583 BOOL parseFailed = NO;
586 fontName = [[[NSString alloc] initWithCString:name
587 encoding:NSUTF8StringEncoding] autorelease];
588 NSArray *components = [fontName componentsSeparatedByString:@":"];
589 if ([components count] == 2) {
590 NSString *sizeString = [components lastObject];
591 if ([sizeString length] > 0
592 && [sizeString characterAtIndex:0] == 'h') {
593 sizeString = [sizeString substringFromIndex:1];
594 if ([sizeString length] > 0) {
595 size = [sizeString floatValue];
596 fontName = [components objectAtIndex:0];
601 } else if ([components count] > 2) {
605 fontName = [[NSFont userFixedPitchFontOfSize:0] displayName];
608 if (!parseFailed && [fontName length] > 0) {
609 if (size < 6 || size > 100) {
610 // Font size 0.0 tells NSFont to use the 'user default size'.
614 NSFont *font = [NSFont fontWithName:fontName size:size];
616 //NSLog(@"Setting font '%@' of size %.2f", fontName, size);
617 NSMutableData *data = [NSMutableData data];
619 lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
621 [data appendBytes:&size length:sizeof(float)];
622 [data appendBytes:&len length:sizeof(int)];
623 [data appendBytes:[fontName UTF8String] length:len];
625 [self queueMessage:SetFontMsgID data:data];
630 NSLog(@"WARNING: Cannot set font with name '%@' of size %.2f",
635 - (void)executeActionWithName:(NSString *)name
637 int len = [name lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
640 NSMutableData *data = [NSMutableData data];
642 [data appendBytes:&len length:sizeof(int)];
643 [data appendBytes:[name UTF8String] length:len];
645 [self queueMessage:ExecuteActionMsgID data:data];
649 - (int)lookupColorWithKey:(NSString *)key
651 if (!(key && [key length] > 0))
654 // First of all try to lookup key in the color dictionary; note that all
655 // keys in this dictionary are lowercase with no whitespace.
657 NSString *stripKey = [[[[key lowercaseString]
658 stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]
659 componentsSeparatedByString:@" "]
660 componentsJoinedByString:@""];
662 if (stripKey && [stripKey length] > 0) {
663 id obj = [colorDict objectForKey:stripKey];
664 if (obj) return [obj intValue];
666 // The key was not in the dictionary; is it perhaps of the form
669 if ([stripKey length] > 1 && [stripKey characterAtIndex:0] == '#') {
670 NSScanner *scanner = [NSScanner scannerWithString:stripKey];
671 [scanner setScanLocation:1];
673 if ([scanner scanHexInt:&hex]) {
679 NSLog(@"WARNING: No color with key %@ found.", stripKey);
683 - (oneway void)processInput:(int)msgid data:(in NSData *)data
685 [lastFlushDate release];
686 lastFlushDate = [[NSDate date] retain];
688 [self handleMessage:msgid data:data];
692 - (BOOL)checkForModifiedBuffers
695 for (buf = firstbuf; buf != NULL; buf = buf->b_next) {
696 if (bufIsChanged(buf)) {
704 - (BOOL)starRegisterToPasteboard:(byref NSPasteboard *)pboard
706 if (VIsual_active && (State & NORMAL) && clip_star.available) {
707 // If there is no pasteboard, return YES to indicate that there is text
712 clip_copy_selection();
714 // Get the text to put on the pasteboard.
715 long_u len = 0; char_u *str = 0;
716 int type = clip_convert_selection(&str, &len, &clip_star);
720 NSString *string = [[NSString alloc]
721 initWithBytes:str length:len encoding:NSUTF8StringEncoding];
723 NSArray *types = [NSArray arrayWithObject:NSStringPboardType];
724 [pboard declareTypes:types owner:nil];
725 BOOL ok = [pboard setString:string forType:NSStringPboardType];
736 - (BOOL)starRegisterFromPasteboard:(byref NSPasteboard *)pboard
738 if (curbuf && !curbuf->b_p_ro) {
749 @implementation MMBackend (Private)
751 - (void)handleMessage:(int)msgid data:(NSData *)data
753 if (KillTaskMsgID == msgid) {
754 //NSLog(@"VimTask received kill message; exiting now.");
755 // Set this flag here so that exit does not send TaskExitedMsgID back
756 // to MMVimController.
757 receivedKillTaskMsg = YES;
759 } else if (InsertTextMsgID == msgid) {
761 NSString *key = [[NSString alloc] initWithData:data
762 encoding:NSUTF8StringEncoding];
763 //NSLog(@"insert text: %@ (hex=%x)", key, [key characterAtIndex:0]);
764 add_to_input_buf((char_u*)[key UTF8String],
765 [key lengthOfBytesUsingEncoding:NSUTF8StringEncoding]);
767 } else if (KeyDownMsgID == msgid || CmdKeyMsgID == msgid) {
769 const void *bytes = [data bytes];
770 int mods = *((int*)bytes); bytes += sizeof(int);
771 int len = *((int*)bytes); bytes += sizeof(int);
772 NSString *key = [[NSString alloc] initWithBytes:bytes length:len
773 encoding:NSUTF8StringEncoding];
774 mods = eventModifierFlagsToVimModMask(mods);
776 [self handleKeyDown:key modifiers:mods];
779 } else if (SelectTabMsgID == msgid) {
781 const void *bytes = [data bytes];
782 int idx = *((int*)bytes) + 1;
783 //NSLog(@"Selecting tab %d", idx);
784 send_tabline_event(idx);
785 } else if (CloseTabMsgID == msgid) {
787 const void *bytes = [data bytes];
788 int idx = *((int*)bytes) + 1;
789 //NSLog(@"Closing tab %d", idx);
790 send_tabline_menu_event(idx, TABLINE_MENU_CLOSE);
791 } else if (AddNewTabMsgID == msgid) {
792 //NSLog(@"Adding new tab");
793 send_tabline_menu_event(0, TABLINE_MENU_NEW);
794 } else if (DraggedTabMsgID == msgid) {
796 const void *bytes = [data bytes];
797 // NOTE! The destination index is 0 based, so do not add 1 to make it 1
799 int idx = *((int*)bytes);
802 } else if (ScrollWheelMsgID == msgid) {
804 const void *bytes = [data bytes];
806 int row = *((int*)bytes); bytes += sizeof(int);
807 int col = *((int*)bytes); bytes += sizeof(int);
808 int flags = *((int*)bytes); bytes += sizeof(int);
809 float dy = *((float*)bytes); bytes += sizeof(float);
811 int button = MOUSE_5;
812 if (dy > 0) button = MOUSE_4;
814 flags = eventModifierFlagsToVimMouseModMask(flags);
816 gui_send_mouse_event(button, col, row, NO, flags);
817 } else if (MouseDownMsgID == msgid) {
819 const void *bytes = [data bytes];
821 int row = *((int*)bytes); bytes += sizeof(int);
822 int col = *((int*)bytes); bytes += sizeof(int);
823 int button = *((int*)bytes); bytes += sizeof(int);
824 int flags = *((int*)bytes); bytes += sizeof(int);
825 int count = *((int*)bytes); bytes += sizeof(int);
827 button = eventButtonNumberToVimMouseButton(button);
828 flags = eventModifierFlagsToVimMouseModMask(flags);
830 gui_send_mouse_event(button, col, row, 0 != count, flags);
831 } else if (MouseUpMsgID == msgid) {
833 const void *bytes = [data bytes];
835 int row = *((int*)bytes); bytes += sizeof(int);
836 int col = *((int*)bytes); bytes += sizeof(int);
837 int flags = *((int*)bytes); bytes += sizeof(int);
839 flags = eventModifierFlagsToVimMouseModMask(flags);
841 gui_send_mouse_event(MOUSE_RELEASE, col, row, NO, flags);
842 } else if (MouseDraggedMsgID == msgid) {
844 const void *bytes = [data bytes];
846 int row = *((int*)bytes); bytes += sizeof(int);
847 int col = *((int*)bytes); bytes += sizeof(int);
848 int flags = *((int*)bytes); bytes += sizeof(int);
850 flags = eventModifierFlagsToVimMouseModMask(flags);
852 gui_send_mouse_event(MOUSE_DRAG, col, row, NO, flags);
853 } else if (SetTextDimensionsMsgID == msgid) {
855 const void *bytes = [data bytes];
856 int rows = *((int*)bytes); bytes += sizeof(int);
857 int cols = *((int*)bytes); bytes += sizeof(int);
859 // NOTE! Vim doesn't call gui_mch_set_shellsize() after
860 // gui_resize_shell(), so we have to manually set the rows and columns
861 // here. (MacVim doesn't change the rows and columns to avoid
862 // inconsistent states between Vim and MacVim.)
863 [self setRows:rows columns:cols];
865 //NSLog(@"[VimTask] Resizing shell to %dx%d.", cols, rows);
866 gui_resize_shell(cols, rows);
867 } else if (ExecuteMenuMsgID == msgid) {
869 const void *bytes = [data bytes];
870 int tag = *((int*)bytes); bytes += sizeof(int);
872 vimmenu_T *menu = (vimmenu_T*)tag;
873 // TODO! Make sure 'menu' is a valid menu pointer!
877 } else if (ToggleToolbarMsgID == msgid) {
878 char_u go[sizeof(GO_ALL)+2];
883 p = vim_strchr(go, GO_TOOLBAR);
887 char_u *end = go + len;
893 go[len] = GO_TOOLBAR;
897 set_option_value((char_u*)"guioptions", 0, go, 0);
899 // Force screen redraw (does it have to be this complicated?).
900 redraw_all_later(CLEAR);
901 update_screen(NOT_VALID);
904 gui_update_cursor(FALSE, FALSE);
906 } else if (ScrollbarEventMsgID == msgid) {
908 const void *bytes = [data bytes];
909 long ident = *((long*)bytes); bytes += sizeof(long);
910 int hitPart = *((int*)bytes); bytes += sizeof(int);
911 float fval = *((float*)bytes); bytes += sizeof(float);
912 scrollbar_T *sb = gui_find_scrollbar(ident);
915 scrollbar_T *sb_info = sb->wp ? &sb->wp->w_scrollbars[0] : sb;
916 long value = sb_info->value;
917 long size = sb_info->size;
918 long max = sb_info->max;
919 BOOL isStillDragging = NO;
920 BOOL updateKnob = YES;
923 case NSScrollerDecrementPage:
924 value -= (size > 2 ? size - 2 : 1);
926 case NSScrollerIncrementPage:
927 value += (size > 2 ? size - 2 : 1);
929 case NSScrollerDecrementLine:
932 case NSScrollerIncrementLine:
936 isStillDragging = YES;
938 case NSScrollerKnobSlot:
939 value = (long)(fval * (max - size + 1));
946 //NSLog(@"value %d -> %d", sb_info->value, value);
947 gui_drag_scrollbar(sb, value, isStillDragging);
950 // Dragging the knob or option+clicking automatically updates
951 // the knob position (on the actual NSScroller), so we only
952 // need to set the knob position in the other cases.
954 // Update both the left&right vertical scrollbars.
955 long identLeft = sb->wp->w_scrollbars[SBAR_LEFT].ident;
956 long identRight = sb->wp->w_scrollbars[SBAR_RIGHT].ident;
957 [self setScrollbarThumbValue:value size:size max:max
958 identifier:identLeft];
959 [self setScrollbarThumbValue:value size:size max:max
960 identifier:identRight];
962 // Update the horizontal scrollbar.
963 [self setScrollbarThumbValue:value size:size max:max
968 } else if (SetFontMsgID == msgid) {
970 const void *bytes = [data bytes];
971 float pointSize = *((float*)bytes); bytes += sizeof(float);
972 //unsigned len = *((unsigned*)bytes); bytes += sizeof(unsigned);
973 bytes += sizeof(unsigned); // len not used
975 NSMutableString *name = [NSMutableString stringWithUTF8String:bytes];
976 [name appendString:[NSString stringWithFormat:@":h%.2f", pointSize]];
978 set_option_value((char_u*)"gfn", 0, (char_u*)[name UTF8String], 0);
980 // Force screen redraw (does it have to be this complicated?).
981 redraw_all_later(CLEAR);
982 update_screen(NOT_VALID);
985 gui_update_cursor(FALSE, FALSE);
987 } else if (VimShouldCloseMsgID == msgid) {
989 } else if (DropFilesMsgID == msgid) {
991 const void *bytes = [data bytes];
992 int n = *((int*)bytes); bytes += sizeof(int);
995 int row = *((int*)bytes); bytes += sizeof(int);
996 int col = *((int*)bytes); bytes += sizeof(int);
998 char_u **fnames = (char_u **)alloc(n * sizeof(char_u *));
1000 const void *end = [data bytes] + [data length];
1002 while (bytes < end && i < n) {
1003 int len = *((int*)bytes); bytes += sizeof(int);
1004 fnames[i++] = vim_strnsave((char_u*)bytes, len);
1008 // NOTE! This function will free 'fnames'.
1009 gui_handle_drop(col, row, 0, fnames, i < n ? i : n);
1012 // HACK! I'm not sure how to get Vim to open a list of files in tabs,
1013 // so instead I create a ':tab drop' command with all the files to open
1015 NSMutableString *cmd = (n > 1)
1016 ? [NSMutableString stringWithString:@":tab drop"]
1017 : [NSMutableString stringWithString:@":drop"];
1019 const void *end = [data bytes] + [data length];
1021 for (i = 0; i < n && bytes < end; ++i) {
1022 int len = *((int*)bytes); bytes += sizeof(int);
1023 NSMutableString *file =
1024 [NSMutableString stringWithUTF8String:bytes];
1025 [file replaceOccurrencesOfString:@" "
1028 range:NSMakeRange(0, [file length])];
1031 [cmd appendString:@" "];
1032 [cmd appendString:file];
1035 // By going to the last tabpage we ensure that the new tabs will appear
1036 // last (if this call is left out, the taborder becomes messy).
1039 do_cmdline_cmd((char_u*)[cmd UTF8String]);
1041 // Force screen redraw (does it have to be this complicated?).
1042 // (This code was taken from the end of gui_handle_drop().)
1043 update_screen(NOT_VALID);
1046 gui_update_cursor(FALSE, FALSE);
1050 } else if (DropStringMsgID == msgid) {
1052 char_u dropkey[3] = { CSI, KS_EXTRA, (char_u)KE_DROP };
1053 const void *bytes = [data bytes];
1054 int len = *((int*)bytes); bytes += sizeof(int);
1055 NSMutableString *string = [NSMutableString stringWithUTF8String:bytes];
1057 // Replace unrecognized end-of-line sequences with \x0a (line feed).
1058 NSRange range = { 0, [string length] };
1059 unsigned n = [string replaceOccurrencesOfString:@"\x0d\x0a"
1060 withString:@"\x0a" options:0
1063 n = [string replaceOccurrencesOfString:@"\x0d" withString:@"\x0a"
1064 options:0 range:range];
1067 len = [string lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
1068 dnd_yank_drag_data((char_u*)[string UTF8String], len);
1069 add_to_input_buf(dropkey, sizeof(dropkey));
1072 NSLog(@"WARNING: Unknown message received (msgid=%d)", msgid);
1076 + (NSDictionary *)specialKeys
1078 static NSDictionary *specialKeys = nil;
1081 NSBundle *mainBundle = [NSBundle mainBundle];
1082 NSString *path = [mainBundle pathForResource:@"SpecialKeys"
1084 specialKeys = [[NSDictionary alloc] initWithContentsOfFile:path];
1090 - (void)handleKeyDown:(NSString *)key modifiers:(int)mods
1097 // Special keys (arrow keys, function keys, etc.) are stored in a plist so
1098 // that new keys can easily be added.
1099 NSString *specialString = [[MMBackend specialKeys]
1101 if (specialString && [specialString length] > 1) {
1102 //NSLog(@"special key: %@", specialString);
1103 int ikey = TO_SPECIAL([specialString characterAtIndex:0],
1104 [specialString characterAtIndex:1]);
1106 ikey = simplify_key(ikey, &mods);
1111 special[1] = K_SECOND(ikey);
1112 special[2] = K_THIRD(ikey);
1116 } else if ([key length] > 0) {
1117 chars = (char_u*)[key UTF8String];
1118 length = [key lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
1119 unichar c = [key characterAtIndex:0];
1121 if ((c == Ctrl_C && ctrl_c_interrupts)
1122 || (c == intr_char && intr_char != Ctrl_C)) {
1123 // TODO: The run loop is not touched while Vim is processing, so
1124 // effectively it is impossible to interrupt Vim.
1129 //NSLog(@"non-special: %@ (hex=%x, mods=%d)", key,
1130 // [key characterAtIndex:0], mods);
1132 // HACK! In most circumstances the Ctrl and Shift modifiers should be
1133 // cleared since they are already added to the key by the AppKit.
1134 // Unfortunately, the only way to deal with when to clear the modifiers
1135 // or not seems to be to have hard-wired rules like this.
1136 if ( !((' ' == c) || (0xa0 == c) || (mods & MOD_MASK_CMD)) ) {
1137 mods &= ~MOD_MASK_SHIFT;
1138 mods &= ~MOD_MASK_CTRL;
1139 //NSLog(@"clear shift ctrl");
1142 // HACK! All Option+key presses go via 'insert text' messages, except
1143 // for <M-Space>. If the Alt flag is not cleared for <M-Space> it does
1144 // not work to map to it.
1145 if (0xa0 == c && !(mods & MOD_MASK_CMD)) {
1146 //NSLog(@"clear alt");
1147 mods &= ~MOD_MASK_ALT;
1151 if (chars && length > 0) {
1153 //NSLog(@"adding mods: %d", mods);
1155 modChars[1] = KS_MODIFIER;
1157 add_to_input_buf(modChars, 3);
1160 //NSLog(@"add to input buf: 0x%x", chars[0]);
1161 add_to_input_buf(chars, length);
1165 - (void)queueMessage:(int)msgid data:(NSData *)data
1167 [queue addObject:[NSData dataWithBytes:&msgid length:sizeof(int)]];
1169 [queue addObject:data];
1171 [queue addObject:[NSData data]];
1174 - (void)connectionDidDie:(NSNotification *)notification
1176 // If the main connection to MacVim is lost this means that MacVim was
1177 // either quit (by the user chosing Quit on the MacVim menu), or it has
1178 // crashed. In either case our only option is to quit now.
1179 // TODO: Write backup file?
1181 //NSLog(@"A Vim process lots its connection to MacVim; quitting.");
1185 @end // MMBackend (Private)
1190 static int eventModifierFlagsToVimModMask(int modifierFlags)
1194 if (modifierFlags & NSShiftKeyMask)
1195 modMask |= MOD_MASK_SHIFT;
1196 if (modifierFlags & NSControlKeyMask)
1197 modMask |= MOD_MASK_CTRL;
1198 if (modifierFlags & NSAlternateKeyMask)
1199 modMask |= MOD_MASK_ALT;
1200 if (modifierFlags & NSCommandKeyMask)
1201 modMask |= MOD_MASK_CMD;
1206 static int vimModMaskToEventModifierFlags(int mods)
1210 if (mods & MOD_MASK_SHIFT)
1211 flags |= NSShiftKeyMask;
1212 if (mods & MOD_MASK_CTRL)
1213 flags |= NSControlKeyMask;
1214 if (mods & MOD_MASK_ALT)
1215 flags |= NSAlternateKeyMask;
1216 if (mods & MOD_MASK_CMD)
1217 flags |= NSCommandKeyMask;
1222 static int eventModifierFlagsToVimMouseModMask(int modifierFlags)
1226 if (modifierFlags & NSShiftKeyMask)
1227 modMask |= MOUSE_SHIFT;
1228 if (modifierFlags & NSControlKeyMask)
1229 modMask |= MOUSE_CTRL;
1230 if (modifierFlags & NSAlternateKeyMask)
1231 modMask |= MOUSE_ALT;
1236 static int eventButtonNumberToVimMouseButton(int buttonNumber)
1238 static int mouseButton[] = { MOUSE_LEFT, MOUSE_RIGHT, MOUSE_MIDDLE,
1239 MOUSE_X1, MOUSE_X2 };
1241 return mouseButton[buttonNumber < 5 ? buttonNumber : 0];
1244 static int specialKeyToNSKey(int key)
1246 if (!IS_SPECIAL(key))
1253 { K_UP, NSUpArrowFunctionKey },
1254 { K_DOWN, NSDownArrowFunctionKey },
1255 { K_LEFT, NSLeftArrowFunctionKey },
1256 { K_RIGHT, NSRightArrowFunctionKey },
1257 { K_F1, NSF1FunctionKey },
1258 { K_F2, NSF2FunctionKey },
1259 { K_F3, NSF3FunctionKey },
1260 { K_F4, NSF4FunctionKey },
1261 { K_F5, NSF5FunctionKey },
1262 { K_F6, NSF6FunctionKey },
1263 { K_F7, NSF7FunctionKey },
1264 { K_F8, NSF8FunctionKey },
1265 { K_F9, NSF9FunctionKey },
1266 { K_F10, NSF10FunctionKey },
1267 { K_F11, NSF11FunctionKey },
1268 { K_F12, NSF12FunctionKey },
1269 { K_F13, NSF13FunctionKey },
1270 { K_F14, NSF14FunctionKey },
1271 { K_F15, NSF15FunctionKey },
1272 { K_F16, NSF16FunctionKey },
1273 { K_F17, NSF17FunctionKey },
1274 { K_F18, NSF18FunctionKey },
1275 { K_F19, NSF19FunctionKey },
1276 { K_F20, NSF20FunctionKey },
1277 { K_F21, NSF21FunctionKey },
1278 { K_F22, NSF22FunctionKey },
1279 { K_F23, NSF23FunctionKey },
1280 { K_F24, NSF24FunctionKey },
1281 { K_F25, NSF25FunctionKey },
1282 { K_F26, NSF26FunctionKey },
1283 { K_F27, NSF27FunctionKey },
1284 { K_F28, NSF28FunctionKey },
1285 { K_F29, NSF29FunctionKey },
1286 { K_F30, NSF30FunctionKey },
1287 { K_F31, NSF31FunctionKey },
1288 { K_F32, NSF32FunctionKey },
1289 { K_F33, NSF33FunctionKey },
1290 { K_F34, NSF34FunctionKey },
1291 { K_F35, NSF35FunctionKey },
1292 { K_DEL, NSBackspaceCharacter },
1293 { K_BS, NSDeleteCharacter },
1294 { K_HOME, NSHomeFunctionKey },
1295 { K_END, NSEndFunctionKey },
1296 { K_PAGEUP, NSPageUpFunctionKey },
1297 { K_PAGEDOWN, NSPageDownFunctionKey }
1301 for (i = 0; i < sizeof(sp2ns)/sizeof(sp2ns[0]); ++i) {
1302 if (sp2ns[i].special == key)
1303 return sp2ns[i].nskey;