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.
15 // This constant controls how often the command queue may be flushed. If it is
16 // too small the app might feel unresponsive; if it is too large there might be
17 // long periods without the screen updating (e.g. when sourcing a large session
18 // file). (The unit is seconds.)
19 static float MMFlushTimeoutInterval = 0.1f;
21 static unsigned MMServerMax = 1000;
22 //static NSTimeInterval MMEvaluateExpressionTimeout = 3;
25 // TODO: Move to separate file.
26 static int eventModifierFlagsToVimModMask(int modifierFlags);
27 static int vimModMaskToEventModifierFlags(int mods);
28 static int eventModifierFlagsToVimMouseModMask(int modifierFlags);
29 static int eventButtonNumberToVimMouseButton(int buttonNumber);
30 static int specialKeyToNSKey(int key);
40 @interface NSString (MMServerNameCompare)
41 - (NSComparisonResult)serverNameCompare:(NSString *)string;
46 @interface MMBackend (Private)
47 - (void)handleMessage:(int)msgid data:(NSData *)data;
48 + (NSDictionary *)specialKeys;
49 - (void)handleKeyDown:(NSString *)key modifiers:(int)mods;
50 - (void)queueMessage:(int)msgid data:(NSData *)data;
51 - (void)connectionDidDie:(NSNotification *)notification;
52 - (void)blinkTimerFired:(NSTimer *)timer;
53 - (void)focusChange:(BOOL)on;
54 - (void)processInputBegin;
55 - (void)processInputEnd;
56 - (NSString *)connectionNameFromServerName:(NSString *)name;
57 - (NSConnection *)connectionForServerName:(NSString *)name;
58 - (NSConnection *)connectionForServerPort:(int)port;
59 - (void)serverConnectionDidDie:(NSNotification *)notification;
60 - (void)addClient:(NSDistantObject *)client;
61 - (NSString *)alternateServerNameForName:(NSString *)name;
66 @implementation MMBackend
68 + (MMBackend *)sharedInstance
70 static MMBackend *singleton = nil;
71 return singleton ? singleton : (singleton = [MMBackend new]);
76 if ((self = [super init])) {
77 queue = [[NSMutableArray alloc] init];
78 #if MM_USE_INPUT_QUEUE
79 inputQueue = [[NSMutableArray alloc] init];
81 drawData = [[NSMutableData alloc] initWithCapacity:1024];
82 connectionNameDict = [[NSMutableDictionary alloc] init];
83 clientProxyDict = [[NSMutableDictionary alloc] init];
84 serverReplyDict = [[NSMutableDictionary alloc] init];
86 NSString *path = [[NSBundle mainBundle] pathForResource:@"Colors"
89 colorDict = [[NSDictionary dictionaryWithContentsOfFile:path]
92 NSLog(@"WARNING: Could not locate Colors.plist.");
95 path = [[NSBundle mainBundle] pathForResource:@"SystemColors"
98 sysColorDict = [[NSDictionary dictionaryWithContentsOfFile:path]
101 NSLog(@"WARNING: Could not locate SystemColors.plist.");
110 //NSLog(@"%@ %s", [self className], _cmd);
112 [[NSNotificationCenter defaultCenter] removeObserver:self];
114 [blinkTimer release]; blinkTimer = nil;
115 #if MM_USE_INPUT_QUEUE
116 [inputQueue release]; inputQueue = nil;
118 [alternateServerName release]; alternateServerName = nil;
119 [serverReplyDict release]; serverReplyDict = nil;
120 [clientProxyDict release]; clientProxyDict = nil;
121 [connectionNameDict release]; connectionNameDict = nil;
122 [queue release]; queue = nil;
123 [drawData release]; drawData = nil;
124 [frontendProxy release]; frontendProxy = nil;
125 [connection release]; connection = nil;
126 [sysColorDict release]; sysColorDict = nil;
127 [colorDict release]; colorDict = nil;
132 - (void)setBackgroundColor:(int)color
134 backgroundColor = color;
137 - (void)setForegroundColor:(int)color
139 foregroundColor = color;
142 - (void)setSpecialColor:(int)color
144 specialColor = color;
147 - (void)setDefaultColorsBackground:(int)bg foreground:(int)fg
149 defaultBackgroundColor = bg;
150 defaultForegroundColor = fg;
152 NSMutableData *data = [NSMutableData data];
154 [data appendBytes:&bg length:sizeof(int)];
155 [data appendBytes:&fg length:sizeof(int)];
157 [self queueMessage:SetDefaultColorsMsgID data:data];
160 - (NSConnection *)connection
163 // NOTE! If the name of the connection changes here it must also be
164 // updated in MMAppController.m.
165 NSString *name = [NSString stringWithFormat:@"%@-connection",
166 [[NSBundle mainBundle] bundleIdentifier]];
168 connection = [NSConnection connectionWithRegisteredName:name host:nil];
172 // NOTE: 'connection' may be nil here.
178 if (![self connection]) {
179 NSBundle *mainBundle = [NSBundle mainBundle];
181 NSString *path = [mainBundle bundlePath];
182 if (![[NSWorkspace sharedWorkspace] launchApplication:path]) {
183 NSLog(@"WARNING: Failed to launch GUI with path %@", path);
187 // HACK! It would be preferable to launch the GUI using NSWorkspace,
188 // however I have not managed to figure out how to pass arguments using
191 // NOTE! Using NSTask to launch the GUI has the negative side-effect
192 // that the GUI won't be activated (or raised) so there is a hack in
193 // MMWindowController which always raises the app when a new window is
195 NSMutableArray *args = [NSMutableArray arrayWithObjects:
196 [NSString stringWithFormat:@"-%@", MMNoWindowKey], @"yes", nil];
197 NSString *exeName = [[mainBundle infoDictionary]
198 objectForKey:@"CFBundleExecutable"];
199 NSString *path = [mainBundle pathForAuxiliaryExecutable:exeName];
201 NSLog(@"ERROR: Could not find MacVim executable in bundle");
205 [NSTask launchedTaskWithLaunchPath:path arguments:args];
208 // HACK! The NSWorkspaceDidLaunchApplicationNotification does not work
209 // for tasks like this, so poll the mach bootstrap server until it
210 // returns a valid connection. Also set a time-out date so that we
211 // don't get stuck doing this forever.
212 NSDate *timeOutDate = [NSDate dateWithTimeIntervalSinceNow:15];
213 while (!connection &&
214 NSOrderedDescending == [timeOutDate compare:[NSDate date]])
216 [[NSRunLoop currentRunLoop]
217 runMode:NSDefaultRunLoopMode
218 beforeDate:[NSDate dateWithTimeIntervalSinceNow:1]];
220 // NOTE: This call will set 'connection' as a side-effect.
225 NSLog(@"WARNING: Timed-out waiting for GUI to launch.");
230 id proxy = [connection rootProxy];
231 [proxy setProtocolForProxy:@protocol(MMAppProtocol)];
233 [[NSNotificationCenter defaultCenter] addObserver:self
234 selector:@selector(connectionDidDie:)
235 name:NSConnectionDidDieNotification object:connection];
237 int pid = [[NSProcessInfo processInfo] processIdentifier];
240 frontendProxy = [proxy connectBackend:self pid:pid];
242 @catch (NSException *e) {
243 NSLog(@"Exception caught when trying to connect backend: \"%@\"", e);
247 [frontendProxy retain];
248 [frontendProxy setProtocolForProxy:@protocol(MMAppProtocol)];
251 return connection && frontendProxy;
254 - (BOOL)openVimWindow
256 [self queueMessage:OpenVimWindowMsgID data:nil];
262 int type = ClearAllDrawType;
264 // Any draw commands in queue are effectively obsolete since this clearAll
265 // will negate any effect they have, therefore we may as well clear the
267 [drawData setLength:0];
269 [drawData appendBytes:&type length:sizeof(int)];
271 [drawData appendBytes:&defaultBackgroundColor length:sizeof(int)];
274 - (void)clearBlockFromRow:(int)row1 column:(int)col1
275 toRow:(int)row2 column:(int)col2
277 int type = ClearBlockDrawType;
279 [drawData appendBytes:&type length:sizeof(int)];
281 [drawData appendBytes:&defaultBackgroundColor length:sizeof(int)];
282 [drawData appendBytes:&row1 length:sizeof(int)];
283 [drawData appendBytes:&col1 length:sizeof(int)];
284 [drawData appendBytes:&row2 length:sizeof(int)];
285 [drawData appendBytes:&col2 length:sizeof(int)];
288 - (void)deleteLinesFromRow:(int)row count:(int)count
289 scrollBottom:(int)bottom left:(int)left right:(int)right
291 int type = DeleteLinesDrawType;
293 [drawData appendBytes:&type length:sizeof(int)];
295 [drawData appendBytes:&defaultBackgroundColor length:sizeof(int)];
296 [drawData appendBytes:&row length:sizeof(int)];
297 [drawData appendBytes:&count length:sizeof(int)];
298 [drawData appendBytes:&bottom length:sizeof(int)];
299 [drawData appendBytes:&left length:sizeof(int)];
300 [drawData appendBytes:&right length:sizeof(int)];
303 - (void)replaceString:(char*)s length:(int)len row:(int)row column:(int)col
306 if (len <= 0) return;
308 int type = ReplaceStringDrawType;
310 [drawData appendBytes:&type length:sizeof(int)];
312 [drawData appendBytes:&backgroundColor length:sizeof(int)];
313 [drawData appendBytes:&foregroundColor length:sizeof(int)];
314 [drawData appendBytes:&specialColor length:sizeof(int)];
315 [drawData appendBytes:&row length:sizeof(int)];
316 [drawData appendBytes:&col length:sizeof(int)];
317 [drawData appendBytes:&flags length:sizeof(int)];
318 [drawData appendBytes:&len length:sizeof(int)];
319 [drawData appendBytes:s length:len];
322 - (void)insertLinesFromRow:(int)row count:(int)count
323 scrollBottom:(int)bottom left:(int)left right:(int)right
325 int type = InsertLinesDrawType;
327 [drawData appendBytes:&type length:sizeof(int)];
329 [drawData appendBytes:&defaultBackgroundColor length:sizeof(int)];
330 [drawData appendBytes:&row length:sizeof(int)];
331 [drawData appendBytes:&count length:sizeof(int)];
332 [drawData appendBytes:&bottom length:sizeof(int)];
333 [drawData appendBytes:&left length:sizeof(int)];
334 [drawData appendBytes:&right length:sizeof(int)];
337 - (void)drawCursorAtRow:(int)row column:(int)col shape:(int)shape
338 fraction:(int)percent color:(int)color
340 int type = DrawCursorDrawType;
342 [drawData appendBytes:&type length:sizeof(int)];
344 [drawData appendBytes:&color length:sizeof(int)];
345 [drawData appendBytes:&row length:sizeof(int)];
346 [drawData appendBytes:&col length:sizeof(int)];
347 [drawData appendBytes:&shape length:sizeof(int)];
348 [drawData appendBytes:&percent length:sizeof(int)];
351 - (void)flushQueue:(BOOL)force
353 // NOTE! This method gets called a lot; if we were to flush every time it
354 // was called MacVim would feel unresponsive. So there is a time out which
355 // ensures that the queue isn't flushed too often.
356 if (!force && lastFlushDate && -[lastFlushDate timeIntervalSinceNow]
357 < MMFlushTimeoutInterval)
360 if ([drawData length] > 0) {
361 [self queueMessage:BatchDrawMsgID data:[drawData copy]];
362 [drawData setLength:0];
365 if ([queue count] > 0) {
367 [frontendProxy processCommandQueue:queue];
369 @catch (NSException *e) {
370 NSLog(@"Exception caught when processing command queue: \"%@\"", e);
373 [queue removeAllObjects];
375 [lastFlushDate release];
376 lastFlushDate = [[NSDate date] retain];
380 - (BOOL)waitForInput:(int)milliseconds
382 NSDate *date = milliseconds > 0 ?
383 [NSDate dateWithTimeIntervalSinceNow:.001*milliseconds] :
384 [NSDate distantFuture];
386 [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:date];
388 // I know of no way to figure out if the run loop exited because input was
389 // found or because of a time out, so I need to manually indicate when
390 // input was received in processInput:data: and then reset it every time
392 BOOL yn = inputReceived;
400 #ifdef MAC_CLIENTSERVER
401 // The default connection is used for the client/server code.
402 [[NSConnection defaultConnection] setRootObject:nil];
403 [[NSConnection defaultConnection] invalidate];
406 // By invalidating the NSConnection the MMWindowController immediately
407 // finds out that the connection is down and as a result
408 // [MMWindowController connectionDidDie:] is invoked.
409 //NSLog(@"%@ %s", [self className], _cmd);
410 [[NSNotificationCenter defaultCenter] removeObserver:self];
411 [connection invalidate];
414 - (void)selectTab:(int)index
416 //NSLog(@"%s%d", _cmd, index);
419 NSData *data = [NSData dataWithBytes:&index length:sizeof(int)];
420 [self queueMessage:SelectTabMsgID data:data];
425 //NSLog(@"%s", _cmd);
427 NSMutableData *data = [NSMutableData data];
429 int idx = tabpage_index(curtab) - 1;
430 [data appendBytes:&idx length:sizeof(int)];
433 for (tp = first_tabpage; tp != NULL; tp = tp->tp_next) {
434 // This function puts the label of the tab in the global 'NameBuff'.
435 get_tabline_label(tp, FALSE);
436 int len = strlen((char*)NameBuff);
437 if (len <= 0) continue;
439 // Count the number of windows in the tabpage.
440 //win_T *wp = tp->tp_firstwin;
442 //for (wincount = 0; wp != NULL; wp = wp->w_next, ++wincount);
444 //[data appendBytes:&wincount length:sizeof(int)];
445 [data appendBytes:&len length:sizeof(int)];
446 [data appendBytes:NameBuff length:len];
449 [self queueMessage:UpdateTabBarMsgID data:data];
452 - (BOOL)tabBarVisible
454 return tabBarVisible;
457 - (void)showTabBar:(BOOL)enable
459 tabBarVisible = enable;
461 int msgid = enable ? ShowTabBarMsgID : HideTabBarMsgID;
462 [self queueMessage:msgid data:nil];
465 - (void)setRows:(int)rows columns:(int)cols
467 //NSLog(@"[VimTask] setRows:%d columns:%d", rows, cols);
469 int dim[] = { rows, cols };
470 NSData *data = [NSData dataWithBytes:&dim length:2*sizeof(int)];
472 [self queueMessage:SetTextDimensionsMsgID data:data];
475 - (void)setVimWindowTitle:(char *)title
477 NSMutableData *data = [NSMutableData data];
478 int len = strlen(title);
479 if (len <= 0) return;
481 [data appendBytes:&len length:sizeof(int)];
482 [data appendBytes:title length:len];
484 [self queueMessage:SetVimWindowTitleMsgID data:data];
487 - (char *)browseForFileInDirectory:(char *)dir title:(char *)title
490 //NSLog(@"browseForFileInDirectory:%s title:%s saving:%d", dir, title,
495 ? [NSString stringWithCString:dir encoding:NSUTF8StringEncoding]
498 ? [NSString stringWithCString:title encoding:NSUTF8StringEncoding]
501 [frontendProxy showSavePanelForDirectory:ds title:ts saving:saving];
503 // Wait until a reply is sent from MMVimController.
504 [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
505 beforeDate:[NSDate distantFuture]];
507 if (dialogReturn && [dialogReturn isKindOfClass:[NSString class]]) {
508 s = vim_strsave((char_u*)[dialogReturn UTF8String]);
511 [dialogReturn release]; dialogReturn = nil;
513 @catch (NSException *e) {
514 NSLog(@"Exception caught when showing save panel: \"%@\"", e);
520 - (oneway void)setDialogReturn:(in bycopy id)obj
522 // NOTE: This is called by
523 // - [MMVimController panelDidEnd:::], and
524 // - [MMVimController alertDidEnd:::],
525 // to indicate that a save/open panel or alert has finished.
527 if (obj != dialogReturn) {
528 [dialogReturn release];
529 dialogReturn = [obj retain];
533 - (int)presentDialogWithType:(int)type title:(char *)title message:(char *)msg
534 buttons:(char *)btns textField:(char *)txtfield
537 NSString *message = nil, *text = nil, *textFieldString = nil;
538 NSArray *buttons = nil;
539 int style = NSInformationalAlertStyle;
541 if (VIM_WARNING == type) style = NSWarningAlertStyle;
542 else if (VIM_ERROR == type) style = NSCriticalAlertStyle;
545 NSString *btnString = [NSString stringWithUTF8String:btns];
546 buttons = [btnString componentsSeparatedByString:@"\n"];
549 message = [NSString stringWithUTF8String:title];
551 text = [NSString stringWithUTF8String:msg];
553 // HACK! If there is a '\n\n' or '\n' sequence in the message, then
554 // make the part up to there into the title. We only do this
555 // because Vim has lots of dialogs without a title and they look
557 // TODO: Fix the actual dialog texts.
558 NSRange eolRange = [text rangeOfString:@"\n\n"];
559 if (NSNotFound == eolRange.location)
560 eolRange = [text rangeOfString:@"\n"];
561 if (NSNotFound != eolRange.location) {
562 message = [text substringToIndex:eolRange.location];
563 text = [text substringFromIndex:NSMaxRange(eolRange)];
568 textFieldString = [NSString stringWithUTF8String:txtfield];
571 [frontendProxy presentDialogWithStyle:style message:message
572 informativeText:text buttonTitles:buttons
573 textFieldString:textFieldString];
575 // Wait until a reply is sent from MMVimController.
576 [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
577 beforeDate:[NSDate distantFuture]];
579 if (dialogReturn && [dialogReturn isKindOfClass:[NSArray class]]
580 && [dialogReturn count]) {
581 retval = [[dialogReturn objectAtIndex:0] intValue];
582 if (txtfield && [dialogReturn count] > 1) {
583 NSString *retString = [dialogReturn objectAtIndex:1];
584 vim_strncpy((char_u*)txtfield, (char_u*)[retString UTF8String],
589 [dialogReturn release]; dialogReturn = nil;
591 @catch (NSException *e) {
592 NSLog(@"Exception caught while showing alert dialog: \"%@\"", e);
598 - (void)addMenuWithTag:(int)tag parent:(int)parentTag name:(char *)name
601 //NSLog(@"addMenuWithTag:%d parent:%d name:%s atIndex:%d", tag, parentTag,
604 int namelen = name ? strlen(name) : 0;
605 NSMutableData *data = [NSMutableData data];
607 [data appendBytes:&tag length:sizeof(int)];
608 [data appendBytes:&parentTag length:sizeof(int)];
609 [data appendBytes:&namelen length:sizeof(int)];
610 if (namelen > 0) [data appendBytes:name length:namelen];
611 [data appendBytes:&index length:sizeof(int)];
613 [self queueMessage:AddMenuMsgID data:data];
616 - (void)addMenuItemWithTag:(int)tag parent:(int)parentTag name:(char *)name
617 tip:(char *)tip icon:(char *)icon
618 keyEquivalent:(int)key modifiers:(int)mods
619 action:(NSString *)action atIndex:(int)index
621 //NSLog(@"addMenuItemWithTag:%d parent:%d name:%s tip:%s atIndex:%d", tag,
622 // parentTag, name, tip, index);
624 int namelen = name ? strlen(name) : 0;
625 int tiplen = tip ? strlen(tip) : 0;
626 int iconlen = icon ? strlen(icon) : 0;
627 int eventFlags = vimModMaskToEventModifierFlags(mods);
628 int actionlen = [action lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
629 NSMutableData *data = [NSMutableData data];
631 key = specialKeyToNSKey(key);
633 [data appendBytes:&tag length:sizeof(int)];
634 [data appendBytes:&parentTag length:sizeof(int)];
635 [data appendBytes:&namelen length:sizeof(int)];
636 if (namelen > 0) [data appendBytes:name length:namelen];
637 [data appendBytes:&tiplen length:sizeof(int)];
638 if (tiplen > 0) [data appendBytes:tip length:tiplen];
639 [data appendBytes:&iconlen length:sizeof(int)];
640 if (iconlen > 0) [data appendBytes:icon length:iconlen];
641 [data appendBytes:&actionlen length:sizeof(int)];
642 if (actionlen > 0) [data appendBytes:[action UTF8String] length:actionlen];
643 [data appendBytes:&index length:sizeof(int)];
644 [data appendBytes:&key length:sizeof(int)];
645 [data appendBytes:&eventFlags length:sizeof(int)];
647 [self queueMessage:AddMenuItemMsgID data:data];
650 - (void)removeMenuItemWithTag:(int)tag
652 NSMutableData *data = [NSMutableData data];
653 [data appendBytes:&tag length:sizeof(int)];
655 [self queueMessage:RemoveMenuItemMsgID data:data];
658 - (void)enableMenuItemWithTag:(int)tag state:(int)enabled
660 NSMutableData *data = [NSMutableData data];
662 [data appendBytes:&tag length:sizeof(int)];
663 [data appendBytes:&enabled length:sizeof(int)];
665 [self queueMessage:EnableMenuItemMsgID data:data];
668 - (void)showPopupMenuWithName:(char *)name atMouseLocation:(BOOL)mouse
670 int len = strlen(name);
671 int row = -1, col = -1;
673 if (len <= 0) return;
675 if (!mouse && curwin) {
676 row = curwin->w_wrow;
677 col = curwin->w_wcol;
680 NSMutableData *data = [NSMutableData data];
682 [data appendBytes:&row length:sizeof(int)];
683 [data appendBytes:&col length:sizeof(int)];
684 [data appendBytes:&len length:sizeof(int)];
685 [data appendBytes:name length:len];
687 [self queueMessage:ShowPopupMenuMsgID data:data];
690 - (void)showToolbar:(int)enable flags:(int)flags
692 NSMutableData *data = [NSMutableData data];
694 [data appendBytes:&enable length:sizeof(int)];
695 [data appendBytes:&flags length:sizeof(int)];
697 [self queueMessage:ShowToolbarMsgID data:data];
700 - (void)createScrollbarWithIdentifier:(long)ident type:(int)type
702 NSMutableData *data = [NSMutableData data];
704 [data appendBytes:&ident length:sizeof(long)];
705 [data appendBytes:&type length:sizeof(int)];
707 [self queueMessage:CreateScrollbarMsgID data:data];
710 - (void)destroyScrollbarWithIdentifier:(long)ident
712 NSMutableData *data = [NSMutableData data];
713 [data appendBytes:&ident length:sizeof(long)];
715 [self queueMessage:DestroyScrollbarMsgID data:data];
718 - (void)showScrollbarWithIdentifier:(long)ident state:(int)visible
720 NSMutableData *data = [NSMutableData data];
722 [data appendBytes:&ident length:sizeof(long)];
723 [data appendBytes:&visible length:sizeof(int)];
725 [self queueMessage:ShowScrollbarMsgID data:data];
728 - (void)setScrollbarPosition:(int)pos length:(int)len identifier:(long)ident
730 NSMutableData *data = [NSMutableData data];
732 [data appendBytes:&ident length:sizeof(long)];
733 [data appendBytes:&pos length:sizeof(int)];
734 [data appendBytes:&len length:sizeof(int)];
736 [self queueMessage:SetScrollbarPositionMsgID data:data];
739 - (void)setScrollbarThumbValue:(long)val size:(long)size max:(long)max
740 identifier:(long)ident
742 float fval = max-size+1 > 0 ? (float)val/(max-size+1) : 0;
743 float prop = (float)size/(max+1);
744 if (fval < 0) fval = 0;
745 else if (fval > 1.0f) fval = 1.0f;
746 if (prop < 0) prop = 0;
747 else if (prop > 1.0f) prop = 1.0f;
749 NSMutableData *data = [NSMutableData data];
751 [data appendBytes:&ident length:sizeof(long)];
752 [data appendBytes:&fval length:sizeof(float)];
753 [data appendBytes:&prop length:sizeof(float)];
755 [self queueMessage:SetScrollbarThumbMsgID data:data];
758 - (BOOL)setFontWithName:(char *)name
762 BOOL parseFailed = NO;
765 fontName = [[[NSString alloc] initWithCString:name
766 encoding:NSUTF8StringEncoding] autorelease];
767 NSArray *components = [fontName componentsSeparatedByString:@":"];
768 if ([components count] == 2) {
769 NSString *sizeString = [components lastObject];
770 if ([sizeString length] > 0
771 && [sizeString characterAtIndex:0] == 'h') {
772 sizeString = [sizeString substringFromIndex:1];
773 if ([sizeString length] > 0) {
774 size = [sizeString floatValue];
775 fontName = [components objectAtIndex:0];
780 } else if ([components count] > 2) {
784 fontName = [[NSFont userFixedPitchFontOfSize:0] displayName];
787 if (!parseFailed && [fontName length] > 0) {
788 if (size < 6 || size > 100) {
789 // Font size 0.0 tells NSFont to use the 'user default size'.
793 NSFont *font = [NSFont fontWithName:fontName size:size];
795 //NSLog(@"Setting font '%@' of size %.2f", fontName, size);
797 lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
799 NSMutableData *data = [NSMutableData data];
801 [data appendBytes:&size length:sizeof(float)];
802 [data appendBytes:&len length:sizeof(int)];
803 [data appendBytes:[fontName UTF8String] length:len];
805 [self queueMessage:SetFontMsgID data:data];
811 NSLog(@"WARNING: Cannot set font with name '%@' of size %.2f",
816 - (void)executeActionWithName:(NSString *)name
818 int len = [name lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
821 NSMutableData *data = [NSMutableData data];
823 [data appendBytes:&len length:sizeof(int)];
824 [data appendBytes:[name UTF8String] length:len];
826 [self queueMessage:ExecuteActionMsgID data:data];
830 - (void)setMouseShape:(int)shape
832 NSMutableData *data = [NSMutableData data];
833 [data appendBytes:&shape length:sizeof(int)];
834 [self queueMessage:SetMouseShapeMsgID data:data];
837 - (void)setBlinkWait:(int)wait on:(int)on off:(int)off
839 // Vim specifies times in milliseconds, whereas Cocoa wants them in
841 blinkWaitInterval = .001f*wait;
842 blinkOnInterval = .001f*on;
843 blinkOffInterval = .001f*off;
849 [blinkTimer invalidate];
850 [blinkTimer release];
854 if (blinkWaitInterval > 0 && blinkOnInterval > 0 && blinkOffInterval > 0
856 blinkState = MMBlinkStateOn;
858 [[NSTimer scheduledTimerWithTimeInterval:blinkWaitInterval
860 selector:@selector(blinkTimerFired:)
861 userInfo:nil repeats:NO] retain];
862 gui_update_cursor(TRUE, FALSE);
863 [self flushQueue:YES];
869 if (MMBlinkStateOff == blinkState) {
870 gui_update_cursor(TRUE, FALSE);
871 [self flushQueue:YES];
874 blinkState = MMBlinkStateNone;
877 - (void)adjustLinespace:(int)linespace
879 NSMutableData *data = [NSMutableData data];
880 [data appendBytes:&linespace length:sizeof(int)];
881 [self queueMessage:AdjustLinespaceMsgID data:data];
886 [self queueMessage:ActivateMsgID data:nil];
889 - (void)setServerName:(NSString *)name
891 NSData *data = [name dataUsingEncoding:NSUTF8StringEncoding];
892 [self queueMessage:SetServerNameMsgID data:data];
895 - (int)lookupColorWithKey:(NSString *)key
897 if (!(key && [key length] > 0))
900 NSString *stripKey = [[[[key lowercaseString]
901 stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]
902 componentsSeparatedByString:@" "]
903 componentsJoinedByString:@""];
905 if (stripKey && [stripKey length] > 0) {
906 // First of all try to lookup key in the color dictionary; note that
907 // all keys in this dictionary are lowercase with no whitespace.
908 id obj = [colorDict objectForKey:stripKey];
909 if (obj) return [obj intValue];
911 // The key was not in the dictionary; is it perhaps of the form
913 if ([stripKey length] > 1 && [stripKey characterAtIndex:0] == '#') {
914 NSScanner *scanner = [NSScanner scannerWithString:stripKey];
915 [scanner setScanLocation:1];
917 if ([scanner scanHexInt:&hex]) {
922 // As a last resort, check if it is one of the system defined colors.
923 // The keys in this dictionary are also lowercase with no whitespace.
924 obj = [sysColorDict objectForKey:stripKey];
926 NSColor *col = [NSColor performSelector:NSSelectorFromString(obj)];
929 col = [col colorUsingColorSpaceName:NSCalibratedRGBColorSpace];
930 [col getRed:&r green:&g blue:&b alpha:&a];
931 return (((int)(r*255+.5f) & 0xff) << 16)
932 + (((int)(g*255+.5f) & 0xff) << 8)
933 + ((int)(b*255+.5f) & 0xff);
938 NSLog(@"WARNING: No color with key %@ found.", stripKey);
942 - (BOOL)hasSpecialKeyWithValue:(NSString *)value
944 NSEnumerator *e = [[MMBackend specialKeys] objectEnumerator];
947 while ((obj = [e nextObject])) {
948 if ([value isEqual:obj])
955 - (oneway void)processInput:(int)msgid data:(in NSData *)data
957 // NOTE: This method might get called whenever the run loop is tended to.
958 // Thus it might get called whilst input is being processed. Normally this
959 // is not a problem, but if it gets called often then it might become
960 // dangerous. E.g. when a focus messages is received the screen is redrawn
961 // because the selection color changes and if another focus message is
962 // received whilst the first one is being processed Vim might crash. To
963 // deal with this problem at the moment, we simply drop messages that are
964 // received while other input is being processed.
965 if (inProcessInput) {
966 #if MM_USE_INPUT_QUEUE
967 [inputQueue addObject:[NSNumber numberWithInt:msgid]];
968 [inputQueue addObject:data];
970 // Just drop the input
971 //NSLog(@"WARNING: Dropping input in %s", _cmd);
974 [self processInputBegin];
975 [self handleMessage:msgid data:data];
976 [self processInputEnd];
980 - (oneway void)processInputAndData:(in NSArray *)messages
982 // NOTE: See comment in processInput:data:.
983 unsigned i, count = [messages count];
985 NSLog(@"WARNING: [messages count] is odd in %s", _cmd);
989 if (inProcessInput) {
990 #if MM_USE_INPUT_QUEUE
991 [inputQueue addObjectsFromArray:messages];
993 // Just drop the input
994 //NSLog(@"WARNING: Dropping input in %s", _cmd);
997 [self processInputBegin];
999 for (i = 0; i < count; i += 2) {
1000 int msgid = [[messages objectAtIndex:i] intValue];
1001 id data = [messages objectAtIndex:i+1];
1002 if ([data isEqual:[NSNull null]])
1005 [self handleMessage:msgid data:data];
1008 [self processInputEnd];
1012 - (BOOL)checkForModifiedBuffers
1015 for (buf = firstbuf; buf != NULL; buf = buf->b_next) {
1016 if (bufIsChanged(buf)) {
1024 - (BOOL)starRegisterToPasteboard:(byref NSPasteboard *)pboard
1026 if (VIsual_active && (State & NORMAL) && clip_star.available) {
1027 // If there is no pasteboard, return YES to indicate that there is text
1032 clip_copy_selection();
1034 // Get the text to put on the pasteboard.
1035 long_u len = 0; char_u *str = 0;
1036 int type = clip_convert_selection(&str, &len, &clip_star);
1040 NSString *string = [[NSString alloc]
1041 initWithBytes:str length:len encoding:NSUTF8StringEncoding];
1043 NSArray *types = [NSArray arrayWithObject:NSStringPboardType];
1044 [pboard declareTypes:types owner:nil];
1045 BOOL ok = [pboard setString:string forType:NSStringPboardType];
1056 - (oneway void)addReply:(in bycopy NSString *)reply
1057 server:(in byref id <MMVimServerProtocol>)server
1059 //NSLog(@"addReply:%@ server:%@", reply, (id)server);
1061 // Replies might come at any time and in any order so we keep them in an
1062 // array inside a dictionary with the send port used as key.
1064 NSConnection *conn = [(NSDistantObject*)server connectionForProxy];
1065 // HACK! Assume connection uses mach ports.
1066 int port = [(NSMachPort*)[conn sendPort] machPort];
1067 NSNumber *key = [NSNumber numberWithInt:port];
1069 NSMutableArray *replies = [serverReplyDict objectForKey:key];
1071 replies = [NSMutableArray array];
1072 [serverReplyDict setObject:replies forKey:key];
1075 [replies addObject:reply];
1078 - (void)addInput:(in bycopy NSString *)input
1079 client:(in byref id <MMVimClientProtocol>)client
1081 //NSLog(@"addInput:%@ client:%@", input, (id)client);
1083 server_to_input_buf((char_u*)[input UTF8String]);
1085 [self addClient:(id)client];
1087 inputReceived = YES;
1090 - (NSString *)evaluateExpression:(in bycopy NSString *)expr
1091 client:(in byref id <MMVimClientProtocol>)client
1093 //NSLog(@"evaluateExpression:%@ client:%@", expr, (id)client);
1095 NSString *eval = nil;
1096 char_u *res = eval_client_expr_to_string((char_u*)[expr UTF8String]);
1099 eval = [NSString stringWithUTF8String:(char*)res];
1103 [self addClient:(id)client];
1108 - (void)registerServerWithName:(NSString *)name
1110 NSString *svrName = name;
1111 NSConnection *svrConn = [NSConnection defaultConnection];
1114 for (i = 0; i < MMServerMax; ++i) {
1115 NSString *connName = [self connectionNameFromServerName:svrName];
1117 if ([svrConn registerName:connName]) {
1118 //NSLog(@"Registered server with name: %@", svrName);
1120 // TODO: Set request/reply time-outs to something else?
1122 // Don't wait for requests (time-out means that the message is
1124 [svrConn setRequestTimeout:0];
1125 //[svrConn setReplyTimeout:MMReplyTimeout];
1126 [svrConn setRootObject:self];
1128 // NOTE: 'serverName' is a global variable
1129 serverName = vim_strsave((char_u*)[svrName UTF8String]);
1131 set_vim_var_string(VV_SEND_SERVER, serverName, -1);
1134 need_maketitle = TRUE;
1136 [self queueMessage:SetServerNameMsgID data:
1137 [svrName dataUsingEncoding:NSUTF8StringEncoding]];
1141 svrName = [NSString stringWithFormat:@"%@%d", name, i+1];
1145 - (BOOL)sendToServer:(NSString *)name string:(NSString *)string
1146 reply:(char_u **)reply port:(int *)port expression:(BOOL)expr
1149 // NOTE: If 'name' equals 'serverName' then the request is local (client
1150 // and server are the same). This case is not handled separately, so a
1151 // connection will be set up anyway (this simplifies the code).
1153 NSConnection *conn = [self connectionForServerName:name];
1156 EMSG2(_(e_noserver), [name UTF8String]);
1161 // HACK! Assume connection uses mach ports.
1162 *port = [(NSMachPort*)[conn sendPort] machPort];
1165 id proxy = [conn rootProxy];
1166 [proxy setProtocolForProxy:@protocol(MMVimServerProtocol)];
1170 NSString *eval = [proxy evaluateExpression:string client:self];
1172 *reply = (eval ? vim_strsave((char_u*)[eval UTF8String])
1173 : vim_strsave((char_u*)_(e_invexprmsg)));
1179 [proxy addInput:string client:self];
1182 @catch (NSException *e) {
1183 NSLog(@"WARNING: Caught exception in %s: \"%@\"", _cmd, e);
1190 - (NSArray *)serverList
1192 NSArray *list = nil;
1194 if ([self connection]) {
1195 id proxy = [connection rootProxy];
1196 [proxy setProtocolForProxy:@protocol(MMAppProtocol)];
1199 list = [proxy serverList];
1201 @catch (NSException *e) {
1202 NSLog(@"Exception caught when listing servers: \"%@\"", e);
1205 EMSG(_("E???: No connection to MacVim, server listing not possible."));
1211 - (NSString *)peekForReplyOnPort:(int)port
1213 //NSLog(@"%s%d", _cmd, port);
1215 NSNumber *key = [NSNumber numberWithInt:port];
1216 NSMutableArray *replies = [serverReplyDict objectForKey:key];
1217 if (replies && [replies count]) {
1218 //NSLog(@" %d replies, topmost is: %@", [replies count],
1219 // [replies objectAtIndex:0]);
1220 return [replies objectAtIndex:0];
1223 //NSLog(@" No replies");
1227 - (NSString *)waitForReplyOnPort:(int)port
1229 //NSLog(@"%s%d", _cmd, port);
1231 NSConnection *conn = [self connectionForServerPort:port];
1235 NSNumber *key = [NSNumber numberWithInt:port];
1236 NSMutableArray *replies = nil;
1237 NSString *reply = nil;
1239 // Wait for reply as long as the connection to the server is valid (unless
1240 // user interrupts wait with Ctrl-C).
1241 while (!got_int && [conn isValid] &&
1242 !(replies = [serverReplyDict objectForKey:key])) {
1243 [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
1244 beforeDate:[NSDate distantFuture]];
1248 if ([replies count] > 0) {
1249 reply = [[replies objectAtIndex:0] retain];
1250 //NSLog(@" Got reply: %@", reply);
1251 [replies removeObjectAtIndex:0];
1252 [reply autorelease];
1255 if ([replies count] == 0)
1256 [serverReplyDict removeObjectForKey:key];
1262 - (BOOL)sendReply:(NSString *)reply toPort:(int)port
1264 id client = [clientProxyDict objectForKey:[NSNumber numberWithInt:port]];
1267 //NSLog(@"sendReply:%@ toPort:%d", reply, port);
1268 [client addReply:reply server:self];
1271 @catch (NSException *e) {
1272 NSLog(@"WARNING: Exception caught in %s: \"%@\"", _cmd, e);
1275 EMSG2(_("E???: server2client failed; no client with id 0x%x"), port);
1285 @implementation MMBackend (Private)
1287 - (void)handleMessage:(int)msgid data:(NSData *)data
1289 if (InsertTextMsgID == msgid) {
1291 NSString *key = [[NSString alloc] initWithData:data
1292 encoding:NSUTF8StringEncoding];
1293 char_u *str = (char_u*)[key UTF8String];
1294 int i, len = [key lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
1297 char_u *conv_str = NULL;
1298 if (input_conv.vc_type != CONV_NONE) {
1299 conv_str = string_convert(&input_conv, str, &len);
1305 for (i = 0; i < len; ++i) {
1306 add_to_input_buf(str+i, 1);
1307 if (CSI == str[i]) {
1308 // NOTE: If the converted string contains the byte CSI, then it
1309 // must be followed by the bytes KS_EXTRA, KE_CSI or things
1311 static char_u extra[2] = { KS_EXTRA, KE_CSI };
1312 add_to_input_buf(extra, 2);
1321 } else if (KeyDownMsgID == msgid || CmdKeyMsgID == msgid) {
1323 const void *bytes = [data bytes];
1324 int mods = *((int*)bytes); bytes += sizeof(int);
1325 int len = *((int*)bytes); bytes += sizeof(int);
1326 NSString *key = [[NSString alloc] initWithBytes:bytes length:len
1327 encoding:NSUTF8StringEncoding];
1328 mods = eventModifierFlagsToVimModMask(mods);
1330 [self handleKeyDown:key modifiers:mods];
1333 } else if (SelectTabMsgID == msgid) {
1335 const void *bytes = [data bytes];
1336 int idx = *((int*)bytes) + 1;
1337 //NSLog(@"Selecting tab %d", idx);
1338 send_tabline_event(idx);
1339 } else if (CloseTabMsgID == msgid) {
1341 const void *bytes = [data bytes];
1342 int idx = *((int*)bytes) + 1;
1343 //NSLog(@"Closing tab %d", idx);
1344 send_tabline_menu_event(idx, TABLINE_MENU_CLOSE);
1345 } else if (AddNewTabMsgID == msgid) {
1346 //NSLog(@"Adding new tab");
1347 send_tabline_menu_event(0, TABLINE_MENU_NEW);
1348 } else if (DraggedTabMsgID == msgid) {
1350 const void *bytes = [data bytes];
1351 // NOTE! The destination index is 0 based, so do not add 1 to make it 1
1353 int idx = *((int*)bytes);
1356 } else if (ScrollWheelMsgID == msgid) {
1358 const void *bytes = [data bytes];
1360 int row = *((int*)bytes); bytes += sizeof(int);
1361 int col = *((int*)bytes); bytes += sizeof(int);
1362 int flags = *((int*)bytes); bytes += sizeof(int);
1363 float dy = *((float*)bytes); bytes += sizeof(float);
1365 int button = MOUSE_5;
1366 if (dy > 0) button = MOUSE_4;
1368 flags = eventModifierFlagsToVimMouseModMask(flags);
1370 gui_send_mouse_event(button, col, row, NO, flags);
1371 } else if (MouseDownMsgID == msgid) {
1373 const void *bytes = [data bytes];
1375 int row = *((int*)bytes); bytes += sizeof(int);
1376 int col = *((int*)bytes); bytes += sizeof(int);
1377 int button = *((int*)bytes); bytes += sizeof(int);
1378 int flags = *((int*)bytes); bytes += sizeof(int);
1379 int count = *((int*)bytes); bytes += sizeof(int);
1381 button = eventButtonNumberToVimMouseButton(button);
1382 flags = eventModifierFlagsToVimMouseModMask(flags);
1384 gui_send_mouse_event(button, col, row, 0 != count, flags);
1385 } else if (MouseUpMsgID == msgid) {
1387 const void *bytes = [data bytes];
1389 int row = *((int*)bytes); bytes += sizeof(int);
1390 int col = *((int*)bytes); bytes += sizeof(int);
1391 int flags = *((int*)bytes); bytes += sizeof(int);
1393 flags = eventModifierFlagsToVimMouseModMask(flags);
1395 gui_send_mouse_event(MOUSE_RELEASE, col, row, NO, flags);
1396 } else if (MouseDraggedMsgID == msgid) {
1398 const void *bytes = [data bytes];
1400 int row = *((int*)bytes); bytes += sizeof(int);
1401 int col = *((int*)bytes); bytes += sizeof(int);
1402 int flags = *((int*)bytes); bytes += sizeof(int);
1404 flags = eventModifierFlagsToVimMouseModMask(flags);
1406 gui_send_mouse_event(MOUSE_DRAG, col, row, NO, flags);
1407 } else if (SetTextDimensionsMsgID == msgid) {
1409 const void *bytes = [data bytes];
1410 int rows = *((int*)bytes); bytes += sizeof(int);
1411 int cols = *((int*)bytes); bytes += sizeof(int);
1413 // NOTE! Vim doesn't call gui_mch_set_shellsize() after
1414 // gui_resize_shell(), so we have to manually set the rows and columns
1415 // here. (MacVim doesn't change the rows and columns to avoid
1416 // inconsistent states between Vim and MacVim.)
1417 [self setRows:rows columns:cols];
1419 //NSLog(@"[VimTask] Resizing shell to %dx%d.", cols, rows);
1420 gui_resize_shell(cols, rows);
1421 } else if (ExecuteMenuMsgID == msgid) {
1423 const void *bytes = [data bytes];
1424 int tag = *((int*)bytes); bytes += sizeof(int);
1426 vimmenu_T *menu = (vimmenu_T*)tag;
1427 // TODO! Make sure 'menu' is a valid menu pointer!
1431 } else if (ToggleToolbarMsgID == msgid) {
1432 char_u go[sizeof(GO_ALL)+2];
1437 p = vim_strchr(go, GO_TOOLBAR);
1441 char_u *end = go + len;
1447 go[len] = GO_TOOLBAR;
1451 set_option_value((char_u*)"guioptions", 0, go, 0);
1453 // Force screen redraw (does it have to be this complicated?).
1454 redraw_all_later(CLEAR);
1455 update_screen(NOT_VALID);
1458 gui_update_cursor(FALSE, FALSE);
1460 } else if (ScrollbarEventMsgID == msgid) {
1462 const void *bytes = [data bytes];
1463 long ident = *((long*)bytes); bytes += sizeof(long);
1464 int hitPart = *((int*)bytes); bytes += sizeof(int);
1465 float fval = *((float*)bytes); bytes += sizeof(float);
1466 scrollbar_T *sb = gui_find_scrollbar(ident);
1469 scrollbar_T *sb_info = sb->wp ? &sb->wp->w_scrollbars[0] : sb;
1470 long value = sb_info->value;
1471 long size = sb_info->size;
1472 long max = sb_info->max;
1473 BOOL isStillDragging = NO;
1474 BOOL updateKnob = YES;
1477 case NSScrollerDecrementPage:
1478 value -= (size > 2 ? size - 2 : 1);
1480 case NSScrollerIncrementPage:
1481 value += (size > 2 ? size - 2 : 1);
1483 case NSScrollerDecrementLine:
1486 case NSScrollerIncrementLine:
1489 case NSScrollerKnob:
1490 isStillDragging = YES;
1492 case NSScrollerKnobSlot:
1493 value = (long)(fval * (max - size + 1));
1500 //NSLog(@"value %d -> %d", sb_info->value, value);
1501 gui_drag_scrollbar(sb, value, isStillDragging);
1504 // Dragging the knob or option+clicking automatically updates
1505 // the knob position (on the actual NSScroller), so we only
1506 // need to set the knob position in the other cases.
1508 // Update both the left&right vertical scrollbars.
1509 long identLeft = sb->wp->w_scrollbars[SBAR_LEFT].ident;
1510 long identRight = sb->wp->w_scrollbars[SBAR_RIGHT].ident;
1511 [self setScrollbarThumbValue:value size:size max:max
1512 identifier:identLeft];
1513 [self setScrollbarThumbValue:value size:size max:max
1514 identifier:identRight];
1516 // Update the horizontal scrollbar.
1517 [self setScrollbarThumbValue:value size:size max:max
1522 } else if (SetFontMsgID == msgid) {
1524 const void *bytes = [data bytes];
1525 float pointSize = *((float*)bytes); bytes += sizeof(float);
1526 //unsigned len = *((unsigned*)bytes); bytes += sizeof(unsigned);
1527 bytes += sizeof(unsigned); // len not used
1529 NSMutableString *name = [NSMutableString stringWithUTF8String:bytes];
1530 [name appendString:[NSString stringWithFormat:@":h%.2f", pointSize]];
1532 set_option_value((char_u*)"gfn", 0, (char_u*)[name UTF8String], 0);
1534 // Force screen redraw (does it have to be this complicated?).
1535 redraw_all_later(CLEAR);
1536 update_screen(NOT_VALID);
1539 gui_update_cursor(FALSE, FALSE);
1541 } else if (VimShouldCloseMsgID == msgid) {
1543 } else if (DropFilesMsgID == msgid) {
1545 const void *bytes = [data bytes];
1546 int n = *((int*)bytes); bytes += sizeof(int);
1549 int row = *((int*)bytes); bytes += sizeof(int);
1550 int col = *((int*)bytes); bytes += sizeof(int);
1552 char_u **fnames = (char_u **)alloc(n * sizeof(char_u *));
1554 const void *end = [data bytes] + [data length];
1556 while (bytes < end && i < n) {
1557 int len = *((int*)bytes); bytes += sizeof(int);
1558 fnames[i++] = vim_strnsave((char_u*)bytes, len);
1562 // NOTE! This function will free 'fnames'.
1563 gui_handle_drop(col, row, 0, fnames, i < n ? i : n);
1566 // HACK! I'm not sure how to get Vim to open a list of files in tabs,
1567 // so instead I create a ':tab drop' command with all the files to open
1569 NSMutableString *cmd = (n > 1)
1570 ? [NSMutableString stringWithString:@":tab drop"]
1571 : [NSMutableString stringWithString:@":drop"];
1573 const void *end = [data bytes] + [data length];
1575 for (i = 0; i < n && bytes < end; ++i) {
1576 int len = *((int*)bytes); bytes += sizeof(int);
1577 NSMutableString *file =
1578 [NSMutableString stringWithUTF8String:bytes];
1579 [file replaceOccurrencesOfString:@" "
1582 range:NSMakeRange(0, [file length])];
1585 [cmd appendString:@" "];
1586 [cmd appendString:file];
1589 // By going to the last tabpage we ensure that the new tabs will appear
1590 // last (if this call is left out, the taborder becomes messy).
1593 do_cmdline_cmd((char_u*)[cmd UTF8String]);
1595 // Force screen redraw (does it have to be this complicated?).
1596 // (This code was taken from the end of gui_handle_drop().)
1597 update_screen(NOT_VALID);
1600 gui_update_cursor(FALSE, FALSE);
1604 } else if (DropStringMsgID == msgid) {
1606 char_u dropkey[3] = { CSI, KS_EXTRA, (char_u)KE_DROP };
1607 const void *bytes = [data bytes];
1608 int len = *((int*)bytes); bytes += sizeof(int);
1609 NSMutableString *string = [NSMutableString stringWithUTF8String:bytes];
1611 // Replace unrecognized end-of-line sequences with \x0a (line feed).
1612 NSRange range = { 0, [string length] };
1613 unsigned n = [string replaceOccurrencesOfString:@"\x0d\x0a"
1614 withString:@"\x0a" options:0
1617 n = [string replaceOccurrencesOfString:@"\x0d" withString:@"\x0a"
1618 options:0 range:range];
1621 len = [string lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
1622 dnd_yank_drag_data((char_u*)[string UTF8String], len);
1623 add_to_input_buf(dropkey, sizeof(dropkey));
1625 } else if (GotFocusMsgID == msgid) {
1627 [self focusChange:YES];
1628 } else if (LostFocusMsgID == msgid) {
1630 [self focusChange:NO];
1631 } else if (MouseMovedMsgID == msgid) {
1632 const void *bytes = [data bytes];
1633 int row = *((int*)bytes); bytes += sizeof(int);
1634 int col = *((int*)bytes); bytes += sizeof(int);
1636 gui_mouse_moved(col, row);
1637 } else if (SetMouseShapeMsgID == msgid) {
1638 const void *bytes = [data bytes];
1639 int shape = *((int*)bytes); bytes += sizeof(int);
1640 update_mouseshape(shape);
1642 NSLog(@"WARNING: Unknown message received (msgid=%d)", msgid);
1646 + (NSDictionary *)specialKeys
1648 static NSDictionary *specialKeys = nil;
1651 NSBundle *mainBundle = [NSBundle mainBundle];
1652 NSString *path = [mainBundle pathForResource:@"SpecialKeys"
1654 specialKeys = [[NSDictionary alloc] initWithContentsOfFile:path];
1660 - (void)handleKeyDown:(NSString *)key modifiers:(int)mods
1667 // Special keys (arrow keys, function keys, etc.) are stored in a plist so
1668 // that new keys can easily be added.
1669 NSString *specialString = [[MMBackend specialKeys]
1671 if (specialString && [specialString length] > 1) {
1672 //NSLog(@"special key: %@", specialString);
1673 int ikey = TO_SPECIAL([specialString characterAtIndex:0],
1674 [specialString characterAtIndex:1]);
1676 ikey = simplify_key(ikey, &mods);
1681 special[1] = K_SECOND(ikey);
1682 special[2] = K_THIRD(ikey);
1686 } else if ([key length] > 0) {
1687 chars = (char_u*)[key UTF8String];
1688 length = [key lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
1689 unichar c = [key characterAtIndex:0];
1691 //NSLog(@"non-special: %@ (hex=%x, mods=%d)", key,
1692 // [key characterAtIndex:0], mods);
1694 if (length == 1 && ((c == Ctrl_C && ctrl_c_interrupts)
1695 || (c == intr_char && intr_char != Ctrl_C))) {
1700 // HACK! In most circumstances the Ctrl and Shift modifiers should be
1701 // cleared since they are already added to the key by the AppKit.
1702 // Unfortunately, the only way to deal with when to clear the modifiers
1703 // or not seems to be to have hard-wired rules like this.
1704 if ( !((' ' == c) || (0xa0 == c) || (mods & MOD_MASK_CMD)) ) {
1705 mods &= ~MOD_MASK_SHIFT;
1706 mods &= ~MOD_MASK_CTRL;
1707 //NSLog(@"clear shift ctrl");
1710 // HACK! All Option+key presses go via 'insert text' messages, except
1711 // for <M-Space>. If the Alt flag is not cleared for <M-Space> it does
1712 // not work to map to it.
1713 if (0xa0 == c && !(mods & MOD_MASK_CMD)) {
1714 //NSLog(@"clear alt");
1715 mods &= ~MOD_MASK_ALT;
1719 if (chars && length > 0) {
1721 //NSLog(@"adding mods: %d", mods);
1723 modChars[1] = KS_MODIFIER;
1725 add_to_input_buf(modChars, 3);
1728 //NSLog(@"add to input buf: 0x%x", chars[0]);
1729 // TODO: Check for CSI bytes?
1730 add_to_input_buf(chars, length);
1734 - (void)queueMessage:(int)msgid data:(NSData *)data
1736 [queue addObject:[NSData dataWithBytes:&msgid length:sizeof(int)]];
1738 [queue addObject:data];
1740 [queue addObject:[NSData data]];
1743 - (void)connectionDidDie:(NSNotification *)notification
1745 // If the main connection to MacVim is lost this means that MacVim was
1746 // either quit (by the user chosing Quit on the MacVim menu), or it has
1747 // crashed. In either case our only option is to quit now.
1748 // TODO: Write backup file?
1750 //NSLog(@"A Vim process lots its connection to MacVim; quitting.");
1754 - (void)blinkTimerFired:(NSTimer *)timer
1756 NSTimeInterval timeInterval = 0;
1758 [blinkTimer release];
1761 if (MMBlinkStateOn == blinkState) {
1762 gui_undraw_cursor();
1763 blinkState = MMBlinkStateOff;
1764 timeInterval = blinkOffInterval;
1765 } else if (MMBlinkStateOff == blinkState) {
1766 gui_update_cursor(TRUE, FALSE);
1767 blinkState = MMBlinkStateOn;
1768 timeInterval = blinkOnInterval;
1771 if (timeInterval > 0) {
1773 [[NSTimer scheduledTimerWithTimeInterval:timeInterval target:self
1774 selector:@selector(blinkTimerFired:)
1775 userInfo:nil repeats:NO] retain];
1776 [self flushQueue:YES];
1780 - (void)focusChange:(BOOL)on
1782 gui_focus_change(on);
1785 - (void)processInputBegin
1787 inProcessInput = YES;
1788 [lastFlushDate release];
1789 lastFlushDate = [[NSDate date] retain];
1792 - (void)processInputEnd
1794 #if MM_USE_INPUT_QUEUE
1795 int count = [inputQueue count];
1797 // TODO: This is troubling, but it is not hard to get Vim to end up
1798 // here. Why does this happen?
1799 NSLog(@"WARNING: inputQueue has odd number of objects (%d)", count);
1800 [inputQueue removeAllObjects];
1801 } else if (count > 0) {
1802 // TODO: Dispatch these messages? Maybe not; usually when the
1803 // 'inputQueue' is non-empty it means that a LOT of messages has been
1804 // sent simultaneously. The only way this happens is when Vim is being
1805 // tormented, e.g. if the user holds down <D-`> to rapidly switch
1808 for (i = 0; i < count; i+=2) {
1809 int msgid = [[inputQueue objectAtIndex:i] intValue];
1810 NSLog(@"%s: Dropping message %s", _cmd, MessageStrings[msgid]);
1813 [inputQueue removeAllObjects];
1817 inputReceived = YES;
1818 inProcessInput = NO;
1821 - (NSString *)connectionNameFromServerName:(NSString *)name
1823 NSString *bundleIdentifier = [[NSBundle mainBundle] bundleIdentifier];
1825 return [[NSString stringWithFormat:@"%@.%@", bundleIdentifier, name]
1829 - (NSConnection *)connectionForServerName:(NSString *)name
1831 // TODO: Try 'name%d' if 'name' fails.
1832 NSString *connName = [self connectionNameFromServerName:name];
1833 NSConnection *svrConn = [connectionNameDict objectForKey:connName];
1836 svrConn = [NSConnection connectionWithRegisteredName:connName
1838 // Try alternate server...
1839 if (!svrConn && alternateServerName) {
1840 //NSLog(@" trying to connect to alternate server: %@",
1841 // alternateServerName);
1842 connName = [self connectionNameFromServerName:alternateServerName];
1843 svrConn = [NSConnection connectionWithRegisteredName:connName
1847 // Try looking for alternate servers...
1849 //NSLog(@" looking for alternate servers...");
1850 NSString *alt = [self alternateServerNameForName:name];
1851 if (alt != alternateServerName) {
1852 //NSLog(@" found alternate server: %@", string);
1853 [alternateServerName release];
1854 alternateServerName = [alt copy];
1858 // Try alternate server again...
1859 if (!svrConn && alternateServerName) {
1860 //NSLog(@" trying to connect to alternate server: %@",
1861 // alternateServerName);
1862 connName = [self connectionNameFromServerName:alternateServerName];
1863 svrConn = [NSConnection connectionWithRegisteredName:connName
1868 [connectionNameDict setObject:svrConn forKey:connName];
1870 //NSLog(@"Adding %@ as connection observer for %@", self, svrConn);
1871 [[NSNotificationCenter defaultCenter] addObserver:self
1872 selector:@selector(serverConnectionDidDie:)
1873 name:NSConnectionDidDieNotification object:svrConn];
1880 - (NSConnection *)connectionForServerPort:(int)port
1883 NSEnumerator *e = [connectionNameDict objectEnumerator];
1885 while ((conn = [e nextObject])) {
1886 // HACK! Assume connection uses mach ports.
1887 if (port == [(NSMachPort*)[conn sendPort] machPort])
1894 - (void)serverConnectionDidDie:(NSNotification *)notification
1896 //NSLog(@"%s%@", _cmd, notification);
1898 NSConnection *svrConn = [notification object];
1900 //NSLog(@"Removing %@ as connection observer from %@", self, svrConn);
1901 [[NSNotificationCenter defaultCenter]
1903 name:NSConnectionDidDieNotification
1906 [connectionNameDict removeObjectsForKeys:
1907 [connectionNameDict allKeysForObject:svrConn]];
1909 // HACK! Assume connection uses mach ports.
1910 int port = [(NSMachPort*)[svrConn sendPort] machPort];
1911 NSNumber *key = [NSNumber numberWithInt:port];
1913 [clientProxyDict removeObjectForKey:key];
1914 [serverReplyDict removeObjectForKey:key];
1917 - (void)addClient:(NSDistantObject *)client
1919 NSConnection *conn = [client connectionForProxy];
1920 // HACK! Assume connection uses mach ports.
1921 int port = [(NSMachPort*)[conn sendPort] machPort];
1922 NSNumber *key = [NSNumber numberWithInt:port];
1924 if (![clientProxyDict objectForKey:key]) {
1925 [client setProtocolForProxy:@protocol(MMVimClientProtocol)];
1926 [clientProxyDict setObject:client forKey:key];
1929 // NOTE: 'clientWindow' is a global variable which is used by <client>
1930 clientWindow = port;
1933 - (NSString *)alternateServerNameForName:(NSString *)name
1935 if (!(name && [name length] > 0))
1938 // Only look for alternates if 'name' doesn't end in a digit.
1939 unichar lastChar = [name characterAtIndex:[name length]-1];
1940 if (lastChar >= '0' && lastChar <= '9')
1943 // Look for alternates among all current servers.
1944 NSArray *list = [self serverList];
1945 if (!(list && [list count] > 0))
1948 // Filter out servers starting with 'name' and ending with a number. The
1949 // (?i) pattern ensures that the match is case insensitive.
1950 NSString *pat = [NSString stringWithFormat:@"(?i)%@[0-9]+\\z", name];
1951 NSPredicate *pred = [NSPredicate predicateWithFormat:
1952 @"SELF MATCHES %@", pat];
1953 list = [list filteredArrayUsingPredicate:pred];
1954 if ([list count] > 0) {
1955 list = [list sortedArrayUsingSelector:@selector(serverNameCompare:)];
1956 return [list objectAtIndex:0];
1962 @end // MMBackend (Private)
1967 @implementation NSString (MMServerNameCompare)
1968 - (NSComparisonResult)serverNameCompare:(NSString *)string
1970 return [self compare:string
1971 options:NSCaseInsensitiveSearch|NSNumericSearch];
1978 static int eventModifierFlagsToVimModMask(int modifierFlags)
1982 if (modifierFlags & NSShiftKeyMask)
1983 modMask |= MOD_MASK_SHIFT;
1984 if (modifierFlags & NSControlKeyMask)
1985 modMask |= MOD_MASK_CTRL;
1986 if (modifierFlags & NSAlternateKeyMask)
1987 modMask |= MOD_MASK_ALT;
1988 if (modifierFlags & NSCommandKeyMask)
1989 modMask |= MOD_MASK_CMD;
1994 static int vimModMaskToEventModifierFlags(int mods)
1998 if (mods & MOD_MASK_SHIFT)
1999 flags |= NSShiftKeyMask;
2000 if (mods & MOD_MASK_CTRL)
2001 flags |= NSControlKeyMask;
2002 if (mods & MOD_MASK_ALT)
2003 flags |= NSAlternateKeyMask;
2004 if (mods & MOD_MASK_CMD)
2005 flags |= NSCommandKeyMask;
2010 static int eventModifierFlagsToVimMouseModMask(int modifierFlags)
2014 if (modifierFlags & NSShiftKeyMask)
2015 modMask |= MOUSE_SHIFT;
2016 if (modifierFlags & NSControlKeyMask)
2017 modMask |= MOUSE_CTRL;
2018 if (modifierFlags & NSAlternateKeyMask)
2019 modMask |= MOUSE_ALT;
2024 static int eventButtonNumberToVimMouseButton(int buttonNumber)
2026 static int mouseButton[] = { MOUSE_LEFT, MOUSE_RIGHT, MOUSE_MIDDLE,
2027 MOUSE_X1, MOUSE_X2 };
2029 return mouseButton[buttonNumber < 5 ? buttonNumber : 0];
2032 static int specialKeyToNSKey(int key)
2034 if (!IS_SPECIAL(key))
2041 { K_UP, NSUpArrowFunctionKey },
2042 { K_DOWN, NSDownArrowFunctionKey },
2043 { K_LEFT, NSLeftArrowFunctionKey },
2044 { K_RIGHT, NSRightArrowFunctionKey },
2045 { K_F1, NSF1FunctionKey },
2046 { K_F2, NSF2FunctionKey },
2047 { K_F3, NSF3FunctionKey },
2048 { K_F4, NSF4FunctionKey },
2049 { K_F5, NSF5FunctionKey },
2050 { K_F6, NSF6FunctionKey },
2051 { K_F7, NSF7FunctionKey },
2052 { K_F8, NSF8FunctionKey },
2053 { K_F9, NSF9FunctionKey },
2054 { K_F10, NSF10FunctionKey },
2055 { K_F11, NSF11FunctionKey },
2056 { K_F12, NSF12FunctionKey },
2057 { K_F13, NSF13FunctionKey },
2058 { K_F14, NSF14FunctionKey },
2059 { K_F15, NSF15FunctionKey },
2060 { K_F16, NSF16FunctionKey },
2061 { K_F17, NSF17FunctionKey },
2062 { K_F18, NSF18FunctionKey },
2063 { K_F19, NSF19FunctionKey },
2064 { K_F20, NSF20FunctionKey },
2065 { K_F21, NSF21FunctionKey },
2066 { K_F22, NSF22FunctionKey },
2067 { K_F23, NSF23FunctionKey },
2068 { K_F24, NSF24FunctionKey },
2069 { K_F25, NSF25FunctionKey },
2070 { K_F26, NSF26FunctionKey },
2071 { K_F27, NSF27FunctionKey },
2072 { K_F28, NSF28FunctionKey },
2073 { K_F29, NSF29FunctionKey },
2074 { K_F30, NSF30FunctionKey },
2075 { K_F31, NSF31FunctionKey },
2076 { K_F32, NSF32FunctionKey },
2077 { K_F33, NSF33FunctionKey },
2078 { K_F34, NSF34FunctionKey },
2079 { K_F35, NSF35FunctionKey },
2080 { K_DEL, NSBackspaceCharacter },
2081 { K_BS, NSDeleteCharacter },
2082 { K_HOME, NSHomeFunctionKey },
2083 { K_END, NSEndFunctionKey },
2084 { K_PAGEUP, NSPageUpFunctionKey },
2085 { K_PAGEDOWN, NSPageDownFunctionKey }
2089 for (i = 0; i < sizeof(sp2ns)/sizeof(sp2ns[0]); ++i) {
2090 if (sp2ns[i].special == key)
2091 return sp2ns[i].nskey;