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;
24 // NOTE: The default font is bundled with the application.
25 static NSString *MMDefaultFontName = @"DejaVu Sans Mono";
26 static float MMDefaultFontSize = 12.0f;
28 // TODO: Move to separate file.
29 static int eventModifierFlagsToVimModMask(int modifierFlags);
30 static int vimModMaskToEventModifierFlags(int mods);
31 static int eventModifierFlagsToVimMouseModMask(int modifierFlags);
32 static int eventButtonNumberToVimMouseButton(int buttonNumber);
33 static int specialKeyToNSKey(int key);
43 @interface NSString (MMServerNameCompare)
44 - (NSComparisonResult)serverNameCompare:(NSString *)string;
49 @interface MMBackend (Private)
50 - (void)handleMessage:(int)msgid data:(NSData *)data;
51 + (NSDictionary *)specialKeys;
52 - (void)handleKeyDown:(NSString *)key modifiers:(int)mods;
53 - (void)queueMessage:(int)msgid data:(NSData *)data;
54 - (void)connectionDidDie:(NSNotification *)notification;
55 - (void)blinkTimerFired:(NSTimer *)timer;
56 - (void)focusChange:(BOOL)on;
57 - (void)processInputBegin;
58 - (void)processInputEnd;
59 - (NSString *)connectionNameFromServerName:(NSString *)name;
60 - (NSConnection *)connectionForServerName:(NSString *)name;
61 - (NSConnection *)connectionForServerPort:(int)port;
62 - (void)serverConnectionDidDie:(NSNotification *)notification;
63 - (void)addClient:(NSDistantObject *)client;
64 - (NSString *)alternateServerNameForName:(NSString *)name;
69 @implementation MMBackend
71 + (MMBackend *)sharedInstance
73 static MMBackend *singleton = nil;
74 return singleton ? singleton : (singleton = [MMBackend new]);
79 if ((self = [super init])) {
80 fontContainerRef = loadFonts();
82 queue = [[NSMutableArray alloc] init];
83 #if MM_USE_INPUT_QUEUE
84 inputQueue = [[NSMutableArray alloc] init];
86 drawData = [[NSMutableData alloc] initWithCapacity:1024];
87 connectionNameDict = [[NSMutableDictionary alloc] init];
88 clientProxyDict = [[NSMutableDictionary alloc] init];
89 serverReplyDict = [[NSMutableDictionary alloc] init];
91 NSString *path = [[NSBundle mainBundle] pathForResource:@"Colors"
94 colorDict = [[NSDictionary dictionaryWithContentsOfFile:path]
97 NSLog(@"WARNING: Could not locate Colors.plist.");
100 path = [[NSBundle mainBundle] pathForResource:@"SystemColors"
103 sysColorDict = [[NSDictionary dictionaryWithContentsOfFile:path]
106 NSLog(@"WARNING: Could not locate SystemColors.plist.");
115 //NSLog(@"%@ %s", [self className], _cmd);
117 [[NSNotificationCenter defaultCenter] removeObserver:self];
119 [blinkTimer release]; blinkTimer = nil;
120 #if MM_USE_INPUT_QUEUE
121 [inputQueue release]; inputQueue = nil;
123 [alternateServerName release]; alternateServerName = nil;
124 [serverReplyDict release]; serverReplyDict = nil;
125 [clientProxyDict release]; clientProxyDict = nil;
126 [connectionNameDict release]; connectionNameDict = nil;
127 [queue release]; queue = nil;
128 [drawData release]; drawData = nil;
129 [frontendProxy release]; frontendProxy = nil;
130 [connection release]; connection = nil;
131 [sysColorDict release]; sysColorDict = nil;
132 [colorDict release]; colorDict = nil;
137 - (void)setBackgroundColor:(int)color
139 backgroundColor = color;
142 - (void)setForegroundColor:(int)color
144 foregroundColor = color;
147 - (void)setSpecialColor:(int)color
149 specialColor = color;
152 - (void)setDefaultColorsBackground:(int)bg foreground:(int)fg
154 defaultBackgroundColor = bg;
155 defaultForegroundColor = fg;
157 NSMutableData *data = [NSMutableData data];
159 [data appendBytes:&bg length:sizeof(int)];
160 [data appendBytes:&fg length:sizeof(int)];
162 [self queueMessage:SetDefaultColorsMsgID data:data];
165 - (NSConnection *)connection
168 // NOTE! If the name of the connection changes here it must also be
169 // updated in MMAppController.m.
170 NSString *name = [NSString stringWithFormat:@"%@-connection",
171 [[NSBundle mainBundle] bundleIdentifier]];
173 connection = [NSConnection connectionWithRegisteredName:name host:nil];
177 // NOTE: 'connection' may be nil here.
183 if (![self connection]) {
184 NSBundle *mainBundle = [NSBundle mainBundle];
186 NSString *path = [mainBundle bundlePath];
187 if (![[NSWorkspace sharedWorkspace] launchApplication:path]) {
188 NSLog(@"WARNING: Failed to launch GUI with path %@", path);
192 // HACK! It would be preferable to launch the GUI using NSWorkspace,
193 // however I have not managed to figure out how to pass arguments using
196 // NOTE! Using NSTask to launch the GUI has the negative side-effect
197 // that the GUI won't be activated (or raised) so there is a hack in
198 // MMWindowController which always raises the app when a new window is
200 NSMutableArray *args = [NSMutableArray arrayWithObjects:
201 [NSString stringWithFormat:@"-%@", MMNoWindowKey], @"yes", nil];
202 NSString *exeName = [[mainBundle infoDictionary]
203 objectForKey:@"CFBundleExecutable"];
204 NSString *path = [mainBundle pathForAuxiliaryExecutable:exeName];
206 NSLog(@"ERROR: Could not find MacVim executable in bundle");
210 [NSTask launchedTaskWithLaunchPath:path arguments:args];
213 // HACK! The NSWorkspaceDidLaunchApplicationNotification does not work
214 // for tasks like this, so poll the mach bootstrap server until it
215 // returns a valid connection. Also set a time-out date so that we
216 // don't get stuck doing this forever.
217 NSDate *timeOutDate = [NSDate dateWithTimeIntervalSinceNow:15];
218 while (!connection &&
219 NSOrderedDescending == [timeOutDate compare:[NSDate date]])
221 [[NSRunLoop currentRunLoop]
222 runMode:NSDefaultRunLoopMode
223 beforeDate:[NSDate dateWithTimeIntervalSinceNow:1]];
225 // NOTE: This call will set 'connection' as a side-effect.
230 NSLog(@"WARNING: Timed-out waiting for GUI to launch.");
235 id proxy = [connection rootProxy];
236 [proxy setProtocolForProxy:@protocol(MMAppProtocol)];
238 [[NSNotificationCenter defaultCenter] addObserver:self
239 selector:@selector(connectionDidDie:)
240 name:NSConnectionDidDieNotification object:connection];
242 int pid = [[NSProcessInfo processInfo] processIdentifier];
245 frontendProxy = [proxy connectBackend:self pid:pid];
247 @catch (NSException *e) {
248 NSLog(@"Exception caught when trying to connect backend: \"%@\"", e);
252 [frontendProxy retain];
253 [frontendProxy setProtocolForProxy:@protocol(MMAppProtocol)];
256 return connection && frontendProxy;
259 - (BOOL)openVimWindow
261 [self queueMessage:OpenVimWindowMsgID data:nil];
267 int type = ClearAllDrawType;
269 // Any draw commands in queue are effectively obsolete since this clearAll
270 // will negate any effect they have, therefore we may as well clear the
272 [drawData setLength:0];
274 [drawData appendBytes:&type length:sizeof(int)];
276 [drawData appendBytes:&defaultBackgroundColor length:sizeof(int)];
279 - (void)clearBlockFromRow:(int)row1 column:(int)col1
280 toRow:(int)row2 column:(int)col2
282 int type = ClearBlockDrawType;
284 [drawData appendBytes:&type length:sizeof(int)];
286 [drawData appendBytes:&defaultBackgroundColor length:sizeof(int)];
287 [drawData appendBytes:&row1 length:sizeof(int)];
288 [drawData appendBytes:&col1 length:sizeof(int)];
289 [drawData appendBytes:&row2 length:sizeof(int)];
290 [drawData appendBytes:&col2 length:sizeof(int)];
293 - (void)deleteLinesFromRow:(int)row count:(int)count
294 scrollBottom:(int)bottom left:(int)left right:(int)right
296 int type = DeleteLinesDrawType;
298 [drawData appendBytes:&type length:sizeof(int)];
300 [drawData appendBytes:&defaultBackgroundColor length:sizeof(int)];
301 [drawData appendBytes:&row length:sizeof(int)];
302 [drawData appendBytes:&count length:sizeof(int)];
303 [drawData appendBytes:&bottom length:sizeof(int)];
304 [drawData appendBytes:&left length:sizeof(int)];
305 [drawData appendBytes:&right length:sizeof(int)];
308 - (void)replaceString:(char*)s length:(int)len row:(int)row column:(int)col
311 if (len <= 0) return;
313 int type = ReplaceStringDrawType;
315 [drawData appendBytes:&type length:sizeof(int)];
317 [drawData appendBytes:&backgroundColor length:sizeof(int)];
318 [drawData appendBytes:&foregroundColor length:sizeof(int)];
319 [drawData appendBytes:&specialColor length:sizeof(int)];
320 [drawData appendBytes:&row length:sizeof(int)];
321 [drawData appendBytes:&col length:sizeof(int)];
322 [drawData appendBytes:&flags length:sizeof(int)];
323 [drawData appendBytes:&len length:sizeof(int)];
324 [drawData appendBytes:s length:len];
327 - (void)insertLinesFromRow:(int)row count:(int)count
328 scrollBottom:(int)bottom left:(int)left right:(int)right
330 int type = InsertLinesDrawType;
332 [drawData appendBytes:&type length:sizeof(int)];
334 [drawData appendBytes:&defaultBackgroundColor length:sizeof(int)];
335 [drawData appendBytes:&row length:sizeof(int)];
336 [drawData appendBytes:&count length:sizeof(int)];
337 [drawData appendBytes:&bottom length:sizeof(int)];
338 [drawData appendBytes:&left length:sizeof(int)];
339 [drawData appendBytes:&right length:sizeof(int)];
342 - (void)drawCursorAtRow:(int)row column:(int)col shape:(int)shape
343 fraction:(int)percent color:(int)color
345 int type = DrawCursorDrawType;
347 [drawData appendBytes:&type length:sizeof(int)];
349 [drawData appendBytes:&color length:sizeof(int)];
350 [drawData appendBytes:&row length:sizeof(int)];
351 [drawData appendBytes:&col length:sizeof(int)];
352 [drawData appendBytes:&shape length:sizeof(int)];
353 [drawData appendBytes:&percent length:sizeof(int)];
356 - (void)flushQueue:(BOOL)force
358 // NOTE! This method gets called a lot; if we were to flush every time it
359 // was called MacVim would feel unresponsive. So there is a time out which
360 // ensures that the queue isn't flushed too often.
361 if (!force && lastFlushDate && -[lastFlushDate timeIntervalSinceNow]
362 < MMFlushTimeoutInterval)
365 if ([drawData length] > 0) {
366 [self queueMessage:BatchDrawMsgID data:[drawData copy]];
367 [drawData setLength:0];
370 if ([queue count] > 0) {
372 [frontendProxy processCommandQueue:queue];
374 @catch (NSException *e) {
375 NSLog(@"Exception caught when processing command queue: \"%@\"", e);
378 [queue removeAllObjects];
380 [lastFlushDate release];
381 lastFlushDate = [[NSDate date] retain];
385 - (BOOL)waitForInput:(int)milliseconds
387 NSDate *date = milliseconds > 0 ?
388 [NSDate dateWithTimeIntervalSinceNow:.001*milliseconds] :
389 [NSDate distantFuture];
391 [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:date];
393 // I know of no way to figure out if the run loop exited because input was
394 // found or because of a time out, so I need to manually indicate when
395 // input was received in processInput:data: and then reset it every time
397 BOOL yn = inputReceived;
405 #ifdef MAC_CLIENTSERVER
406 // The default connection is used for the client/server code.
407 [[NSConnection defaultConnection] setRootObject:nil];
408 [[NSConnection defaultConnection] invalidate];
411 // By invalidating the NSConnection the MMWindowController immediately
412 // finds out that the connection is down and as a result
413 // [MMWindowController connectionDidDie:] is invoked.
414 //NSLog(@"%@ %s", [self className], _cmd);
415 [[NSNotificationCenter defaultCenter] removeObserver:self];
416 [connection invalidate];
418 if (fontContainerRef) {
419 ATSFontDeactivate(fontContainerRef, NULL, kATSOptionFlagsDefault);
420 fontContainerRef = 0;
425 - (void)selectTab:(int)index
427 //NSLog(@"%s%d", _cmd, index);
430 NSData *data = [NSData dataWithBytes:&index length:sizeof(int)];
431 [self queueMessage:SelectTabMsgID data:data];
436 //NSLog(@"%s", _cmd);
438 NSMutableData *data = [NSMutableData data];
440 int idx = tabpage_index(curtab) - 1;
441 [data appendBytes:&idx length:sizeof(int)];
444 for (tp = first_tabpage; tp != NULL; tp = tp->tp_next) {
445 // This function puts the label of the tab in the global 'NameBuff'.
446 get_tabline_label(tp, FALSE);
447 int len = strlen((char*)NameBuff);
448 if (len <= 0) continue;
450 // Count the number of windows in the tabpage.
451 //win_T *wp = tp->tp_firstwin;
453 //for (wincount = 0; wp != NULL; wp = wp->w_next, ++wincount);
455 //[data appendBytes:&wincount length:sizeof(int)];
456 [data appendBytes:&len length:sizeof(int)];
457 [data appendBytes:NameBuff length:len];
460 [self queueMessage:UpdateTabBarMsgID data:data];
463 - (BOOL)tabBarVisible
465 return tabBarVisible;
468 - (void)showTabBar:(BOOL)enable
470 tabBarVisible = enable;
472 int msgid = enable ? ShowTabBarMsgID : HideTabBarMsgID;
473 [self queueMessage:msgid data:nil];
476 - (void)setRows:(int)rows columns:(int)cols
478 //NSLog(@"[VimTask] setRows:%d columns:%d", rows, cols);
480 int dim[] = { rows, cols };
481 NSData *data = [NSData dataWithBytes:&dim length:2*sizeof(int)];
483 [self queueMessage:SetTextDimensionsMsgID data:data];
486 - (void)setVimWindowTitle:(char *)title
488 NSMutableData *data = [NSMutableData data];
489 int len = strlen(title);
490 if (len <= 0) return;
492 [data appendBytes:&len length:sizeof(int)];
493 [data appendBytes:title length:len];
495 [self queueMessage:SetVimWindowTitleMsgID data:data];
498 - (char *)browseForFileInDirectory:(char *)dir title:(char *)title
501 //NSLog(@"browseForFileInDirectory:%s title:%s saving:%d", dir, title,
506 ? [NSString stringWithCString:dir encoding:NSUTF8StringEncoding]
509 ? [NSString stringWithCString:title encoding:NSUTF8StringEncoding]
512 [frontendProxy showSavePanelForDirectory:ds title:ts saving:saving];
514 // Wait until a reply is sent from MMVimController.
515 [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
516 beforeDate:[NSDate distantFuture]];
518 if (dialogReturn && [dialogReturn isKindOfClass:[NSString class]]) {
519 s = vim_strsave((char_u*)[dialogReturn UTF8String]);
522 [dialogReturn release]; dialogReturn = nil;
524 @catch (NSException *e) {
525 NSLog(@"Exception caught when showing save panel: \"%@\"", e);
531 - (oneway void)setDialogReturn:(in bycopy id)obj
533 // NOTE: This is called by
534 // - [MMVimController panelDidEnd:::], and
535 // - [MMVimController alertDidEnd:::],
536 // to indicate that a save/open panel or alert has finished.
538 if (obj != dialogReturn) {
539 [dialogReturn release];
540 dialogReturn = [obj retain];
544 - (int)presentDialogWithType:(int)type title:(char *)title message:(char *)msg
545 buttons:(char *)btns textField:(char *)txtfield
548 NSString *message = nil, *text = nil, *textFieldString = nil;
549 NSArray *buttons = nil;
550 int style = NSInformationalAlertStyle;
552 if (VIM_WARNING == type) style = NSWarningAlertStyle;
553 else if (VIM_ERROR == type) style = NSCriticalAlertStyle;
556 NSString *btnString = [NSString stringWithUTF8String:btns];
557 buttons = [btnString componentsSeparatedByString:@"\n"];
560 message = [NSString stringWithUTF8String:title];
562 text = [NSString stringWithUTF8String:msg];
564 // HACK! If there is a '\n\n' or '\n' sequence in the message, then
565 // make the part up to there into the title. We only do this
566 // because Vim has lots of dialogs without a title and they look
568 // TODO: Fix the actual dialog texts.
569 NSRange eolRange = [text rangeOfString:@"\n\n"];
570 if (NSNotFound == eolRange.location)
571 eolRange = [text rangeOfString:@"\n"];
572 if (NSNotFound != eolRange.location) {
573 message = [text substringToIndex:eolRange.location];
574 text = [text substringFromIndex:NSMaxRange(eolRange)];
579 textFieldString = [NSString stringWithUTF8String:txtfield];
582 [frontendProxy presentDialogWithStyle:style message:message
583 informativeText:text buttonTitles:buttons
584 textFieldString:textFieldString];
586 // Wait until a reply is sent from MMVimController.
587 [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
588 beforeDate:[NSDate distantFuture]];
590 if (dialogReturn && [dialogReturn isKindOfClass:[NSArray class]]
591 && [dialogReturn count]) {
592 retval = [[dialogReturn objectAtIndex:0] intValue];
593 if (txtfield && [dialogReturn count] > 1) {
594 NSString *retString = [dialogReturn objectAtIndex:1];
595 vim_strncpy((char_u*)txtfield, (char_u*)[retString UTF8String],
600 [dialogReturn release]; dialogReturn = nil;
602 @catch (NSException *e) {
603 NSLog(@"Exception caught while showing alert dialog: \"%@\"", e);
609 - (void)addMenuWithTag:(int)tag parent:(int)parentTag name:(char *)name
612 //NSLog(@"addMenuWithTag:%d parent:%d name:%s atIndex:%d", tag, parentTag,
615 int namelen = name ? strlen(name) : 0;
616 NSMutableData *data = [NSMutableData data];
618 [data appendBytes:&tag length:sizeof(int)];
619 [data appendBytes:&parentTag length:sizeof(int)];
620 [data appendBytes:&namelen length:sizeof(int)];
621 if (namelen > 0) [data appendBytes:name length:namelen];
622 [data appendBytes:&index length:sizeof(int)];
624 [self queueMessage:AddMenuMsgID data:data];
627 - (void)addMenuItemWithTag:(int)tag parent:(int)parentTag name:(char *)name
628 tip:(char *)tip icon:(char *)icon
629 keyEquivalent:(int)key modifiers:(int)mods
630 action:(NSString *)action atIndex:(int)index
632 //NSLog(@"addMenuItemWithTag:%d parent:%d name:%s tip:%s atIndex:%d", tag,
633 // parentTag, name, tip, index);
635 int namelen = name ? strlen(name) : 0;
636 int tiplen = tip ? strlen(tip) : 0;
637 int iconlen = icon ? strlen(icon) : 0;
638 int eventFlags = vimModMaskToEventModifierFlags(mods);
639 int actionlen = [action lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
640 NSMutableData *data = [NSMutableData data];
642 key = specialKeyToNSKey(key);
644 [data appendBytes:&tag length:sizeof(int)];
645 [data appendBytes:&parentTag length:sizeof(int)];
646 [data appendBytes:&namelen length:sizeof(int)];
647 if (namelen > 0) [data appendBytes:name length:namelen];
648 [data appendBytes:&tiplen length:sizeof(int)];
649 if (tiplen > 0) [data appendBytes:tip length:tiplen];
650 [data appendBytes:&iconlen length:sizeof(int)];
651 if (iconlen > 0) [data appendBytes:icon length:iconlen];
652 [data appendBytes:&actionlen length:sizeof(int)];
653 if (actionlen > 0) [data appendBytes:[action UTF8String] length:actionlen];
654 [data appendBytes:&index length:sizeof(int)];
655 [data appendBytes:&key length:sizeof(int)];
656 [data appendBytes:&eventFlags length:sizeof(int)];
658 [self queueMessage:AddMenuItemMsgID data:data];
661 - (void)removeMenuItemWithTag:(int)tag
663 NSMutableData *data = [NSMutableData data];
664 [data appendBytes:&tag length:sizeof(int)];
666 [self queueMessage:RemoveMenuItemMsgID data:data];
669 - (void)enableMenuItemWithTag:(int)tag state:(int)enabled
671 NSMutableData *data = [NSMutableData data];
673 [data appendBytes:&tag length:sizeof(int)];
674 [data appendBytes:&enabled length:sizeof(int)];
676 [self queueMessage:EnableMenuItemMsgID data:data];
679 - (void)showPopupMenuWithName:(char *)name atMouseLocation:(BOOL)mouse
681 int len = strlen(name);
682 int row = -1, col = -1;
684 if (len <= 0) return;
686 if (!mouse && curwin) {
687 row = curwin->w_wrow;
688 col = curwin->w_wcol;
691 NSMutableData *data = [NSMutableData data];
693 [data appendBytes:&row length:sizeof(int)];
694 [data appendBytes:&col length:sizeof(int)];
695 [data appendBytes:&len length:sizeof(int)];
696 [data appendBytes:name length:len];
698 [self queueMessage:ShowPopupMenuMsgID data:data];
701 - (void)showToolbar:(int)enable flags:(int)flags
703 NSMutableData *data = [NSMutableData data];
705 [data appendBytes:&enable length:sizeof(int)];
706 [data appendBytes:&flags length:sizeof(int)];
708 [self queueMessage:ShowToolbarMsgID data:data];
711 - (void)createScrollbarWithIdentifier:(long)ident type:(int)type
713 NSMutableData *data = [NSMutableData data];
715 [data appendBytes:&ident length:sizeof(long)];
716 [data appendBytes:&type length:sizeof(int)];
718 [self queueMessage:CreateScrollbarMsgID data:data];
721 - (void)destroyScrollbarWithIdentifier:(long)ident
723 NSMutableData *data = [NSMutableData data];
724 [data appendBytes:&ident length:sizeof(long)];
726 [self queueMessage:DestroyScrollbarMsgID data:data];
729 - (void)showScrollbarWithIdentifier:(long)ident state:(int)visible
731 NSMutableData *data = [NSMutableData data];
733 [data appendBytes:&ident length:sizeof(long)];
734 [data appendBytes:&visible length:sizeof(int)];
736 [self queueMessage:ShowScrollbarMsgID data:data];
739 - (void)setScrollbarPosition:(int)pos length:(int)len identifier:(long)ident
741 NSMutableData *data = [NSMutableData data];
743 [data appendBytes:&ident length:sizeof(long)];
744 [data appendBytes:&pos length:sizeof(int)];
745 [data appendBytes:&len length:sizeof(int)];
747 [self queueMessage:SetScrollbarPositionMsgID data:data];
750 - (void)setScrollbarThumbValue:(long)val size:(long)size max:(long)max
751 identifier:(long)ident
753 float fval = max-size+1 > 0 ? (float)val/(max-size+1) : 0;
754 float prop = (float)size/(max+1);
755 if (fval < 0) fval = 0;
756 else if (fval > 1.0f) fval = 1.0f;
757 if (prop < 0) prop = 0;
758 else if (prop > 1.0f) prop = 1.0f;
760 NSMutableData *data = [NSMutableData data];
762 [data appendBytes:&ident length:sizeof(long)];
763 [data appendBytes:&fval length:sizeof(float)];
764 [data appendBytes:&prop length:sizeof(float)];
766 [self queueMessage:SetScrollbarThumbMsgID data:data];
769 - (BOOL)setFontWithName:(char *)name
771 NSString *fontName = MMDefaultFontName;
772 float size = MMDefaultFontSize;
773 BOOL parseFailed = NO;
776 fontName = [[[NSString alloc] initWithCString:name
777 encoding:NSUTF8StringEncoding] autorelease];
779 if ([fontName isEqual:@"*"]) {
780 // :set gfn=* shows the font panel.
781 do_cmdline_cmd((char_u*)":action orderFrontFontPanel:");
785 NSArray *components = [fontName componentsSeparatedByString:@":"];
786 if ([components count] == 2) {
787 NSString *sizeString = [components lastObject];
788 if ([sizeString length] > 0
789 && [sizeString characterAtIndex:0] == 'h') {
790 sizeString = [sizeString substringFromIndex:1];
791 if ([sizeString length] > 0) {
792 size = [sizeString floatValue];
793 fontName = [components objectAtIndex:0];
798 } else if ([components count] > 2) {
803 if (!parseFailed && [fontName length] > 0) {
804 if (size < 6 || size > 100) {
805 // Font size 0.0 tells NSFont to use the 'user default size'.
809 NSFont *font = [NSFont fontWithName:fontName size:size];
811 if (!font && MMDefaultFontName == fontName) {
812 // If for some reason the MacVim default font is not in the app
813 // bundle, then fall back on the system default font.
815 font = [NSFont userFixedPitchFontOfSize:size];
816 fontName = [font displayName];
820 //NSLog(@"Setting font '%@' of size %.2f", fontName, size);
822 lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
824 NSMutableData *data = [NSMutableData data];
826 [data appendBytes:&size length:sizeof(float)];
827 [data appendBytes:&len length:sizeof(int)];
828 [data appendBytes:[fontName UTF8String] length:len];
830 [self queueMessage:SetFontMsgID data:data];
836 //NSLog(@"WARNING: Cannot set font with name '%@' of size %.2f",
841 - (void)executeActionWithName:(NSString *)name
843 int len = [name lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
846 NSMutableData *data = [NSMutableData data];
848 [data appendBytes:&len length:sizeof(int)];
849 [data appendBytes:[name UTF8String] length:len];
851 [self queueMessage:ExecuteActionMsgID data:data];
855 - (void)setMouseShape:(int)shape
857 NSMutableData *data = [NSMutableData data];
858 [data appendBytes:&shape length:sizeof(int)];
859 [self queueMessage:SetMouseShapeMsgID data:data];
862 - (void)setBlinkWait:(int)wait on:(int)on off:(int)off
864 // Vim specifies times in milliseconds, whereas Cocoa wants them in
866 blinkWaitInterval = .001f*wait;
867 blinkOnInterval = .001f*on;
868 blinkOffInterval = .001f*off;
874 [blinkTimer invalidate];
875 [blinkTimer release];
879 if (blinkWaitInterval > 0 && blinkOnInterval > 0 && blinkOffInterval > 0
881 blinkState = MMBlinkStateOn;
883 [[NSTimer scheduledTimerWithTimeInterval:blinkWaitInterval
885 selector:@selector(blinkTimerFired:)
886 userInfo:nil repeats:NO] retain];
887 gui_update_cursor(TRUE, FALSE);
888 [self flushQueue:YES];
894 if (MMBlinkStateOff == blinkState) {
895 gui_update_cursor(TRUE, FALSE);
896 [self flushQueue:YES];
899 blinkState = MMBlinkStateNone;
902 - (void)adjustLinespace:(int)linespace
904 NSMutableData *data = [NSMutableData data];
905 [data appendBytes:&linespace length:sizeof(int)];
906 [self queueMessage:AdjustLinespaceMsgID data:data];
911 [self queueMessage:ActivateMsgID data:nil];
914 - (void)setServerName:(NSString *)name
916 NSData *data = [name dataUsingEncoding:NSUTF8StringEncoding];
917 [self queueMessage:SetServerNameMsgID data:data];
920 - (int)lookupColorWithKey:(NSString *)key
922 if (!(key && [key length] > 0))
925 NSString *stripKey = [[[[key lowercaseString]
926 stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]
927 componentsSeparatedByString:@" "]
928 componentsJoinedByString:@""];
930 if (stripKey && [stripKey length] > 0) {
931 // First of all try to lookup key in the color dictionary; note that
932 // all keys in this dictionary are lowercase with no whitespace.
933 id obj = [colorDict objectForKey:stripKey];
934 if (obj) return [obj intValue];
936 // The key was not in the dictionary; is it perhaps of the form
938 if ([stripKey length] > 1 && [stripKey characterAtIndex:0] == '#') {
939 NSScanner *scanner = [NSScanner scannerWithString:stripKey];
940 [scanner setScanLocation:1];
942 if ([scanner scanHexInt:&hex]) {
947 // As a last resort, check if it is one of the system defined colors.
948 // The keys in this dictionary are also lowercase with no whitespace.
949 obj = [sysColorDict objectForKey:stripKey];
951 NSColor *col = [NSColor performSelector:NSSelectorFromString(obj)];
954 col = [col colorUsingColorSpaceName:NSCalibratedRGBColorSpace];
955 [col getRed:&r green:&g blue:&b alpha:&a];
956 return (((int)(r*255+.5f) & 0xff) << 16)
957 + (((int)(g*255+.5f) & 0xff) << 8)
958 + ((int)(b*255+.5f) & 0xff);
963 NSLog(@"WARNING: No color with key %@ found.", stripKey);
967 - (BOOL)hasSpecialKeyWithValue:(NSString *)value
969 NSEnumerator *e = [[MMBackend specialKeys] objectEnumerator];
972 while ((obj = [e nextObject])) {
973 if ([value isEqual:obj])
980 - (oneway void)processInput:(int)msgid data:(in NSData *)data
982 // NOTE: This method might get called whenever the run loop is tended to.
983 // Thus it might get called whilst input is being processed. Normally this
984 // is not a problem, but if it gets called often then it might become
985 // dangerous. E.g. when a focus messages is received the screen is redrawn
986 // because the selection color changes and if another focus message is
987 // received whilst the first one is being processed Vim might crash. To
988 // deal with this problem at the moment, we simply drop messages that are
989 // received while other input is being processed.
990 if (inProcessInput) {
991 #if MM_USE_INPUT_QUEUE
992 [inputQueue addObject:[NSNumber numberWithInt:msgid]];
993 [inputQueue addObject:data];
995 // Just drop the input
996 //NSLog(@"WARNING: Dropping input in %s", _cmd);
999 [self processInputBegin];
1000 [self handleMessage:msgid data:data];
1001 [self processInputEnd];
1005 - (oneway void)processInputAndData:(in NSArray *)messages
1007 // NOTE: See comment in processInput:data:.
1008 unsigned i, count = [messages count];
1010 NSLog(@"WARNING: [messages count] is odd in %s", _cmd);
1014 if (inProcessInput) {
1015 #if MM_USE_INPUT_QUEUE
1016 [inputQueue addObjectsFromArray:messages];
1018 // Just drop the input
1019 //NSLog(@"WARNING: Dropping input in %s", _cmd);
1022 [self processInputBegin];
1024 for (i = 0; i < count; i += 2) {
1025 int msgid = [[messages objectAtIndex:i] intValue];
1026 id data = [messages objectAtIndex:i+1];
1027 if ([data isEqual:[NSNull null]])
1030 [self handleMessage:msgid data:data];
1033 [self processInputEnd];
1037 - (BOOL)checkForModifiedBuffers
1040 for (buf = firstbuf; buf != NULL; buf = buf->b_next) {
1041 if (bufIsChanged(buf)) {
1049 - (BOOL)starRegisterToPasteboard:(byref NSPasteboard *)pboard
1051 if (VIsual_active && (State & NORMAL) && clip_star.available) {
1052 // If there is no pasteboard, return YES to indicate that there is text
1057 clip_copy_selection();
1059 // Get the text to put on the pasteboard.
1060 long_u len = 0; char_u *str = 0;
1061 int type = clip_convert_selection(&str, &len, &clip_star);
1065 NSString *string = [[NSString alloc]
1066 initWithBytes:str length:len encoding:NSUTF8StringEncoding];
1068 NSArray *types = [NSArray arrayWithObject:NSStringPboardType];
1069 [pboard declareTypes:types owner:nil];
1070 BOOL ok = [pboard setString:string forType:NSStringPboardType];
1081 - (oneway void)addReply:(in bycopy NSString *)reply
1082 server:(in byref id <MMVimServerProtocol>)server
1084 //NSLog(@"addReply:%@ server:%@", reply, (id)server);
1086 // Replies might come at any time and in any order so we keep them in an
1087 // array inside a dictionary with the send port used as key.
1089 NSConnection *conn = [(NSDistantObject*)server connectionForProxy];
1090 // HACK! Assume connection uses mach ports.
1091 int port = [(NSMachPort*)[conn sendPort] machPort];
1092 NSNumber *key = [NSNumber numberWithInt:port];
1094 NSMutableArray *replies = [serverReplyDict objectForKey:key];
1096 replies = [NSMutableArray array];
1097 [serverReplyDict setObject:replies forKey:key];
1100 [replies addObject:reply];
1103 - (void)addInput:(in bycopy NSString *)input
1104 client:(in byref id <MMVimClientProtocol>)client
1106 //NSLog(@"addInput:%@ client:%@", input, (id)client);
1108 server_to_input_buf((char_u*)[input UTF8String]);
1110 [self addClient:(id)client];
1112 inputReceived = YES;
1115 - (NSString *)evaluateExpression:(in bycopy NSString *)expr
1116 client:(in byref id <MMVimClientProtocol>)client
1118 //NSLog(@"evaluateExpression:%@ client:%@", expr, (id)client);
1120 NSString *eval = nil;
1121 char_u *res = eval_client_expr_to_string((char_u*)[expr UTF8String]);
1124 eval = [NSString stringWithUTF8String:(char*)res];
1128 [self addClient:(id)client];
1133 - (void)registerServerWithName:(NSString *)name
1135 NSString *svrName = name;
1136 NSConnection *svrConn = [NSConnection defaultConnection];
1139 for (i = 0; i < MMServerMax; ++i) {
1140 NSString *connName = [self connectionNameFromServerName:svrName];
1142 if ([svrConn registerName:connName]) {
1143 //NSLog(@"Registered server with name: %@", svrName);
1145 // TODO: Set request/reply time-outs to something else?
1147 // Don't wait for requests (time-out means that the message is
1149 [svrConn setRequestTimeout:0];
1150 //[svrConn setReplyTimeout:MMReplyTimeout];
1151 [svrConn setRootObject:self];
1153 // NOTE: 'serverName' is a global variable
1154 serverName = vim_strsave((char_u*)[svrName UTF8String]);
1156 set_vim_var_string(VV_SEND_SERVER, serverName, -1);
1159 need_maketitle = TRUE;
1161 [self queueMessage:SetServerNameMsgID data:
1162 [svrName dataUsingEncoding:NSUTF8StringEncoding]];
1166 svrName = [NSString stringWithFormat:@"%@%d", name, i+1];
1170 - (BOOL)sendToServer:(NSString *)name string:(NSString *)string
1171 reply:(char_u **)reply port:(int *)port expression:(BOOL)expr
1174 // NOTE: If 'name' equals 'serverName' then the request is local (client
1175 // and server are the same). This case is not handled separately, so a
1176 // connection will be set up anyway (this simplifies the code).
1178 NSConnection *conn = [self connectionForServerName:name];
1181 EMSG2(_(e_noserver), [name UTF8String]);
1186 // HACK! Assume connection uses mach ports.
1187 *port = [(NSMachPort*)[conn sendPort] machPort];
1190 id proxy = [conn rootProxy];
1191 [proxy setProtocolForProxy:@protocol(MMVimServerProtocol)];
1195 NSString *eval = [proxy evaluateExpression:string client:self];
1197 *reply = (eval ? vim_strsave((char_u*)[eval UTF8String])
1198 : vim_strsave((char_u*)_(e_invexprmsg)));
1204 [proxy addInput:string client:self];
1207 @catch (NSException *e) {
1208 NSLog(@"WARNING: Caught exception in %s: \"%@\"", _cmd, e);
1215 - (NSArray *)serverList
1217 NSArray *list = nil;
1219 if ([self connection]) {
1220 id proxy = [connection rootProxy];
1221 [proxy setProtocolForProxy:@protocol(MMAppProtocol)];
1224 list = [proxy serverList];
1226 @catch (NSException *e) {
1227 NSLog(@"Exception caught when listing servers: \"%@\"", e);
1230 EMSG(_("E???: No connection to MacVim, server listing not possible."));
1236 - (NSString *)peekForReplyOnPort:(int)port
1238 //NSLog(@"%s%d", _cmd, port);
1240 NSNumber *key = [NSNumber numberWithInt:port];
1241 NSMutableArray *replies = [serverReplyDict objectForKey:key];
1242 if (replies && [replies count]) {
1243 //NSLog(@" %d replies, topmost is: %@", [replies count],
1244 // [replies objectAtIndex:0]);
1245 return [replies objectAtIndex:0];
1248 //NSLog(@" No replies");
1252 - (NSString *)waitForReplyOnPort:(int)port
1254 //NSLog(@"%s%d", _cmd, port);
1256 NSConnection *conn = [self connectionForServerPort:port];
1260 NSNumber *key = [NSNumber numberWithInt:port];
1261 NSMutableArray *replies = nil;
1262 NSString *reply = nil;
1264 // Wait for reply as long as the connection to the server is valid (unless
1265 // user interrupts wait with Ctrl-C).
1266 while (!got_int && [conn isValid] &&
1267 !(replies = [serverReplyDict objectForKey:key])) {
1268 [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
1269 beforeDate:[NSDate distantFuture]];
1273 if ([replies count] > 0) {
1274 reply = [[replies objectAtIndex:0] retain];
1275 //NSLog(@" Got reply: %@", reply);
1276 [replies removeObjectAtIndex:0];
1277 [reply autorelease];
1280 if ([replies count] == 0)
1281 [serverReplyDict removeObjectForKey:key];
1287 - (BOOL)sendReply:(NSString *)reply toPort:(int)port
1289 id client = [clientProxyDict objectForKey:[NSNumber numberWithInt:port]];
1292 //NSLog(@"sendReply:%@ toPort:%d", reply, port);
1293 [client addReply:reply server:self];
1296 @catch (NSException *e) {
1297 NSLog(@"WARNING: Exception caught in %s: \"%@\"", _cmd, e);
1300 EMSG2(_("E???: server2client failed; no client with id 0x%x"), port);
1310 @implementation MMBackend (Private)
1312 - (void)handleMessage:(int)msgid data:(NSData *)data
1314 if (InsertTextMsgID == msgid) {
1316 NSString *key = [[NSString alloc] initWithData:data
1317 encoding:NSUTF8StringEncoding];
1318 char_u *str = (char_u*)[key UTF8String];
1319 int i, len = [key lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
1322 char_u *conv_str = NULL;
1323 if (input_conv.vc_type != CONV_NONE) {
1324 conv_str = string_convert(&input_conv, str, &len);
1330 for (i = 0; i < len; ++i) {
1331 add_to_input_buf(str+i, 1);
1332 if (CSI == str[i]) {
1333 // NOTE: If the converted string contains the byte CSI, then it
1334 // must be followed by the bytes KS_EXTRA, KE_CSI or things
1336 static char_u extra[2] = { KS_EXTRA, KE_CSI };
1337 add_to_input_buf(extra, 2);
1346 } else if (KeyDownMsgID == msgid || CmdKeyMsgID == msgid) {
1348 const void *bytes = [data bytes];
1349 int mods = *((int*)bytes); bytes += sizeof(int);
1350 int len = *((int*)bytes); bytes += sizeof(int);
1351 NSString *key = [[NSString alloc] initWithBytes:bytes length:len
1352 encoding:NSUTF8StringEncoding];
1353 mods = eventModifierFlagsToVimModMask(mods);
1355 [self handleKeyDown:key modifiers:mods];
1358 } else if (SelectTabMsgID == msgid) {
1360 const void *bytes = [data bytes];
1361 int idx = *((int*)bytes) + 1;
1362 //NSLog(@"Selecting tab %d", idx);
1363 send_tabline_event(idx);
1364 } else if (CloseTabMsgID == msgid) {
1366 const void *bytes = [data bytes];
1367 int idx = *((int*)bytes) + 1;
1368 //NSLog(@"Closing tab %d", idx);
1369 send_tabline_menu_event(idx, TABLINE_MENU_CLOSE);
1370 } else if (AddNewTabMsgID == msgid) {
1371 //NSLog(@"Adding new tab");
1372 send_tabline_menu_event(0, TABLINE_MENU_NEW);
1373 } else if (DraggedTabMsgID == msgid) {
1375 const void *bytes = [data bytes];
1376 // NOTE! The destination index is 0 based, so do not add 1 to make it 1
1378 int idx = *((int*)bytes);
1381 } else if (ScrollWheelMsgID == msgid) {
1383 const void *bytes = [data bytes];
1385 int row = *((int*)bytes); bytes += sizeof(int);
1386 int col = *((int*)bytes); bytes += sizeof(int);
1387 int flags = *((int*)bytes); bytes += sizeof(int);
1388 float dy = *((float*)bytes); bytes += sizeof(float);
1390 int button = MOUSE_5;
1391 if (dy > 0) button = MOUSE_4;
1393 flags = eventModifierFlagsToVimMouseModMask(flags);
1395 gui_send_mouse_event(button, col, row, NO, flags);
1396 } else if (MouseDownMsgID == 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 button = *((int*)bytes); bytes += sizeof(int);
1403 int flags = *((int*)bytes); bytes += sizeof(int);
1404 int count = *((int*)bytes); bytes += sizeof(int);
1406 button = eventButtonNumberToVimMouseButton(button);
1407 flags = eventModifierFlagsToVimMouseModMask(flags);
1409 gui_send_mouse_event(button, col, row, count>1, flags);
1410 } else if (MouseUpMsgID == msgid) {
1412 const void *bytes = [data bytes];
1414 int row = *((int*)bytes); bytes += sizeof(int);
1415 int col = *((int*)bytes); bytes += sizeof(int);
1416 int flags = *((int*)bytes); bytes += sizeof(int);
1418 flags = eventModifierFlagsToVimMouseModMask(flags);
1420 gui_send_mouse_event(MOUSE_RELEASE, col, row, NO, flags);
1421 } else if (MouseDraggedMsgID == msgid) {
1423 const void *bytes = [data bytes];
1425 int row = *((int*)bytes); bytes += sizeof(int);
1426 int col = *((int*)bytes); bytes += sizeof(int);
1427 int flags = *((int*)bytes); bytes += sizeof(int);
1429 flags = eventModifierFlagsToVimMouseModMask(flags);
1431 gui_send_mouse_event(MOUSE_DRAG, col, row, NO, flags);
1432 } else if (SetTextDimensionsMsgID == msgid) {
1434 const void *bytes = [data bytes];
1435 int rows = *((int*)bytes); bytes += sizeof(int);
1436 int cols = *((int*)bytes); bytes += sizeof(int);
1438 // NOTE! Vim doesn't call gui_mch_set_shellsize() after
1439 // gui_resize_shell(), so we have to manually set the rows and columns
1440 // here. (MacVim doesn't change the rows and columns to avoid
1441 // inconsistent states between Vim and MacVim.)
1442 [self setRows:rows columns:cols];
1444 //NSLog(@"[VimTask] Resizing shell to %dx%d.", cols, rows);
1445 gui_resize_shell(cols, rows);
1446 } else if (ExecuteMenuMsgID == msgid) {
1448 const void *bytes = [data bytes];
1449 int tag = *((int*)bytes); bytes += sizeof(int);
1451 vimmenu_T *menu = (vimmenu_T*)tag;
1452 // TODO! Make sure 'menu' is a valid menu pointer!
1456 } else if (ToggleToolbarMsgID == msgid) {
1457 char_u go[sizeof(GO_ALL)+2];
1462 p = vim_strchr(go, GO_TOOLBAR);
1466 char_u *end = go + len;
1472 go[len] = GO_TOOLBAR;
1476 set_option_value((char_u*)"guioptions", 0, go, 0);
1478 // Force screen redraw (does it have to be this complicated?).
1479 redraw_all_later(CLEAR);
1480 update_screen(NOT_VALID);
1483 gui_update_cursor(FALSE, FALSE);
1485 } else if (ScrollbarEventMsgID == msgid) {
1487 const void *bytes = [data bytes];
1488 long ident = *((long*)bytes); bytes += sizeof(long);
1489 int hitPart = *((int*)bytes); bytes += sizeof(int);
1490 float fval = *((float*)bytes); bytes += sizeof(float);
1491 scrollbar_T *sb = gui_find_scrollbar(ident);
1494 scrollbar_T *sb_info = sb->wp ? &sb->wp->w_scrollbars[0] : sb;
1495 long value = sb_info->value;
1496 long size = sb_info->size;
1497 long max = sb_info->max;
1498 BOOL isStillDragging = NO;
1499 BOOL updateKnob = YES;
1502 case NSScrollerDecrementPage:
1503 value -= (size > 2 ? size - 2 : 1);
1505 case NSScrollerIncrementPage:
1506 value += (size > 2 ? size - 2 : 1);
1508 case NSScrollerDecrementLine:
1511 case NSScrollerIncrementLine:
1514 case NSScrollerKnob:
1515 isStillDragging = YES;
1517 case NSScrollerKnobSlot:
1518 value = (long)(fval * (max - size + 1));
1525 //NSLog(@"value %d -> %d", sb_info->value, value);
1526 gui_drag_scrollbar(sb, value, isStillDragging);
1529 // Dragging the knob or option+clicking automatically updates
1530 // the knob position (on the actual NSScroller), so we only
1531 // need to set the knob position in the other cases.
1533 // Update both the left&right vertical scrollbars.
1534 long identLeft = sb->wp->w_scrollbars[SBAR_LEFT].ident;
1535 long identRight = sb->wp->w_scrollbars[SBAR_RIGHT].ident;
1536 [self setScrollbarThumbValue:value size:size max:max
1537 identifier:identLeft];
1538 [self setScrollbarThumbValue:value size:size max:max
1539 identifier:identRight];
1541 // Update the horizontal scrollbar.
1542 [self setScrollbarThumbValue:value size:size max:max
1547 } else if (SetFontMsgID == msgid) {
1549 const void *bytes = [data bytes];
1550 float pointSize = *((float*)bytes); bytes += sizeof(float);
1551 //unsigned len = *((unsigned*)bytes); bytes += sizeof(unsigned);
1552 bytes += sizeof(unsigned); // len not used
1554 NSMutableString *name = [NSMutableString stringWithUTF8String:bytes];
1555 [name appendString:[NSString stringWithFormat:@":h%.2f", pointSize]];
1557 set_option_value((char_u*)"gfn", 0, (char_u*)[name UTF8String], 0);
1559 // Force screen redraw (does it have to be this complicated?).
1560 redraw_all_later(CLEAR);
1561 update_screen(NOT_VALID);
1564 gui_update_cursor(FALSE, FALSE);
1566 } else if (VimShouldCloseMsgID == msgid) {
1568 } else if (DropFilesMsgID == msgid) {
1570 const void *bytes = [data bytes];
1571 const void *end = [data bytes] + [data length];
1572 int n = *((int*)bytes); bytes += sizeof(int);
1574 if (State & CMDLINE) {
1575 // HACK! If Vim is in command line mode then the files names
1576 // should be added to the command line, instead of opening the
1577 // files in tabs. This is taken care of by gui_handle_drop().
1578 char_u **fnames = (char_u **)alloc(n * sizeof(char_u *));
1581 while (bytes < end && i < n) {
1582 int len = *((int*)bytes); bytes += sizeof(int);
1583 fnames[i++] = vim_strnsave((char_u*)bytes, len);
1587 // NOTE! This function will free 'fnames'.
1588 // HACK! It is assumed that the 'x' and 'y' arguments are
1589 // unused when in command line mode.
1590 gui_handle_drop(0, 0, 0, fnames, i < n ? i : n);
1593 // HACK! I'm not sure how to get Vim to open a list of files in
1594 // tabs, so instead I create a ':tab drop' command with all the
1595 // files to open and execute it.
1596 NSMutableString *cmd = (n > 1)
1597 ? [NSMutableString stringWithString:@":tab drop"]
1598 : [NSMutableString stringWithString:@":drop"];
1601 for (i = 0; i < n && bytes < end; ++i) {
1602 int len = *((int*)bytes); bytes += sizeof(int);
1603 NSMutableString *file =
1604 [NSMutableString stringWithUTF8String:bytes];
1605 [file replaceOccurrencesOfString:@" "
1608 range:NSMakeRange(0,[file length])];
1611 [cmd appendString:@" "];
1612 [cmd appendString:file];
1615 // By going to the last tabpage we ensure that the new tabs will
1616 // appear last (if this call is left out, the taborder becomes
1620 do_cmdline_cmd((char_u*)[cmd UTF8String]);
1622 // Force screen redraw (does it have to be this complicated?).
1623 // (This code was taken from the end of gui_handle_drop().)
1624 update_screen(NOT_VALID);
1627 gui_update_cursor(FALSE, FALSE);
1631 } else if (DropStringMsgID == msgid) {
1633 char_u dropkey[3] = { CSI, KS_EXTRA, (char_u)KE_DROP };
1634 const void *bytes = [data bytes];
1635 int len = *((int*)bytes); bytes += sizeof(int);
1636 NSMutableString *string = [NSMutableString stringWithUTF8String:bytes];
1638 // Replace unrecognized end-of-line sequences with \x0a (line feed).
1639 NSRange range = { 0, [string length] };
1640 unsigned n = [string replaceOccurrencesOfString:@"\x0d\x0a"
1641 withString:@"\x0a" options:0
1644 n = [string replaceOccurrencesOfString:@"\x0d" withString:@"\x0a"
1645 options:0 range:range];
1648 len = [string lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
1649 dnd_yank_drag_data((char_u*)[string UTF8String], len);
1650 add_to_input_buf(dropkey, sizeof(dropkey));
1652 } else if (GotFocusMsgID == msgid) {
1654 [self focusChange:YES];
1655 } else if (LostFocusMsgID == msgid) {
1657 [self focusChange:NO];
1658 } else if (MouseMovedMsgID == msgid) {
1659 const void *bytes = [data bytes];
1660 int row = *((int*)bytes); bytes += sizeof(int);
1661 int col = *((int*)bytes); bytes += sizeof(int);
1663 gui_mouse_moved(col, row);
1664 } else if (SetMouseShapeMsgID == msgid) {
1665 const void *bytes = [data bytes];
1666 int shape = *((int*)bytes); bytes += sizeof(int);
1667 update_mouseshape(shape);
1669 NSLog(@"WARNING: Unknown message received (msgid=%d)", msgid);
1673 + (NSDictionary *)specialKeys
1675 static NSDictionary *specialKeys = nil;
1678 NSBundle *mainBundle = [NSBundle mainBundle];
1679 NSString *path = [mainBundle pathForResource:@"SpecialKeys"
1681 specialKeys = [[NSDictionary alloc] initWithContentsOfFile:path];
1687 - (void)handleKeyDown:(NSString *)key modifiers:(int)mods
1691 char_u *chars = (char_u*)[key UTF8String];
1692 int length = [key lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
1694 // Special keys (arrow keys, function keys, etc.) are stored in a plist so
1695 // that new keys can easily be added.
1696 NSString *specialString = [[MMBackend specialKeys]
1698 if (specialString && [specialString length] > 1) {
1699 //NSLog(@"special key: %@", specialString);
1700 int ikey = TO_SPECIAL([specialString characterAtIndex:0],
1701 [specialString characterAtIndex:1]);
1703 ikey = simplify_key(ikey, &mods);
1708 special[1] = K_SECOND(ikey);
1709 special[2] = K_THIRD(ikey);
1713 } else if (1 == length && TAB == chars[0]) {
1714 // Tab is a trouble child:
1715 // - <Tab> is added to the input buffer as is
1716 // - <S-Tab> is translated to, {CSI,'k','B'} (i.e. 'Back-tab')
1717 // - <M-Tab> should be 0x80|TAB but this is not valid utf-8 so it needs
1718 // to be converted to utf-8
1719 // - <S-M-Tab> is translated to <S-Tab> with ALT modifier
1720 // - <C-Tab> is reserved by Mac OS X
1721 // - <D-Tab> is reserved by Mac OS X
1726 if (mods & MOD_MASK_SHIFT) {
1727 mods &= ~MOD_MASK_SHIFT;
1729 special[1] = K_SECOND(K_S_TAB);
1730 special[2] = K_THIRD(K_S_TAB);
1732 } else if (mods & MOD_MASK_ALT) {
1733 int mtab = 0x80 | TAB;
1735 special[0] = (mtab >> 6) + 0xc0;
1736 special[1] = mtab & 0xbf;
1738 mods &= ~MOD_MASK_ALT;
1740 } else if (length > 0) {
1741 unichar c = [key characterAtIndex:0];
1743 //NSLog(@"non-special: %@ (hex=%x, mods=%d)", key,
1744 // [key characterAtIndex:0], mods);
1746 if (length == 1 && ((c == Ctrl_C && ctrl_c_interrupts)
1747 || (c == intr_char && intr_char != Ctrl_C))) {
1752 // HACK! In most circumstances the Ctrl and Shift modifiers should be
1753 // cleared since they are already added to the key by the AppKit.
1754 // Unfortunately, the only way to deal with when to clear the modifiers
1755 // or not seems to be to have hard-wired rules like this.
1756 if ( !((' ' == c) || (0xa0 == c) || (mods & MOD_MASK_CMD)
1758 mods &= ~MOD_MASK_SHIFT;
1759 mods &= ~MOD_MASK_CTRL;
1760 //NSLog(@"clear shift ctrl");
1763 // HACK! All Option+key presses go via 'insert text' messages, except
1764 // for <M-Space>. If the Alt flag is not cleared for <M-Space> it does
1765 // not work to map to it.
1766 if (0xa0 == c && !(mods & MOD_MASK_CMD)) {
1767 //NSLog(@"clear alt");
1768 mods &= ~MOD_MASK_ALT;
1772 if (chars && length > 0) {
1774 //NSLog(@"adding mods: %d", mods);
1776 modChars[1] = KS_MODIFIER;
1778 add_to_input_buf(modChars, 3);
1781 //NSLog(@"add to input buf: 0x%x", chars[0]);
1782 // TODO: Check for CSI bytes?
1783 add_to_input_buf(chars, length);
1787 - (void)queueMessage:(int)msgid data:(NSData *)data
1789 [queue addObject:[NSData dataWithBytes:&msgid length:sizeof(int)]];
1791 [queue addObject:data];
1793 [queue addObject:[NSData data]];
1796 - (void)connectionDidDie:(NSNotification *)notification
1798 // If the main connection to MacVim is lost this means that MacVim was
1799 // either quit (by the user chosing Quit on the MacVim menu), or it has
1800 // crashed. In either case our only option is to quit now.
1801 // TODO: Write backup file?
1803 //NSLog(@"A Vim process lots its connection to MacVim; quitting.");
1807 - (void)blinkTimerFired:(NSTimer *)timer
1809 NSTimeInterval timeInterval = 0;
1811 [blinkTimer release];
1814 if (MMBlinkStateOn == blinkState) {
1815 gui_undraw_cursor();
1816 blinkState = MMBlinkStateOff;
1817 timeInterval = blinkOffInterval;
1818 } else if (MMBlinkStateOff == blinkState) {
1819 gui_update_cursor(TRUE, FALSE);
1820 blinkState = MMBlinkStateOn;
1821 timeInterval = blinkOnInterval;
1824 if (timeInterval > 0) {
1826 [[NSTimer scheduledTimerWithTimeInterval:timeInterval target:self
1827 selector:@selector(blinkTimerFired:)
1828 userInfo:nil repeats:NO] retain];
1829 [self flushQueue:YES];
1833 - (void)focusChange:(BOOL)on
1835 gui_focus_change(on);
1838 - (void)processInputBegin
1840 inProcessInput = YES;
1841 [lastFlushDate release];
1842 lastFlushDate = [[NSDate date] retain];
1845 - (void)processInputEnd
1847 #if MM_USE_INPUT_QUEUE
1848 int count = [inputQueue count];
1850 // TODO: This is troubling, but it is not hard to get Vim to end up
1851 // here. Why does this happen?
1852 NSLog(@"WARNING: inputQueue has odd number of objects (%d)", count);
1853 [inputQueue removeAllObjects];
1854 } else if (count > 0) {
1855 // TODO: Dispatch these messages? Maybe not; usually when the
1856 // 'inputQueue' is non-empty it means that a LOT of messages has been
1857 // sent simultaneously. The only way this happens is when Vim is being
1858 // tormented, e.g. if the user holds down <D-`> to rapidly switch
1861 for (i = 0; i < count; i+=2) {
1862 int msgid = [[inputQueue objectAtIndex:i] intValue];
1863 NSLog(@"%s: Dropping message %s", _cmd, MessageStrings[msgid]);
1866 [inputQueue removeAllObjects];
1870 inputReceived = YES;
1871 inProcessInput = NO;
1874 - (NSString *)connectionNameFromServerName:(NSString *)name
1876 NSString *bundleIdentifier = [[NSBundle mainBundle] bundleIdentifier];
1878 return [[NSString stringWithFormat:@"%@.%@", bundleIdentifier, name]
1882 - (NSConnection *)connectionForServerName:(NSString *)name
1884 // TODO: Try 'name%d' if 'name' fails.
1885 NSString *connName = [self connectionNameFromServerName:name];
1886 NSConnection *svrConn = [connectionNameDict objectForKey:connName];
1889 svrConn = [NSConnection connectionWithRegisteredName:connName
1891 // Try alternate server...
1892 if (!svrConn && alternateServerName) {
1893 //NSLog(@" trying to connect to alternate server: %@",
1894 // alternateServerName);
1895 connName = [self connectionNameFromServerName:alternateServerName];
1896 svrConn = [NSConnection connectionWithRegisteredName:connName
1900 // Try looking for alternate servers...
1902 //NSLog(@" looking for alternate servers...");
1903 NSString *alt = [self alternateServerNameForName:name];
1904 if (alt != alternateServerName) {
1905 //NSLog(@" found alternate server: %@", string);
1906 [alternateServerName release];
1907 alternateServerName = [alt copy];
1911 // Try alternate server again...
1912 if (!svrConn && alternateServerName) {
1913 //NSLog(@" trying to connect to alternate server: %@",
1914 // alternateServerName);
1915 connName = [self connectionNameFromServerName:alternateServerName];
1916 svrConn = [NSConnection connectionWithRegisteredName:connName
1921 [connectionNameDict setObject:svrConn forKey:connName];
1923 //NSLog(@"Adding %@ as connection observer for %@", self, svrConn);
1924 [[NSNotificationCenter defaultCenter] addObserver:self
1925 selector:@selector(serverConnectionDidDie:)
1926 name:NSConnectionDidDieNotification object:svrConn];
1933 - (NSConnection *)connectionForServerPort:(int)port
1936 NSEnumerator *e = [connectionNameDict objectEnumerator];
1938 while ((conn = [e nextObject])) {
1939 // HACK! Assume connection uses mach ports.
1940 if (port == [(NSMachPort*)[conn sendPort] machPort])
1947 - (void)serverConnectionDidDie:(NSNotification *)notification
1949 //NSLog(@"%s%@", _cmd, notification);
1951 NSConnection *svrConn = [notification object];
1953 //NSLog(@"Removing %@ as connection observer from %@", self, svrConn);
1954 [[NSNotificationCenter defaultCenter]
1956 name:NSConnectionDidDieNotification
1959 [connectionNameDict removeObjectsForKeys:
1960 [connectionNameDict allKeysForObject:svrConn]];
1962 // HACK! Assume connection uses mach ports.
1963 int port = [(NSMachPort*)[svrConn sendPort] machPort];
1964 NSNumber *key = [NSNumber numberWithInt:port];
1966 [clientProxyDict removeObjectForKey:key];
1967 [serverReplyDict removeObjectForKey:key];
1970 - (void)addClient:(NSDistantObject *)client
1972 NSConnection *conn = [client connectionForProxy];
1973 // HACK! Assume connection uses mach ports.
1974 int port = [(NSMachPort*)[conn sendPort] machPort];
1975 NSNumber *key = [NSNumber numberWithInt:port];
1977 if (![clientProxyDict objectForKey:key]) {
1978 [client setProtocolForProxy:@protocol(MMVimClientProtocol)];
1979 [clientProxyDict setObject:client forKey:key];
1982 // NOTE: 'clientWindow' is a global variable which is used by <client>
1983 clientWindow = port;
1986 - (NSString *)alternateServerNameForName:(NSString *)name
1988 if (!(name && [name length] > 0))
1991 // Only look for alternates if 'name' doesn't end in a digit.
1992 unichar lastChar = [name characterAtIndex:[name length]-1];
1993 if (lastChar >= '0' && lastChar <= '9')
1996 // Look for alternates among all current servers.
1997 NSArray *list = [self serverList];
1998 if (!(list && [list count] > 0))
2001 // Filter out servers starting with 'name' and ending with a number. The
2002 // (?i) pattern ensures that the match is case insensitive.
2003 NSString *pat = [NSString stringWithFormat:@"(?i)%@[0-9]+\\z", name];
2004 NSPredicate *pred = [NSPredicate predicateWithFormat:
2005 @"SELF MATCHES %@", pat];
2006 list = [list filteredArrayUsingPredicate:pred];
2007 if ([list count] > 0) {
2008 list = [list sortedArrayUsingSelector:@selector(serverNameCompare:)];
2009 return [list objectAtIndex:0];
2015 @end // MMBackend (Private)
2020 @implementation NSString (MMServerNameCompare)
2021 - (NSComparisonResult)serverNameCompare:(NSString *)string
2023 return [self compare:string
2024 options:NSCaseInsensitiveSearch|NSNumericSearch];
2031 static int eventModifierFlagsToVimModMask(int modifierFlags)
2035 if (modifierFlags & NSShiftKeyMask)
2036 modMask |= MOD_MASK_SHIFT;
2037 if (modifierFlags & NSControlKeyMask)
2038 modMask |= MOD_MASK_CTRL;
2039 if (modifierFlags & NSAlternateKeyMask)
2040 modMask |= MOD_MASK_ALT;
2041 if (modifierFlags & NSCommandKeyMask)
2042 modMask |= MOD_MASK_CMD;
2047 static int vimModMaskToEventModifierFlags(int mods)
2051 if (mods & MOD_MASK_SHIFT)
2052 flags |= NSShiftKeyMask;
2053 if (mods & MOD_MASK_CTRL)
2054 flags |= NSControlKeyMask;
2055 if (mods & MOD_MASK_ALT)
2056 flags |= NSAlternateKeyMask;
2057 if (mods & MOD_MASK_CMD)
2058 flags |= NSCommandKeyMask;
2063 static int eventModifierFlagsToVimMouseModMask(int modifierFlags)
2067 if (modifierFlags & NSShiftKeyMask)
2068 modMask |= MOUSE_SHIFT;
2069 if (modifierFlags & NSControlKeyMask)
2070 modMask |= MOUSE_CTRL;
2071 if (modifierFlags & NSAlternateKeyMask)
2072 modMask |= MOUSE_ALT;
2077 static int eventButtonNumberToVimMouseButton(int buttonNumber)
2079 static int mouseButton[] = { MOUSE_LEFT, MOUSE_RIGHT, MOUSE_MIDDLE,
2080 MOUSE_X1, MOUSE_X2 };
2082 return mouseButton[buttonNumber < 5 ? buttonNumber : 0];
2085 static int specialKeyToNSKey(int key)
2087 if (!IS_SPECIAL(key))
2094 { K_UP, NSUpArrowFunctionKey },
2095 { K_DOWN, NSDownArrowFunctionKey },
2096 { K_LEFT, NSLeftArrowFunctionKey },
2097 { K_RIGHT, NSRightArrowFunctionKey },
2098 { K_F1, NSF1FunctionKey },
2099 { K_F2, NSF2FunctionKey },
2100 { K_F3, NSF3FunctionKey },
2101 { K_F4, NSF4FunctionKey },
2102 { K_F5, NSF5FunctionKey },
2103 { K_F6, NSF6FunctionKey },
2104 { K_F7, NSF7FunctionKey },
2105 { K_F8, NSF8FunctionKey },
2106 { K_F9, NSF9FunctionKey },
2107 { K_F10, NSF10FunctionKey },
2108 { K_F11, NSF11FunctionKey },
2109 { K_F12, NSF12FunctionKey },
2110 { K_F13, NSF13FunctionKey },
2111 { K_F14, NSF14FunctionKey },
2112 { K_F15, NSF15FunctionKey },
2113 { K_F16, NSF16FunctionKey },
2114 { K_F17, NSF17FunctionKey },
2115 { K_F18, NSF18FunctionKey },
2116 { K_F19, NSF19FunctionKey },
2117 { K_F20, NSF20FunctionKey },
2118 { K_F21, NSF21FunctionKey },
2119 { K_F22, NSF22FunctionKey },
2120 { K_F23, NSF23FunctionKey },
2121 { K_F24, NSF24FunctionKey },
2122 { K_F25, NSF25FunctionKey },
2123 { K_F26, NSF26FunctionKey },
2124 { K_F27, NSF27FunctionKey },
2125 { K_F28, NSF28FunctionKey },
2126 { K_F29, NSF29FunctionKey },
2127 { K_F30, NSF30FunctionKey },
2128 { K_F31, NSF31FunctionKey },
2129 { K_F32, NSF32FunctionKey },
2130 { K_F33, NSF33FunctionKey },
2131 { K_F34, NSF34FunctionKey },
2132 { K_F35, NSF35FunctionKey },
2133 { K_DEL, NSBackspaceCharacter },
2134 { K_BS, NSDeleteCharacter },
2135 { K_HOME, NSHomeFunctionKey },
2136 { K_END, NSEndFunctionKey },
2137 { K_PAGEUP, NSPageUpFunctionKey },
2138 { K_PAGEDOWN, NSPageDownFunctionKey }
2142 for (i = 0; i < sizeof(sp2ns)/sizeof(sp2ns[0]); ++i) {
2143 if (sp2ns[i].special == key)
2144 return sp2ns[i].nskey;