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 // NOTE: Colors in MMBackend are stored as unsigned ints on the form 0xaarrggbb
16 // whereas colors in Vim are int without the alpha component.
17 #define MM_COLOR(col) ((unsigned)( ((col)&0xffffff) | 0xff000000 ))
18 #define MM_COLOR_WITH_TRANSP(col,transp) \
19 ((unsigned)( ((col)&0xffffff) | (((unsigned)(255-(transp))&0xff)<<24) ))
22 // This constant controls how often the command queue may be flushed. If it is
23 // too small the app might feel unresponsive; if it is too large there might be
24 // long periods without the screen updating (e.g. when sourcing a large session
25 // file). (The unit is seconds.)
26 static float MMFlushTimeoutInterval = 0.1f;
28 static unsigned MMServerMax = 1000;
30 // NOTE: The default font is bundled with the application.
31 static NSString *MMDefaultFontName = @"DejaVu Sans Mono";
32 static float MMDefaultFontSize = 12.0f;
34 // TODO: Move to separate file.
35 static int eventModifierFlagsToVimModMask(int modifierFlags);
36 static int vimModMaskToEventModifierFlags(int mods);
37 static int eventModifierFlagsToVimMouseModMask(int modifierFlags);
38 static int eventButtonNumberToVimMouseButton(int buttonNumber);
39 static int specialKeyToNSKey(int key);
49 @interface NSString (MMServerNameCompare)
50 - (NSComparisonResult)serverNameCompare:(NSString *)string;
55 @interface MMBackend (Private)
56 - (void)handleMessage:(int)msgid data:(NSData *)data;
57 + (NSDictionary *)specialKeys;
58 - (void)handleKeyDown:(NSString *)key modifiers:(int)mods;
59 - (void)queueMessage:(int)msgid data:(NSData *)data;
60 - (void)connectionDidDie:(NSNotification *)notification;
61 - (void)blinkTimerFired:(NSTimer *)timer;
62 - (void)focusChange:(BOOL)on;
63 - (void)processInputBegin;
64 - (void)processInputEnd;
65 - (NSString *)connectionNameFromServerName:(NSString *)name;
66 - (NSConnection *)connectionForServerName:(NSString *)name;
67 - (NSConnection *)connectionForServerPort:(int)port;
68 - (void)serverConnectionDidDie:(NSNotification *)notification;
69 - (void)addClient:(NSDistantObject *)client;
70 - (NSString *)alternateServerNameForName:(NSString *)name;
75 @implementation MMBackend
77 + (MMBackend *)sharedInstance
79 static MMBackend *singleton = nil;
80 return singleton ? singleton : (singleton = [MMBackend new]);
85 if ((self = [super init])) {
86 fontContainerRef = loadFonts();
88 queue = [[NSMutableArray alloc] init];
89 #if MM_USE_INPUT_QUEUE
90 inputQueue = [[NSMutableArray alloc] init];
92 drawData = [[NSMutableData alloc] initWithCapacity:1024];
93 connectionNameDict = [[NSMutableDictionary alloc] init];
94 clientProxyDict = [[NSMutableDictionary alloc] init];
95 serverReplyDict = [[NSMutableDictionary alloc] init];
97 NSString *path = [[NSBundle mainBundle] pathForResource:@"Colors"
100 colorDict = [[NSDictionary dictionaryWithContentsOfFile:path]
103 NSLog(@"WARNING: Could not locate Colors.plist.");
106 path = [[NSBundle mainBundle] pathForResource:@"SystemColors"
109 sysColorDict = [[NSDictionary dictionaryWithContentsOfFile:path]
112 NSLog(@"WARNING: Could not locate SystemColors.plist.");
121 //NSLog(@"%@ %s", [self className], _cmd);
123 [[NSNotificationCenter defaultCenter] removeObserver:self];
125 [blinkTimer release]; blinkTimer = nil;
126 #if MM_USE_INPUT_QUEUE
127 [inputQueue release]; inputQueue = nil;
129 [alternateServerName release]; alternateServerName = nil;
130 [serverReplyDict release]; serverReplyDict = nil;
131 [clientProxyDict release]; clientProxyDict = nil;
132 [connectionNameDict release]; connectionNameDict = nil;
133 [queue release]; queue = nil;
134 [drawData release]; drawData = nil;
135 [frontendProxy release]; frontendProxy = nil;
136 [connection release]; connection = nil;
137 [sysColorDict release]; sysColorDict = nil;
138 [colorDict release]; colorDict = nil;
143 - (void)setBackgroundColor:(int)color
145 backgroundColor = MM_COLOR_WITH_TRANSP(color,p_transp);
148 - (void)setForegroundColor:(int)color
150 foregroundColor = MM_COLOR(color);
153 - (void)setSpecialColor:(int)color
155 specialColor = MM_COLOR(color);
158 - (void)setDefaultColorsBackground:(int)bg foreground:(int)fg
160 defaultBackgroundColor = MM_COLOR_WITH_TRANSP(bg,p_transp);
161 defaultForegroundColor = MM_COLOR(fg);
163 NSMutableData *data = [NSMutableData data];
165 [data appendBytes:&defaultBackgroundColor length:sizeof(unsigned)];
166 [data appendBytes:&defaultForegroundColor length:sizeof(unsigned)];
168 [self queueMessage:SetDefaultColorsMsgID data:data];
171 - (NSConnection *)connection
174 // NOTE! If the name of the connection changes here it must also be
175 // updated in MMAppController.m.
176 NSString *name = [NSString stringWithFormat:@"%@-connection",
177 [[NSBundle mainBundle] bundleIdentifier]];
179 connection = [NSConnection connectionWithRegisteredName:name host:nil];
183 // NOTE: 'connection' may be nil here.
189 if (![self connection]) {
190 NSBundle *mainBundle = [NSBundle mainBundle];
192 NSString *path = [mainBundle bundlePath];
193 if (![[NSWorkspace sharedWorkspace] launchApplication:path]) {
194 NSLog(@"WARNING: Failed to launch GUI with path %@", path);
198 // HACK! It would be preferable to launch the GUI using NSWorkspace,
199 // however I have not managed to figure out how to pass arguments using
202 // NOTE! Using NSTask to launch the GUI has the negative side-effect
203 // that the GUI won't be activated (or raised) so there is a hack in
204 // MMWindowController which always raises the app when a new window is
206 NSMutableArray *args = [NSMutableArray arrayWithObjects:
207 [NSString stringWithFormat:@"-%@", MMNoWindowKey], @"yes", nil];
208 NSString *exeName = [[mainBundle infoDictionary]
209 objectForKey:@"CFBundleExecutable"];
210 NSString *path = [mainBundle pathForAuxiliaryExecutable:exeName];
212 NSLog(@"ERROR: Could not find MacVim executable in bundle");
216 [NSTask launchedTaskWithLaunchPath:path arguments:args];
219 // HACK! The NSWorkspaceDidLaunchApplicationNotification does not work
220 // for tasks like this, so poll the mach bootstrap server until it
221 // returns a valid connection. Also set a time-out date so that we
222 // don't get stuck doing this forever.
223 NSDate *timeOutDate = [NSDate dateWithTimeIntervalSinceNow:15];
224 while (!connection &&
225 NSOrderedDescending == [timeOutDate compare:[NSDate date]])
227 [[NSRunLoop currentRunLoop]
228 runMode:NSDefaultRunLoopMode
229 beforeDate:[NSDate dateWithTimeIntervalSinceNow:1]];
231 // NOTE: This call will set 'connection' as a side-effect.
236 NSLog(@"WARNING: Timed-out waiting for GUI to launch.");
241 id proxy = [connection rootProxy];
242 [proxy setProtocolForProxy:@protocol(MMAppProtocol)];
244 [[NSNotificationCenter defaultCenter] addObserver:self
245 selector:@selector(connectionDidDie:)
246 name:NSConnectionDidDieNotification object:connection];
248 int pid = [[NSProcessInfo processInfo] processIdentifier];
251 frontendProxy = [proxy connectBackend:self pid:pid];
253 @catch (NSException *e) {
254 NSLog(@"Exception caught when trying to connect backend: \"%@\"", e);
258 [frontendProxy retain];
259 [frontendProxy setProtocolForProxy:@protocol(MMAppProtocol)];
262 return connection && frontendProxy;
265 - (BOOL)openVimWindow
267 [self queueMessage:OpenVimWindowMsgID data:nil];
273 int type = ClearAllDrawType;
275 // Any draw commands in queue are effectively obsolete since this clearAll
276 // will negate any effect they have, therefore we may as well clear the
278 [drawData setLength:0];
280 [drawData appendBytes:&type length:sizeof(int)];
282 [drawData appendBytes:&defaultBackgroundColor length:sizeof(unsigned)];
285 - (void)clearBlockFromRow:(int)row1 column:(int)col1
286 toRow:(int)row2 column:(int)col2
288 int type = ClearBlockDrawType;
290 [drawData appendBytes:&type length:sizeof(int)];
292 [drawData appendBytes:&defaultBackgroundColor length:sizeof(unsigned)];
293 [drawData appendBytes:&row1 length:sizeof(int)];
294 [drawData appendBytes:&col1 length:sizeof(int)];
295 [drawData appendBytes:&row2 length:sizeof(int)];
296 [drawData appendBytes:&col2 length:sizeof(int)];
299 - (void)deleteLinesFromRow:(int)row count:(int)count
300 scrollBottom:(int)bottom left:(int)left right:(int)right
302 int type = DeleteLinesDrawType;
304 [drawData appendBytes:&type length:sizeof(int)];
306 [drawData appendBytes:&defaultBackgroundColor length:sizeof(unsigned)];
307 [drawData appendBytes:&row length:sizeof(int)];
308 [drawData appendBytes:&count length:sizeof(int)];
309 [drawData appendBytes:&bottom length:sizeof(int)];
310 [drawData appendBytes:&left length:sizeof(int)];
311 [drawData appendBytes:&right length:sizeof(int)];
314 - (void)replaceString:(char*)s length:(int)len row:(int)row column:(int)col
317 if (len <= 0) return;
319 int type = ReplaceStringDrawType;
321 [drawData appendBytes:&type length:sizeof(int)];
323 [drawData appendBytes:&backgroundColor length:sizeof(unsigned)];
324 [drawData appendBytes:&foregroundColor length:sizeof(unsigned)];
325 [drawData appendBytes:&specialColor length:sizeof(unsigned)];
326 [drawData appendBytes:&row length:sizeof(int)];
327 [drawData appendBytes:&col length:sizeof(int)];
328 [drawData appendBytes:&flags length:sizeof(int)];
329 [drawData appendBytes:&len length:sizeof(int)];
330 [drawData appendBytes:s length:len];
333 - (void)insertLinesFromRow:(int)row count:(int)count
334 scrollBottom:(int)bottom left:(int)left right:(int)right
336 int type = InsertLinesDrawType;
338 [drawData appendBytes:&type length:sizeof(int)];
340 [drawData appendBytes:&defaultBackgroundColor length:sizeof(unsigned)];
341 [drawData appendBytes:&row length:sizeof(int)];
342 [drawData appendBytes:&count length:sizeof(int)];
343 [drawData appendBytes:&bottom length:sizeof(int)];
344 [drawData appendBytes:&left length:sizeof(int)];
345 [drawData appendBytes:&right length:sizeof(int)];
348 - (void)drawCursorAtRow:(int)row column:(int)col shape:(int)shape
349 fraction:(int)percent color:(int)color
351 int type = DrawCursorDrawType;
352 unsigned uc = MM_COLOR(color);
354 [drawData appendBytes:&type length:sizeof(int)];
356 [drawData appendBytes:&uc length:sizeof(unsigned)];
357 [drawData appendBytes:&row length:sizeof(int)];
358 [drawData appendBytes:&col length:sizeof(int)];
359 [drawData appendBytes:&shape length:sizeof(int)];
360 [drawData appendBytes:&percent length:sizeof(int)];
363 - (void)flushQueue:(BOOL)force
365 // NOTE! This method gets called a lot; if we were to flush every time it
366 // was called MacVim would feel unresponsive. So there is a time out which
367 // ensures that the queue isn't flushed too often.
368 if (!force && lastFlushDate && -[lastFlushDate timeIntervalSinceNow]
369 < MMFlushTimeoutInterval)
372 if ([drawData length] > 0) {
373 [self queueMessage:BatchDrawMsgID data:[drawData copy]];
374 [drawData setLength:0];
377 if ([queue count] > 0) {
379 [frontendProxy processCommandQueue:queue];
381 @catch (NSException *e) {
382 NSLog(@"Exception caught when processing command queue: \"%@\"", e);
385 [queue removeAllObjects];
387 [lastFlushDate release];
388 lastFlushDate = [[NSDate date] retain];
392 - (BOOL)waitForInput:(int)milliseconds
394 NSDate *date = milliseconds > 0 ?
395 [NSDate dateWithTimeIntervalSinceNow:.001*milliseconds] :
396 [NSDate distantFuture];
398 [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:date];
400 // I know of no way to figure out if the run loop exited because input was
401 // found or because of a time out, so I need to manually indicate when
402 // input was received in processInput:data: and then reset it every time
404 BOOL yn = inputReceived;
412 #ifdef MAC_CLIENTSERVER
413 // The default connection is used for the client/server code.
414 [[NSConnection defaultConnection] setRootObject:nil];
415 [[NSConnection defaultConnection] invalidate];
418 // By invalidating the NSConnection the MMWindowController immediately
419 // finds out that the connection is down and as a result
420 // [MMWindowController connectionDidDie:] is invoked.
421 //NSLog(@"%@ %s", [self className], _cmd);
422 [[NSNotificationCenter defaultCenter] removeObserver:self];
423 [connection invalidate];
425 if (fontContainerRef) {
426 ATSFontDeactivate(fontContainerRef, NULL, kATSOptionFlagsDefault);
427 fontContainerRef = 0;
432 - (void)selectTab:(int)index
434 //NSLog(@"%s%d", _cmd, index);
437 NSData *data = [NSData dataWithBytes:&index length:sizeof(int)];
438 [self queueMessage:SelectTabMsgID data:data];
443 //NSLog(@"%s", _cmd);
445 NSMutableData *data = [NSMutableData data];
447 int idx = tabpage_index(curtab) - 1;
448 [data appendBytes:&idx length:sizeof(int)];
451 for (tp = first_tabpage; tp != NULL; tp = tp->tp_next) {
452 // This function puts the label of the tab in the global 'NameBuff'.
453 get_tabline_label(tp, FALSE);
454 char_u *s = NameBuff;
456 if (len <= 0) continue;
459 s = CONVERT_TO_UTF8(s);
462 // Count the number of windows in the tabpage.
463 //win_T *wp = tp->tp_firstwin;
465 //for (wincount = 0; wp != NULL; wp = wp->w_next, ++wincount);
467 //[data appendBytes:&wincount length:sizeof(int)];
468 [data appendBytes:&len length:sizeof(int)];
469 [data appendBytes:s length:len];
472 CONVERT_TO_UTF8_FREE(s);
476 [self queueMessage:UpdateTabBarMsgID data:data];
479 - (BOOL)tabBarVisible
481 return tabBarVisible;
484 - (void)showTabBar:(BOOL)enable
486 tabBarVisible = enable;
488 int msgid = enable ? ShowTabBarMsgID : HideTabBarMsgID;
489 [self queueMessage:msgid data:nil];
492 - (void)setRows:(int)rows columns:(int)cols
494 //NSLog(@"[VimTask] setRows:%d columns:%d", rows, cols);
496 int dim[] = { rows, cols };
497 NSData *data = [NSData dataWithBytes:&dim length:2*sizeof(int)];
499 [self queueMessage:SetTextDimensionsMsgID data:data];
502 - (void)setWindowTitle:(char *)title
504 NSMutableData *data = [NSMutableData data];
505 int len = strlen(title);
506 if (len <= 0) return;
508 [data appendBytes:&len length:sizeof(int)];
509 [data appendBytes:title length:len];
511 [self queueMessage:SetWindowTitleMsgID data:data];
514 - (char *)browseForFileInDirectory:(char *)dir title:(char *)title
517 //NSLog(@"browseForFileInDirectory:%s title:%s saving:%d", dir, title,
521 NSString *ds = dir ? [NSString stringWithUTF8String:dir] : nil;
522 NSString *ts = title ? [NSString stringWithUTF8String:title] : nil;
524 [frontendProxy showSavePanelForDirectory:ds title:ts saving:saving];
526 // Wait until a reply is sent from MMVimController.
527 [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
528 beforeDate:[NSDate distantFuture]];
530 if (dialogReturn && [dialogReturn isKindOfClass:[NSString class]]) {
531 char_u *ret = (char_u*)[dialogReturn UTF8String];
533 ret = CONVERT_FROM_UTF8(ret);
535 s = vim_strsave(ret);
537 CONVERT_FROM_UTF8_FREE(ret);
541 [dialogReturn release]; dialogReturn = nil;
543 @catch (NSException *e) {
544 NSLog(@"Exception caught when showing save panel: \"%@\"", e);
550 - (oneway void)setDialogReturn:(in bycopy id)obj
552 // NOTE: This is called by
553 // - [MMVimController panelDidEnd:::], and
554 // - [MMVimController alertDidEnd:::],
555 // to indicate that a save/open panel or alert has finished.
557 if (obj != dialogReturn) {
558 [dialogReturn release];
559 dialogReturn = [obj retain];
563 - (int)presentDialogWithType:(int)type title:(char *)title message:(char *)msg
564 buttons:(char *)btns textField:(char *)txtfield
567 NSString *message = nil, *text = nil, *textFieldString = nil;
568 NSArray *buttons = nil;
569 int style = NSInformationalAlertStyle;
571 if (VIM_WARNING == type) style = NSWarningAlertStyle;
572 else if (VIM_ERROR == type) style = NSCriticalAlertStyle;
575 NSString *btnString = [NSString stringWithUTF8String:btns];
576 buttons = [btnString componentsSeparatedByString:@"\n"];
579 message = [NSString stringWithUTF8String:title];
581 text = [NSString stringWithUTF8String:msg];
583 // HACK! If there is a '\n\n' or '\n' sequence in the message, then
584 // make the part up to there into the title. We only do this
585 // because Vim has lots of dialogs without a title and they look
587 // TODO: Fix the actual dialog texts.
588 NSRange eolRange = [text rangeOfString:@"\n\n"];
589 if (NSNotFound == eolRange.location)
590 eolRange = [text rangeOfString:@"\n"];
591 if (NSNotFound != eolRange.location) {
592 message = [text substringToIndex:eolRange.location];
593 text = [text substringFromIndex:NSMaxRange(eolRange)];
598 textFieldString = [NSString stringWithUTF8String:txtfield];
601 [frontendProxy presentDialogWithStyle:style message:message
602 informativeText:text buttonTitles:buttons
603 textFieldString:textFieldString];
605 // Wait until a reply is sent from MMVimController.
606 [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
607 beforeDate:[NSDate distantFuture]];
609 if (dialogReturn && [dialogReturn isKindOfClass:[NSArray class]]
610 && [dialogReturn count]) {
611 retval = [[dialogReturn objectAtIndex:0] intValue];
612 if (txtfield && [dialogReturn count] > 1) {
613 NSString *retString = [dialogReturn objectAtIndex:1];
614 char_u *ret = (char_u*)[retString UTF8String];
616 ret = CONVERT_FROM_UTF8(ret);
618 vim_strncpy((char_u*)txtfield, ret, IOSIZE - 1);
620 CONVERT_FROM_UTF8_FREE(ret);
625 [dialogReturn release]; dialogReturn = nil;
627 @catch (NSException *e) {
628 NSLog(@"Exception caught while showing alert dialog: \"%@\"", e);
634 - (void)addMenuWithTag:(int)tag parent:(int)parentTag name:(char *)name
637 //NSLog(@"addMenuWithTag:%d parent:%d name:%s atIndex:%d", tag, parentTag,
640 int namelen = name ? strlen(name) : 0;
641 NSMutableData *data = [NSMutableData data];
643 [data appendBytes:&tag length:sizeof(int)];
644 [data appendBytes:&parentTag length:sizeof(int)];
645 [data appendBytes:&namelen length:sizeof(int)];
646 if (namelen > 0) [data appendBytes:name length:namelen];
647 [data appendBytes:&index length:sizeof(int)];
649 [self queueMessage:AddMenuMsgID data:data];
652 - (void)addMenuItemWithTag:(int)tag parent:(int)parentTag name:(char *)name
653 tip:(char *)tip icon:(char *)icon
654 keyEquivalent:(int)key modifiers:(int)mods
655 action:(NSString *)action atIndex:(int)index
657 //NSLog(@"addMenuItemWithTag:%d parent:%d name:%s tip:%s atIndex:%d", tag,
658 // parentTag, name, tip, index);
660 int namelen = name ? strlen(name) : 0;
661 int tiplen = tip ? strlen(tip) : 0;
662 int iconlen = icon ? strlen(icon) : 0;
663 int eventFlags = vimModMaskToEventModifierFlags(mods);
664 int actionlen = [action lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
665 NSMutableData *data = [NSMutableData data];
667 key = specialKeyToNSKey(key);
669 [data appendBytes:&tag length:sizeof(int)];
670 [data appendBytes:&parentTag length:sizeof(int)];
671 [data appendBytes:&namelen length:sizeof(int)];
672 if (namelen > 0) [data appendBytes:name length:namelen];
673 [data appendBytes:&tiplen length:sizeof(int)];
674 if (tiplen > 0) [data appendBytes:tip length:tiplen];
675 [data appendBytes:&iconlen length:sizeof(int)];
676 if (iconlen > 0) [data appendBytes:icon length:iconlen];
677 [data appendBytes:&actionlen length:sizeof(int)];
678 if (actionlen > 0) [data appendBytes:[action UTF8String] length:actionlen];
679 [data appendBytes:&index length:sizeof(int)];
680 [data appendBytes:&key length:sizeof(int)];
681 [data appendBytes:&eventFlags length:sizeof(int)];
683 [self queueMessage:AddMenuItemMsgID data:data];
686 - (void)removeMenuItemWithTag:(int)tag
688 NSMutableData *data = [NSMutableData data];
689 [data appendBytes:&tag length:sizeof(int)];
691 [self queueMessage:RemoveMenuItemMsgID data:data];
694 - (void)enableMenuItemWithTag:(int)tag state:(int)enabled
696 NSMutableData *data = [NSMutableData data];
698 [data appendBytes:&tag length:sizeof(int)];
699 [data appendBytes:&enabled length:sizeof(int)];
701 [self queueMessage:EnableMenuItemMsgID data:data];
704 - (void)showPopupMenuWithName:(char *)name atMouseLocation:(BOOL)mouse
706 int len = strlen(name);
707 int row = -1, col = -1;
709 if (len <= 0) return;
711 if (!mouse && curwin) {
712 row = curwin->w_wrow;
713 col = curwin->w_wcol;
716 NSMutableData *data = [NSMutableData data];
718 [data appendBytes:&row length:sizeof(int)];
719 [data appendBytes:&col length:sizeof(int)];
720 [data appendBytes:&len length:sizeof(int)];
721 [data appendBytes:name length:len];
723 [self queueMessage:ShowPopupMenuMsgID data:data];
726 - (void)showToolbar:(int)enable flags:(int)flags
728 NSMutableData *data = [NSMutableData data];
730 [data appendBytes:&enable length:sizeof(int)];
731 [data appendBytes:&flags length:sizeof(int)];
733 [self queueMessage:ShowToolbarMsgID data:data];
736 - (void)createScrollbarWithIdentifier:(long)ident type:(int)type
738 NSMutableData *data = [NSMutableData data];
740 [data appendBytes:&ident length:sizeof(long)];
741 [data appendBytes:&type length:sizeof(int)];
743 [self queueMessage:CreateScrollbarMsgID data:data];
746 - (void)destroyScrollbarWithIdentifier:(long)ident
748 NSMutableData *data = [NSMutableData data];
749 [data appendBytes:&ident length:sizeof(long)];
751 [self queueMessage:DestroyScrollbarMsgID data:data];
754 - (void)showScrollbarWithIdentifier:(long)ident state:(int)visible
756 NSMutableData *data = [NSMutableData data];
758 [data appendBytes:&ident length:sizeof(long)];
759 [data appendBytes:&visible length:sizeof(int)];
761 [self queueMessage:ShowScrollbarMsgID data:data];
764 - (void)setScrollbarPosition:(int)pos length:(int)len identifier:(long)ident
766 NSMutableData *data = [NSMutableData data];
768 [data appendBytes:&ident length:sizeof(long)];
769 [data appendBytes:&pos length:sizeof(int)];
770 [data appendBytes:&len length:sizeof(int)];
772 [self queueMessage:SetScrollbarPositionMsgID data:data];
775 - (void)setScrollbarThumbValue:(long)val size:(long)size max:(long)max
776 identifier:(long)ident
778 float fval = max-size+1 > 0 ? (float)val/(max-size+1) : 0;
779 float prop = (float)size/(max+1);
780 if (fval < 0) fval = 0;
781 else if (fval > 1.0f) fval = 1.0f;
782 if (prop < 0) prop = 0;
783 else if (prop > 1.0f) prop = 1.0f;
785 NSMutableData *data = [NSMutableData data];
787 [data appendBytes:&ident length:sizeof(long)];
788 [data appendBytes:&fval length:sizeof(float)];
789 [data appendBytes:&prop length:sizeof(float)];
791 [self queueMessage:SetScrollbarThumbMsgID data:data];
794 - (BOOL)setFontWithName:(char *)name
796 NSString *fontName = MMDefaultFontName;
797 float size = MMDefaultFontSize;
798 BOOL parseFailed = NO;
801 fontName = [NSString stringWithUTF8String:name];
803 if ([fontName isEqual:@"*"]) {
804 // :set gfn=* shows the font panel.
805 do_cmdline_cmd((char_u*)":action orderFrontFontPanel:");
809 NSArray *components = [fontName componentsSeparatedByString:@":"];
810 if ([components count] == 2) {
811 NSString *sizeString = [components lastObject];
812 if ([sizeString length] > 0
813 && [sizeString characterAtIndex:0] == 'h') {
814 sizeString = [sizeString substringFromIndex:1];
815 if ([sizeString length] > 0) {
816 size = [sizeString floatValue];
817 fontName = [components objectAtIndex:0];
822 } else if ([components count] > 2) {
827 if (!parseFailed && [fontName length] > 0) {
828 if (size < 6 || size > 100) {
829 // Font size 0.0 tells NSFont to use the 'user default size'.
833 NSFont *font = [NSFont fontWithName:fontName size:size];
835 if (!font && MMDefaultFontName == fontName) {
836 // If for some reason the MacVim default font is not in the app
837 // bundle, then fall back on the system default font.
839 font = [NSFont userFixedPitchFontOfSize:size];
840 fontName = [font displayName];
844 //NSLog(@"Setting font '%@' of size %.2f", fontName, size);
846 lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
848 NSMutableData *data = [NSMutableData data];
850 [data appendBytes:&size length:sizeof(float)];
851 [data appendBytes:&len length:sizeof(int)];
852 [data appendBytes:[fontName UTF8String] length:len];
854 [self queueMessage:SetFontMsgID data:data];
860 //NSLog(@"WARNING: Cannot set font with name '%@' of size %.2f",
865 - (void)executeActionWithName:(NSString *)name
867 int len = [name lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
870 NSMutableData *data = [NSMutableData data];
872 [data appendBytes:&len length:sizeof(int)];
873 [data appendBytes:[name UTF8String] length:len];
875 [self queueMessage:ExecuteActionMsgID data:data];
879 - (void)setMouseShape:(int)shape
881 NSMutableData *data = [NSMutableData data];
882 [data appendBytes:&shape length:sizeof(int)];
883 [self queueMessage:SetMouseShapeMsgID data:data];
886 - (void)setBlinkWait:(int)wait on:(int)on off:(int)off
888 // Vim specifies times in milliseconds, whereas Cocoa wants them in
890 blinkWaitInterval = .001f*wait;
891 blinkOnInterval = .001f*on;
892 blinkOffInterval = .001f*off;
898 [blinkTimer invalidate];
899 [blinkTimer release];
903 if (blinkWaitInterval > 0 && blinkOnInterval > 0 && blinkOffInterval > 0
905 blinkState = MMBlinkStateOn;
907 [[NSTimer scheduledTimerWithTimeInterval:blinkWaitInterval
909 selector:@selector(blinkTimerFired:)
910 userInfo:nil repeats:NO] retain];
911 gui_update_cursor(TRUE, FALSE);
912 [self flushQueue:YES];
918 if (MMBlinkStateOff == blinkState) {
919 gui_update_cursor(TRUE, FALSE);
920 [self flushQueue:YES];
923 blinkState = MMBlinkStateNone;
926 - (void)adjustLinespace:(int)linespace
928 NSMutableData *data = [NSMutableData data];
929 [data appendBytes:&linespace length:sizeof(int)];
930 [self queueMessage:AdjustLinespaceMsgID data:data];
935 [self queueMessage:ActivateMsgID data:nil];
938 - (int)lookupColorWithKey:(NSString *)key
940 if (!(key && [key length] > 0))
943 NSString *stripKey = [[[[key lowercaseString]
944 stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]
945 componentsSeparatedByString:@" "]
946 componentsJoinedByString:@""];
948 if (stripKey && [stripKey length] > 0) {
949 // First of all try to lookup key in the color dictionary; note that
950 // all keys in this dictionary are lowercase with no whitespace.
951 id obj = [colorDict objectForKey:stripKey];
952 if (obj) return [obj intValue];
954 // The key was not in the dictionary; is it perhaps of the form
956 if ([stripKey length] > 1 && [stripKey characterAtIndex:0] == '#') {
957 NSScanner *scanner = [NSScanner scannerWithString:stripKey];
958 [scanner setScanLocation:1];
960 if ([scanner scanHexInt:&hex]) {
965 // As a last resort, check if it is one of the system defined colors.
966 // The keys in this dictionary are also lowercase with no whitespace.
967 obj = [sysColorDict objectForKey:stripKey];
969 NSColor *col = [NSColor performSelector:NSSelectorFromString(obj)];
972 col = [col colorUsingColorSpaceName:NSCalibratedRGBColorSpace];
973 [col getRed:&r green:&g blue:&b alpha:&a];
974 return (((int)(r*255+.5f) & 0xff) << 16)
975 + (((int)(g*255+.5f) & 0xff) << 8)
976 + ((int)(b*255+.5f) & 0xff);
981 NSLog(@"WARNING: No color with key %@ found.", stripKey);
985 - (BOOL)hasSpecialKeyWithValue:(NSString *)value
987 NSEnumerator *e = [[MMBackend specialKeys] objectEnumerator];
990 while ((obj = [e nextObject])) {
991 if ([value isEqual:obj])
998 - (oneway void)processInput:(int)msgid data:(in NSData *)data
1000 // NOTE: This method might get called whenever the run loop is tended to.
1001 // Thus it might get called whilst input is being processed. Normally this
1002 // is not a problem, but if it gets called often then it might become
1003 // dangerous. E.g. say a message causes the screen to be redrawn and then
1004 // another message is received causing another simultaneous screen redraw;
1005 // this is not good. To deal with this problem at the moment, we simply
1006 // drop messages that are received while other input is being processed.
1007 if (inProcessInput) {
1008 #if MM_USE_INPUT_QUEUE
1009 [inputQueue addObject:[NSNumber numberWithInt:msgid]];
1010 [inputQueue addObject:data];
1012 // Just drop the input
1013 //NSLog(@"WARNING: Dropping input in %s", _cmd);
1016 [self processInputBegin];
1017 [self handleMessage:msgid data:data];
1018 [self processInputEnd];
1022 - (oneway void)processInputAndData:(in NSArray *)messages
1024 // NOTE: See comment in processInput:data:.
1025 unsigned i, count = [messages count];
1027 NSLog(@"WARNING: [messages count] is odd in %s", _cmd);
1031 if (inProcessInput) {
1032 #if MM_USE_INPUT_QUEUE
1033 [inputQueue addObjectsFromArray:messages];
1035 // Just drop the input
1036 //NSLog(@"WARNING: Dropping input in %s", _cmd);
1039 [self processInputBegin];
1041 for (i = 0; i < count; i += 2) {
1042 int msgid = [[messages objectAtIndex:i] intValue];
1043 id data = [messages objectAtIndex:i+1];
1044 if ([data isEqual:[NSNull null]])
1047 [self handleMessage:msgid data:data];
1050 [self processInputEnd];
1054 - (BOOL)checkForModifiedBuffers
1057 for (buf = firstbuf; buf != NULL; buf = buf->b_next) {
1058 if (bufIsChanged(buf)) {
1066 - (BOOL)starRegisterToPasteboard:(byref NSPasteboard *)pboard
1068 if (VIsual_active && (State & NORMAL) && clip_star.available) {
1069 // If there is no pasteboard, return YES to indicate that there is text
1074 clip_copy_selection();
1076 // Get the text to put on the pasteboard.
1077 long_u llen = 0; char_u *str = 0;
1078 int type = clip_convert_selection(&str, &llen, &clip_star);
1082 // TODO: Avoid overflow.
1083 int len = (int)llen;
1085 if (output_conv.vc_type != CONV_NONE) {
1086 char_u *conv_str = string_convert(&output_conv, str, &len);
1094 NSString *string = [[NSString alloc]
1095 initWithBytes:str length:len encoding:NSUTF8StringEncoding];
1097 NSArray *types = [NSArray arrayWithObject:NSStringPboardType];
1098 [pboard declareTypes:types owner:nil];
1099 BOOL ok = [pboard setString:string forType:NSStringPboardType];
1110 - (oneway void)addReply:(in bycopy NSString *)reply
1111 server:(in byref id <MMVimServerProtocol>)server
1113 //NSLog(@"addReply:%@ server:%@", reply, (id)server);
1115 // Replies might come at any time and in any order so we keep them in an
1116 // array inside a dictionary with the send port used as key.
1118 NSConnection *conn = [(NSDistantObject*)server connectionForProxy];
1119 // HACK! Assume connection uses mach ports.
1120 int port = [(NSMachPort*)[conn sendPort] machPort];
1121 NSNumber *key = [NSNumber numberWithInt:port];
1123 NSMutableArray *replies = [serverReplyDict objectForKey:key];
1125 replies = [NSMutableArray array];
1126 [serverReplyDict setObject:replies forKey:key];
1129 [replies addObject:reply];
1132 - (void)addInput:(in bycopy NSString *)input
1133 client:(in byref id <MMVimClientProtocol>)client
1135 //NSLog(@"addInput:%@ client:%@", input, (id)client);
1137 char_u *s = (char_u*)[input UTF8String];
1140 s = CONVERT_FROM_UTF8(s);
1143 server_to_input_buf(s);
1146 CONVERT_FROM_UTF8_FREE(s);
1149 [self addClient:(id)client];
1151 inputReceived = YES;
1154 - (NSString *)evaluateExpression:(in bycopy NSString *)expr
1155 client:(in byref id <MMVimClientProtocol>)client
1157 //NSLog(@"evaluateExpression:%@ client:%@", expr, (id)client);
1159 NSString *eval = nil;
1160 char_u *s = (char_u*)[expr UTF8String];
1163 s = CONVERT_FROM_UTF8(s);
1166 char_u *res = eval_client_expr_to_string(s);
1169 CONVERT_FROM_UTF8_FREE(s);
1175 s = CONVERT_TO_UTF8(s);
1177 eval = [NSString stringWithUTF8String:(char*)s];
1179 CONVERT_TO_UTF8_FREE(s);
1184 [self addClient:(id)client];
1189 - (void)registerServerWithName:(NSString *)name
1191 NSString *svrName = name;
1192 NSConnection *svrConn = [NSConnection defaultConnection];
1195 for (i = 0; i < MMServerMax; ++i) {
1196 NSString *connName = [self connectionNameFromServerName:svrName];
1198 if ([svrConn registerName:connName]) {
1199 //NSLog(@"Registered server with name: %@", svrName);
1201 // TODO: Set request/reply time-outs to something else?
1203 // Don't wait for requests (time-out means that the message is
1205 [svrConn setRequestTimeout:0];
1206 //[svrConn setReplyTimeout:MMReplyTimeout];
1207 [svrConn setRootObject:self];
1209 char_u *s = (char_u*)[svrName UTF8String];
1211 s = CONVERT_FROM_UTF8(s);
1213 // NOTE: 'serverName' is a global variable
1214 serverName = vim_strsave(s);
1216 CONVERT_FROM_UTF8_FREE(s);
1219 set_vim_var_string(VV_SEND_SERVER, serverName, -1);
1222 need_maketitle = TRUE;
1224 [self queueMessage:SetServerNameMsgID data:
1225 [svrName dataUsingEncoding:NSUTF8StringEncoding]];
1229 svrName = [NSString stringWithFormat:@"%@%d", name, i+1];
1233 - (BOOL)sendToServer:(NSString *)name string:(NSString *)string
1234 reply:(char_u **)reply port:(int *)port expression:(BOOL)expr
1237 // NOTE: If 'name' equals 'serverName' then the request is local (client
1238 // and server are the same). This case is not handled separately, so a
1239 // connection will be set up anyway (this simplifies the code).
1241 NSConnection *conn = [self connectionForServerName:name];
1244 char_u *s = (char_u*)[name UTF8String];
1246 s = CONVERT_FROM_UTF8(s);
1248 EMSG2(_(e_noserver), s);
1250 CONVERT_FROM_UTF8_FREE(s);
1257 // HACK! Assume connection uses mach ports.
1258 *port = [(NSMachPort*)[conn sendPort] machPort];
1261 id proxy = [conn rootProxy];
1262 [proxy setProtocolForProxy:@protocol(MMVimServerProtocol)];
1266 NSString *eval = [proxy evaluateExpression:string client:self];
1269 char_u *r = (char_u*)[eval UTF8String];
1271 r = CONVERT_FROM_UTF8(r);
1273 *reply = vim_strsave(r);
1275 CONVERT_FROM_UTF8_FREE(r);
1278 *reply = vim_strsave((char_u*)_(e_invexprmsg));
1285 [proxy addInput:string client:self];
1288 @catch (NSException *e) {
1289 NSLog(@"WARNING: Caught exception in %s: \"%@\"", _cmd, e);
1296 - (NSArray *)serverList
1298 NSArray *list = nil;
1300 if ([self connection]) {
1301 id proxy = [connection rootProxy];
1302 [proxy setProtocolForProxy:@protocol(MMAppProtocol)];
1305 list = [proxy serverList];
1307 @catch (NSException *e) {
1308 NSLog(@"Exception caught when listing servers: \"%@\"", e);
1311 EMSG(_("E???: No connection to MacVim, server listing not possible."));
1317 - (NSString *)peekForReplyOnPort:(int)port
1319 //NSLog(@"%s%d", _cmd, port);
1321 NSNumber *key = [NSNumber numberWithInt:port];
1322 NSMutableArray *replies = [serverReplyDict objectForKey:key];
1323 if (replies && [replies count]) {
1324 //NSLog(@" %d replies, topmost is: %@", [replies count],
1325 // [replies objectAtIndex:0]);
1326 return [replies objectAtIndex:0];
1329 //NSLog(@" No replies");
1333 - (NSString *)waitForReplyOnPort:(int)port
1335 //NSLog(@"%s%d", _cmd, port);
1337 NSConnection *conn = [self connectionForServerPort:port];
1341 NSNumber *key = [NSNumber numberWithInt:port];
1342 NSMutableArray *replies = nil;
1343 NSString *reply = nil;
1345 // Wait for reply as long as the connection to the server is valid (unless
1346 // user interrupts wait with Ctrl-C).
1347 while (!got_int && [conn isValid] &&
1348 !(replies = [serverReplyDict objectForKey:key])) {
1349 [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
1350 beforeDate:[NSDate distantFuture]];
1354 if ([replies count] > 0) {
1355 reply = [[replies objectAtIndex:0] retain];
1356 //NSLog(@" Got reply: %@", reply);
1357 [replies removeObjectAtIndex:0];
1358 [reply autorelease];
1361 if ([replies count] == 0)
1362 [serverReplyDict removeObjectForKey:key];
1368 - (BOOL)sendReply:(NSString *)reply toPort:(int)port
1370 id client = [clientProxyDict objectForKey:[NSNumber numberWithInt:port]];
1373 //NSLog(@"sendReply:%@ toPort:%d", reply, port);
1374 [client addReply:reply server:self];
1377 @catch (NSException *e) {
1378 NSLog(@"WARNING: Exception caught in %s: \"%@\"", _cmd, e);
1381 EMSG2(_("E???: server2client failed; no client with id 0x%x"), port);
1391 @implementation MMBackend (Private)
1393 - (void)handleMessage:(int)msgid data:(NSData *)data
1395 if (InsertTextMsgID == msgid) {
1397 NSString *key = [[NSString alloc] initWithData:data
1398 encoding:NSUTF8StringEncoding];
1399 char_u *str = (char_u*)[key UTF8String];
1400 int i, len = [key lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
1403 char_u *conv_str = NULL;
1404 if (input_conv.vc_type != CONV_NONE) {
1405 conv_str = string_convert(&input_conv, str, &len);
1411 for (i = 0; i < len; ++i) {
1412 add_to_input_buf(str+i, 1);
1413 if (CSI == str[i]) {
1414 // NOTE: If the converted string contains the byte CSI, then it
1415 // must be followed by the bytes KS_EXTRA, KE_CSI or things
1417 static char_u extra[2] = { KS_EXTRA, KE_CSI };
1418 add_to_input_buf(extra, 2);
1427 } else if (KeyDownMsgID == msgid || CmdKeyMsgID == msgid) {
1429 const void *bytes = [data bytes];
1430 int mods = *((int*)bytes); bytes += sizeof(int);
1431 int len = *((int*)bytes); bytes += sizeof(int);
1432 NSString *key = [[NSString alloc] initWithBytes:bytes length:len
1433 encoding:NSUTF8StringEncoding];
1434 mods = eventModifierFlagsToVimModMask(mods);
1436 [self handleKeyDown:key modifiers:mods];
1439 } else if (SelectTabMsgID == msgid) {
1441 const void *bytes = [data bytes];
1442 int idx = *((int*)bytes) + 1;
1443 //NSLog(@"Selecting tab %d", idx);
1444 send_tabline_event(idx);
1445 } else if (CloseTabMsgID == msgid) {
1447 const void *bytes = [data bytes];
1448 int idx = *((int*)bytes) + 1;
1449 //NSLog(@"Closing tab %d", idx);
1450 send_tabline_menu_event(idx, TABLINE_MENU_CLOSE);
1451 } else if (AddNewTabMsgID == msgid) {
1452 //NSLog(@"Adding new tab");
1453 send_tabline_menu_event(0, TABLINE_MENU_NEW);
1454 } else if (DraggedTabMsgID == msgid) {
1456 const void *bytes = [data bytes];
1457 // NOTE! The destination index is 0 based, so do not add 1 to make it 1
1459 int idx = *((int*)bytes);
1462 } else if (ScrollWheelMsgID == msgid) {
1464 const void *bytes = [data bytes];
1466 int row = *((int*)bytes); bytes += sizeof(int);
1467 int col = *((int*)bytes); bytes += sizeof(int);
1468 int flags = *((int*)bytes); bytes += sizeof(int);
1469 float dy = *((float*)bytes); bytes += sizeof(float);
1471 int button = MOUSE_5;
1472 if (dy > 0) button = MOUSE_4;
1474 flags = eventModifierFlagsToVimMouseModMask(flags);
1476 gui_send_mouse_event(button, col, row, NO, flags);
1477 } else if (MouseDownMsgID == msgid) {
1479 const void *bytes = [data bytes];
1481 int row = *((int*)bytes); bytes += sizeof(int);
1482 int col = *((int*)bytes); bytes += sizeof(int);
1483 int button = *((int*)bytes); bytes += sizeof(int);
1484 int flags = *((int*)bytes); bytes += sizeof(int);
1485 int count = *((int*)bytes); bytes += sizeof(int);
1487 button = eventButtonNumberToVimMouseButton(button);
1488 flags = eventModifierFlagsToVimMouseModMask(flags);
1490 gui_send_mouse_event(button, col, row, count>1, flags);
1491 } else if (MouseUpMsgID == msgid) {
1493 const void *bytes = [data bytes];
1495 int row = *((int*)bytes); bytes += sizeof(int);
1496 int col = *((int*)bytes); bytes += sizeof(int);
1497 int flags = *((int*)bytes); bytes += sizeof(int);
1499 flags = eventModifierFlagsToVimMouseModMask(flags);
1501 gui_send_mouse_event(MOUSE_RELEASE, col, row, NO, flags);
1502 } else if (MouseDraggedMsgID == msgid) {
1504 const void *bytes = [data bytes];
1506 int row = *((int*)bytes); bytes += sizeof(int);
1507 int col = *((int*)bytes); bytes += sizeof(int);
1508 int flags = *((int*)bytes); bytes += sizeof(int);
1510 flags = eventModifierFlagsToVimMouseModMask(flags);
1512 gui_send_mouse_event(MOUSE_DRAG, col, row, NO, flags);
1513 } else if (SetTextDimensionsMsgID == msgid) {
1515 const void *bytes = [data bytes];
1516 int rows = *((int*)bytes); bytes += sizeof(int);
1517 int cols = *((int*)bytes); bytes += sizeof(int);
1519 // NOTE! Vim doesn't call gui_mch_set_shellsize() after
1520 // gui_resize_shell(), so we have to manually set the rows and columns
1521 // here. (MacVim doesn't change the rows and columns to avoid
1522 // inconsistent states between Vim and MacVim.)
1523 [self setRows:rows columns:cols];
1525 //NSLog(@"[VimTask] Resizing shell to %dx%d.", cols, rows);
1526 gui_resize_shell(cols, rows);
1527 } else if (ExecuteMenuMsgID == msgid) {
1529 const void *bytes = [data bytes];
1530 int tag = *((int*)bytes); bytes += sizeof(int);
1532 vimmenu_T *menu = (vimmenu_T*)tag;
1533 // TODO! Make sure 'menu' is a valid menu pointer!
1537 } else if (ToggleToolbarMsgID == msgid) {
1538 char_u go[sizeof(GO_ALL)+2];
1543 p = vim_strchr(go, GO_TOOLBAR);
1547 char_u *end = go + len;
1553 go[len] = GO_TOOLBAR;
1557 set_option_value((char_u*)"guioptions", 0, go, 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 (ScrollbarEventMsgID == msgid) {
1568 const void *bytes = [data bytes];
1569 long ident = *((long*)bytes); bytes += sizeof(long);
1570 int hitPart = *((int*)bytes); bytes += sizeof(int);
1571 float fval = *((float*)bytes); bytes += sizeof(float);
1572 scrollbar_T *sb = gui_find_scrollbar(ident);
1575 scrollbar_T *sb_info = sb->wp ? &sb->wp->w_scrollbars[0] : sb;
1576 long value = sb_info->value;
1577 long size = sb_info->size;
1578 long max = sb_info->max;
1579 BOOL isStillDragging = NO;
1580 BOOL updateKnob = YES;
1583 case NSScrollerDecrementPage:
1584 value -= (size > 2 ? size - 2 : 1);
1586 case NSScrollerIncrementPage:
1587 value += (size > 2 ? size - 2 : 1);
1589 case NSScrollerDecrementLine:
1592 case NSScrollerIncrementLine:
1595 case NSScrollerKnob:
1596 isStillDragging = YES;
1598 case NSScrollerKnobSlot:
1599 value = (long)(fval * (max - size + 1));
1606 //NSLog(@"value %d -> %d", sb_info->value, value);
1607 gui_drag_scrollbar(sb, value, isStillDragging);
1610 // Dragging the knob or option+clicking automatically updates
1611 // the knob position (on the actual NSScroller), so we only
1612 // need to set the knob position in the other cases.
1614 // Update both the left&right vertical scrollbars.
1615 long identLeft = sb->wp->w_scrollbars[SBAR_LEFT].ident;
1616 long identRight = sb->wp->w_scrollbars[SBAR_RIGHT].ident;
1617 [self setScrollbarThumbValue:value size:size max:max
1618 identifier:identLeft];
1619 [self setScrollbarThumbValue:value size:size max:max
1620 identifier:identRight];
1622 // Update the horizontal scrollbar.
1623 [self setScrollbarThumbValue:value size:size max:max
1628 } else if (SetFontMsgID == msgid) {
1630 const void *bytes = [data bytes];
1631 float pointSize = *((float*)bytes); bytes += sizeof(float);
1632 //unsigned len = *((unsigned*)bytes); bytes += sizeof(unsigned);
1633 bytes += sizeof(unsigned); // len not used
1635 NSMutableString *name = [NSMutableString stringWithUTF8String:bytes];
1636 [name appendString:[NSString stringWithFormat:@":h%.2f", pointSize]];
1637 char_u *s = (char_u*)[name UTF8String];
1640 s = CONVERT_FROM_UTF8(s);
1643 set_option_value((char_u*)"guifont", 0, s, 0);
1646 CONVERT_FROM_UTF8_FREE(s);
1649 // Force screen redraw (does it have to be this complicated?).
1650 redraw_all_later(CLEAR);
1651 update_screen(NOT_VALID);
1654 gui_update_cursor(FALSE, FALSE);
1656 } else if (VimShouldCloseMsgID == msgid) {
1658 } else if (DropFilesMsgID == msgid) {
1660 const void *bytes = [data bytes];
1661 const void *end = [data bytes] + [data length];
1662 int n = *((int*)bytes); bytes += sizeof(int);
1664 if (State & CMDLINE) {
1665 // HACK! If Vim is in command line mode then the files names
1666 // should be added to the command line, instead of opening the
1667 // files in tabs. This is taken care of by gui_handle_drop().
1668 char_u **fnames = (char_u **)alloc(n * sizeof(char_u *));
1671 while (bytes < end && i < n) {
1672 int len = *((int*)bytes); bytes += sizeof(int);
1673 char_u *s = (char_u*)bytes;
1675 s = CONVERT_FROM_UTF8(s);
1677 fnames[i++] = vim_strsave(s);
1679 CONVERT_FROM_UTF8_FREE(s);
1684 // NOTE! This function will free 'fnames'.
1685 // HACK! It is assumed that the 'x' and 'y' arguments are
1686 // unused when in command line mode.
1687 gui_handle_drop(0, 0, 0, fnames, i < n ? i : n);
1690 // HACK! I'm not sure how to get Vim to open a list of files in
1691 // tabs, so instead I create a ':tab drop' command with all the
1692 // files to open and execute it.
1693 NSMutableString *cmd = (n > 1)
1694 ? [NSMutableString stringWithString:@":tab drop"]
1695 : [NSMutableString stringWithString:@":drop"];
1698 for (i = 0; i < n && bytes < end; ++i) {
1699 int len = *((int*)bytes); bytes += sizeof(int);
1700 NSMutableString *file =
1701 [NSMutableString stringWithUTF8String:bytes];
1702 [file replaceOccurrencesOfString:@" "
1705 range:NSMakeRange(0,[file length])];
1708 [cmd appendString:@" "];
1709 [cmd appendString:file];
1712 // By going to the last tabpage we ensure that the new tabs will
1713 // appear last (if this call is left out, the taborder becomes
1717 char_u *s = (char_u*)[cmd UTF8String];
1719 s = CONVERT_FROM_UTF8(s);
1723 CONVERT_FROM_UTF8_FREE(s);
1726 // Force screen redraw (does it have to be this complicated?).
1727 // (This code was taken from the end of gui_handle_drop().)
1728 update_screen(NOT_VALID);
1731 gui_update_cursor(FALSE, FALSE);
1735 } else if (DropStringMsgID == msgid) {
1737 char_u dropkey[3] = { CSI, KS_EXTRA, (char_u)KE_DROP };
1738 const void *bytes = [data bytes];
1739 int len = *((int*)bytes); bytes += sizeof(int);
1740 NSMutableString *string = [NSMutableString stringWithUTF8String:bytes];
1742 // Replace unrecognized end-of-line sequences with \x0a (line feed).
1743 NSRange range = { 0, [string length] };
1744 unsigned n = [string replaceOccurrencesOfString:@"\x0d\x0a"
1745 withString:@"\x0a" options:0
1748 n = [string replaceOccurrencesOfString:@"\x0d" withString:@"\x0a"
1749 options:0 range:range];
1752 len = [string lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
1753 char_u *s = (char_u*)[string UTF8String];
1755 if (input_conv.vc_type != CONV_NONE)
1756 s = string_convert(&input_conv, s, &len);
1758 dnd_yank_drag_data(s, len);
1760 if (input_conv.vc_type != CONV_NONE)
1763 add_to_input_buf(dropkey, sizeof(dropkey));
1765 } else if (GotFocusMsgID == msgid) {
1767 [self focusChange:YES];
1768 } else if (LostFocusMsgID == msgid) {
1770 [self focusChange:NO];
1771 } else if (MouseMovedMsgID == msgid) {
1772 const void *bytes = [data bytes];
1773 int row = *((int*)bytes); bytes += sizeof(int);
1774 int col = *((int*)bytes); bytes += sizeof(int);
1776 gui_mouse_moved(col, row);
1777 } else if (SetMouseShapeMsgID == msgid) {
1778 const void *bytes = [data bytes];
1779 int shape = *((int*)bytes); bytes += sizeof(int);
1780 update_mouseshape(shape);
1782 NSLog(@"WARNING: Unknown message received (msgid=%d)", msgid);
1786 + (NSDictionary *)specialKeys
1788 static NSDictionary *specialKeys = nil;
1791 NSBundle *mainBundle = [NSBundle mainBundle];
1792 NSString *path = [mainBundle pathForResource:@"SpecialKeys"
1794 specialKeys = [[NSDictionary alloc] initWithContentsOfFile:path];
1800 - (void)handleKeyDown:(NSString *)key modifiers:(int)mods
1804 char_u *chars = (char_u*)[key UTF8String];
1806 char_u *conv_str = NULL;
1808 int length = [key lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
1810 // Special keys (arrow keys, function keys, etc.) are stored in a plist so
1811 // that new keys can easily be added.
1812 NSString *specialString = [[MMBackend specialKeys]
1814 if (specialString && [specialString length] > 1) {
1815 //NSLog(@"special key: %@", specialString);
1816 int ikey = TO_SPECIAL([specialString characterAtIndex:0],
1817 [specialString characterAtIndex:1]);
1819 ikey = simplify_key(ikey, &mods);
1824 special[1] = K_SECOND(ikey);
1825 special[2] = K_THIRD(ikey);
1829 } else if (1 == length && TAB == chars[0]) {
1830 // Tab is a trouble child:
1831 // - <Tab> is added to the input buffer as is
1832 // - <S-Tab> is translated to, {CSI,'k','B'} (i.e. 'Back-tab')
1833 // - <M-Tab> should be 0x80|TAB but this is not valid utf-8 so it needs
1834 // to be converted to utf-8
1835 // - <S-M-Tab> is translated to <S-Tab> with ALT modifier
1836 // - <C-Tab> is reserved by Mac OS X
1837 // - <D-Tab> is reserved by Mac OS X
1842 if (mods & MOD_MASK_SHIFT) {
1843 mods &= ~MOD_MASK_SHIFT;
1845 special[1] = K_SECOND(K_S_TAB);
1846 special[2] = K_THIRD(K_S_TAB);
1848 } else if (mods & MOD_MASK_ALT) {
1849 int mtab = 0x80 | TAB;
1852 special[0] = (mtab >> 6) + 0xc0;
1853 special[1] = mtab & 0xbf;
1859 mods &= ~MOD_MASK_ALT;
1861 } else if (length > 0) {
1862 unichar c = [key characterAtIndex:0];
1864 //NSLog(@"non-special: %@ (hex=%x, mods=%d)", key,
1865 // [key characterAtIndex:0], mods);
1867 if (length == 1 && ((c == Ctrl_C && ctrl_c_interrupts)
1868 || (c == intr_char && intr_char != Ctrl_C))) {
1873 // HACK! In most circumstances the Ctrl and Shift modifiers should be
1874 // cleared since they are already added to the key by the AppKit.
1875 // Unfortunately, the only way to deal with when to clear the modifiers
1876 // or not seems to be to have hard-wired rules like this.
1877 if ( !((' ' == c) || (0xa0 == c) || (mods & MOD_MASK_CMD)
1879 mods &= ~MOD_MASK_SHIFT;
1880 mods &= ~MOD_MASK_CTRL;
1881 //NSLog(@"clear shift ctrl");
1884 // HACK! All Option+key presses go via 'insert text' messages, except
1885 // for <M-Space>. If the Alt flag is not cleared for <M-Space> it does
1886 // not work to map to it.
1887 if (0xa0 == c && !(mods & MOD_MASK_CMD)) {
1888 //NSLog(@"clear alt");
1889 mods &= ~MOD_MASK_ALT;
1893 if (input_conv.vc_type != CONV_NONE) {
1894 conv_str = string_convert(&input_conv, chars, &length);
1901 if (chars && length > 0) {
1903 //NSLog(@"adding mods: %d", mods);
1905 modChars[1] = KS_MODIFIER;
1907 add_to_input_buf(modChars, 3);
1910 //NSLog(@"add to input buf: 0x%x", chars[0]);
1911 // TODO: Check for CSI bytes?
1912 add_to_input_buf(chars, length);
1921 - (void)queueMessage:(int)msgid data:(NSData *)data
1923 [queue addObject:[NSData dataWithBytes:&msgid length:sizeof(int)]];
1925 [queue addObject:data];
1927 [queue addObject:[NSData data]];
1930 - (void)connectionDidDie:(NSNotification *)notification
1932 // If the main connection to MacVim is lost this means that MacVim was
1933 // either quit (by the user chosing Quit on the MacVim menu), or it has
1934 // crashed. In either case our only option is to quit now.
1935 // TODO: Write backup file?
1937 //NSLog(@"A Vim process lots its connection to MacVim; quitting.");
1941 - (void)blinkTimerFired:(NSTimer *)timer
1943 NSTimeInterval timeInterval = 0;
1945 [blinkTimer release];
1948 if (MMBlinkStateOn == blinkState) {
1949 gui_undraw_cursor();
1950 blinkState = MMBlinkStateOff;
1951 timeInterval = blinkOffInterval;
1952 } else if (MMBlinkStateOff == blinkState) {
1953 gui_update_cursor(TRUE, FALSE);
1954 blinkState = MMBlinkStateOn;
1955 timeInterval = blinkOnInterval;
1958 if (timeInterval > 0) {
1960 [[NSTimer scheduledTimerWithTimeInterval:timeInterval target:self
1961 selector:@selector(blinkTimerFired:)
1962 userInfo:nil repeats:NO] retain];
1963 [self flushQueue:YES];
1967 - (void)focusChange:(BOOL)on
1969 gui_focus_change(on);
1972 - (void)processInputBegin
1974 inProcessInput = YES;
1976 // Don't flush too soon or update speed will suffer.
1977 [lastFlushDate release];
1978 lastFlushDate = [[NSDate date] retain];
1981 - (void)processInputEnd
1983 #if MM_USE_INPUT_QUEUE
1984 int count = [inputQueue count];
1986 // TODO: This is troubling, but it is not hard to get Vim to end up
1987 // here. Why does this happen?
1988 NSLog(@"WARNING: inputQueue has odd number of objects (%d)", count);
1989 [inputQueue removeAllObjects];
1990 } else if (count > 0) {
1991 // TODO: Dispatch these messages? Maybe not; usually when the
1992 // 'inputQueue' is non-empty it means that a LOT of messages has been
1993 // sent simultaneously. The only way this happens is when Vim is being
1994 // tormented, e.g. if the user holds down <D-`> to rapidly switch
1997 for (i = 0; i < count; i+=2) {
1998 int msgid = [[inputQueue objectAtIndex:i] intValue];
1999 NSLog(@"%s: Dropping message %s", _cmd, MessageStrings[msgid]);
2002 [inputQueue removeAllObjects];
2006 inputReceived = YES;
2007 inProcessInput = NO;
2010 - (NSString *)connectionNameFromServerName:(NSString *)name
2012 NSString *bundleIdentifier = [[NSBundle mainBundle] bundleIdentifier];
2014 return [[NSString stringWithFormat:@"%@.%@", bundleIdentifier, name]
2018 - (NSConnection *)connectionForServerName:(NSString *)name
2020 // TODO: Try 'name%d' if 'name' fails.
2021 NSString *connName = [self connectionNameFromServerName:name];
2022 NSConnection *svrConn = [connectionNameDict objectForKey:connName];
2025 svrConn = [NSConnection connectionWithRegisteredName:connName
2027 // Try alternate server...
2028 if (!svrConn && alternateServerName) {
2029 //NSLog(@" trying to connect to alternate server: %@",
2030 // alternateServerName);
2031 connName = [self connectionNameFromServerName:alternateServerName];
2032 svrConn = [NSConnection connectionWithRegisteredName:connName
2036 // Try looking for alternate servers...
2038 //NSLog(@" looking for alternate servers...");
2039 NSString *alt = [self alternateServerNameForName:name];
2040 if (alt != alternateServerName) {
2041 //NSLog(@" found alternate server: %@", string);
2042 [alternateServerName release];
2043 alternateServerName = [alt copy];
2047 // Try alternate server again...
2048 if (!svrConn && alternateServerName) {
2049 //NSLog(@" trying to connect to alternate server: %@",
2050 // alternateServerName);
2051 connName = [self connectionNameFromServerName:alternateServerName];
2052 svrConn = [NSConnection connectionWithRegisteredName:connName
2057 [connectionNameDict setObject:svrConn forKey:connName];
2059 //NSLog(@"Adding %@ as connection observer for %@", self, svrConn);
2060 [[NSNotificationCenter defaultCenter] addObserver:self
2061 selector:@selector(serverConnectionDidDie:)
2062 name:NSConnectionDidDieNotification object:svrConn];
2069 - (NSConnection *)connectionForServerPort:(int)port
2072 NSEnumerator *e = [connectionNameDict objectEnumerator];
2074 while ((conn = [e nextObject])) {
2075 // HACK! Assume connection uses mach ports.
2076 if (port == [(NSMachPort*)[conn sendPort] machPort])
2083 - (void)serverConnectionDidDie:(NSNotification *)notification
2085 //NSLog(@"%s%@", _cmd, notification);
2087 NSConnection *svrConn = [notification object];
2089 //NSLog(@"Removing %@ as connection observer from %@", self, svrConn);
2090 [[NSNotificationCenter defaultCenter]
2092 name:NSConnectionDidDieNotification
2095 [connectionNameDict removeObjectsForKeys:
2096 [connectionNameDict allKeysForObject:svrConn]];
2098 // HACK! Assume connection uses mach ports.
2099 int port = [(NSMachPort*)[svrConn sendPort] machPort];
2100 NSNumber *key = [NSNumber numberWithInt:port];
2102 [clientProxyDict removeObjectForKey:key];
2103 [serverReplyDict removeObjectForKey:key];
2106 - (void)addClient:(NSDistantObject *)client
2108 NSConnection *conn = [client connectionForProxy];
2109 // HACK! Assume connection uses mach ports.
2110 int port = [(NSMachPort*)[conn sendPort] machPort];
2111 NSNumber *key = [NSNumber numberWithInt:port];
2113 if (![clientProxyDict objectForKey:key]) {
2114 [client setProtocolForProxy:@protocol(MMVimClientProtocol)];
2115 [clientProxyDict setObject:client forKey:key];
2118 // NOTE: 'clientWindow' is a global variable which is used by <client>
2119 clientWindow = port;
2122 - (NSString *)alternateServerNameForName:(NSString *)name
2124 if (!(name && [name length] > 0))
2127 // Only look for alternates if 'name' doesn't end in a digit.
2128 unichar lastChar = [name characterAtIndex:[name length]-1];
2129 if (lastChar >= '0' && lastChar <= '9')
2132 // Look for alternates among all current servers.
2133 NSArray *list = [self serverList];
2134 if (!(list && [list count] > 0))
2137 // Filter out servers starting with 'name' and ending with a number. The
2138 // (?i) pattern ensures that the match is case insensitive.
2139 NSString *pat = [NSString stringWithFormat:@"(?i)%@[0-9]+\\z", name];
2140 NSPredicate *pred = [NSPredicate predicateWithFormat:
2141 @"SELF MATCHES %@", pat];
2142 list = [list filteredArrayUsingPredicate:pred];
2143 if ([list count] > 0) {
2144 list = [list sortedArrayUsingSelector:@selector(serverNameCompare:)];
2145 return [list objectAtIndex:0];
2151 @end // MMBackend (Private)
2156 @implementation NSString (MMServerNameCompare)
2157 - (NSComparisonResult)serverNameCompare:(NSString *)string
2159 return [self compare:string
2160 options:NSCaseInsensitiveSearch|NSNumericSearch];
2167 static int eventModifierFlagsToVimModMask(int modifierFlags)
2171 if (modifierFlags & NSShiftKeyMask)
2172 modMask |= MOD_MASK_SHIFT;
2173 if (modifierFlags & NSControlKeyMask)
2174 modMask |= MOD_MASK_CTRL;
2175 if (modifierFlags & NSAlternateKeyMask)
2176 modMask |= MOD_MASK_ALT;
2177 if (modifierFlags & NSCommandKeyMask)
2178 modMask |= MOD_MASK_CMD;
2183 static int vimModMaskToEventModifierFlags(int mods)
2187 if (mods & MOD_MASK_SHIFT)
2188 flags |= NSShiftKeyMask;
2189 if (mods & MOD_MASK_CTRL)
2190 flags |= NSControlKeyMask;
2191 if (mods & MOD_MASK_ALT)
2192 flags |= NSAlternateKeyMask;
2193 if (mods & MOD_MASK_CMD)
2194 flags |= NSCommandKeyMask;
2199 static int eventModifierFlagsToVimMouseModMask(int modifierFlags)
2203 if (modifierFlags & NSShiftKeyMask)
2204 modMask |= MOUSE_SHIFT;
2205 if (modifierFlags & NSControlKeyMask)
2206 modMask |= MOUSE_CTRL;
2207 if (modifierFlags & NSAlternateKeyMask)
2208 modMask |= MOUSE_ALT;
2213 static int eventButtonNumberToVimMouseButton(int buttonNumber)
2215 static int mouseButton[] = { MOUSE_LEFT, MOUSE_RIGHT, MOUSE_MIDDLE,
2216 MOUSE_X1, MOUSE_X2 };
2218 return mouseButton[buttonNumber < 5 ? buttonNumber : 0];
2221 static int specialKeyToNSKey(int key)
2223 if (!IS_SPECIAL(key))
2230 { K_UP, NSUpArrowFunctionKey },
2231 { K_DOWN, NSDownArrowFunctionKey },
2232 { K_LEFT, NSLeftArrowFunctionKey },
2233 { K_RIGHT, NSRightArrowFunctionKey },
2234 { K_F1, NSF1FunctionKey },
2235 { K_F2, NSF2FunctionKey },
2236 { K_F3, NSF3FunctionKey },
2237 { K_F4, NSF4FunctionKey },
2238 { K_F5, NSF5FunctionKey },
2239 { K_F6, NSF6FunctionKey },
2240 { K_F7, NSF7FunctionKey },
2241 { K_F8, NSF8FunctionKey },
2242 { K_F9, NSF9FunctionKey },
2243 { K_F10, NSF10FunctionKey },
2244 { K_F11, NSF11FunctionKey },
2245 { K_F12, NSF12FunctionKey },
2246 { K_F13, NSF13FunctionKey },
2247 { K_F14, NSF14FunctionKey },
2248 { K_F15, NSF15FunctionKey },
2249 { K_F16, NSF16FunctionKey },
2250 { K_F17, NSF17FunctionKey },
2251 { K_F18, NSF18FunctionKey },
2252 { K_F19, NSF19FunctionKey },
2253 { K_F20, NSF20FunctionKey },
2254 { K_F21, NSF21FunctionKey },
2255 { K_F22, NSF22FunctionKey },
2256 { K_F23, NSF23FunctionKey },
2257 { K_F24, NSF24FunctionKey },
2258 { K_F25, NSF25FunctionKey },
2259 { K_F26, NSF26FunctionKey },
2260 { K_F27, NSF27FunctionKey },
2261 { K_F28, NSF28FunctionKey },
2262 { K_F29, NSF29FunctionKey },
2263 { K_F30, NSF30FunctionKey },
2264 { K_F31, NSF31FunctionKey },
2265 { K_F32, NSF32FunctionKey },
2266 { K_F33, NSF33FunctionKey },
2267 { K_F34, NSF34FunctionKey },
2268 { K_F35, NSF35FunctionKey },
2269 { K_DEL, NSBackspaceCharacter },
2270 { K_BS, NSDeleteCharacter },
2271 { K_HOME, NSHomeFunctionKey },
2272 { K_END, NSEndFunctionKey },
2273 { K_PAGEUP, NSPageUpFunctionKey },
2274 { K_PAGEDOWN, NSPageDownFunctionKey }
2278 for (i = 0; i < sizeof(sp2ns)/sizeof(sp2ns[0]); ++i) {
2279 if (sp2ns[i].special == key)
2280 return sp2ns[i].nskey;