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. Also note that
17 // 'transp' is assumed to be a value between 0 and 100.
18 #define MM_COLOR(col) ((unsigned)( ((col)&0xffffff) | 0xff000000 ))
19 #define MM_COLOR_WITH_TRANSP(col,transp) \
20 ((unsigned)( ((col)&0xffffff) \
21 | ((((unsigned)((((100-(transp))*255)/100)+.5f))&0xff)<<24) ))
24 // This constant controls how often the command queue may be flushed. If it is
25 // too small the app might feel unresponsive; if it is too large there might be
26 // long periods without the screen updating (e.g. when sourcing a large session
27 // file). (The unit is seconds.)
28 static float MMFlushTimeoutInterval = 0.1f;
29 static int MMFlushQueueLenHint = 80*40;
31 static unsigned MMServerMax = 1000;
33 // NOTE: The default font is bundled with the application.
34 static NSString *MMDefaultFontName = @"DejaVu Sans Mono";
35 static float MMDefaultFontSize = 12.0f;
37 // TODO: Move to separate file.
38 static int eventModifierFlagsToVimModMask(int modifierFlags);
39 static int vimModMaskToEventModifierFlags(int mods);
40 static int eventModifierFlagsToVimMouseModMask(int modifierFlags);
41 static int eventButtonNumberToVimMouseButton(int buttonNumber);
42 static int specialKeyToNSKey(int key);
52 @interface NSString (MMServerNameCompare)
53 - (NSComparisonResult)serverNameCompare:(NSString *)string;
58 @interface MMBackend (Private)
59 - (void)handleMessage:(int)msgid data:(NSData *)data;
60 + (NSDictionary *)specialKeys;
61 - (void)handleInsertText:(NSData *)data;
62 - (void)handleKeyDown:(NSString *)key modifiers:(int)mods;
63 - (void)queueMessage:(int)msgid data:(NSData *)data;
64 - (void)connectionDidDie:(NSNotification *)notification;
65 - (void)blinkTimerFired:(NSTimer *)timer;
66 - (void)focusChange:(BOOL)on;
67 - (void)processInputBegin;
68 - (void)processInputEnd;
69 - (void)handleToggleToolbar;
70 - (void)handleScrollbarEvent:(NSData *)data;
71 - (void)handleSetFont:(NSData *)data;
72 - (void)handleDropFiles:(NSData *)data;
73 - (void)handleDropString:(NSData *)data;
78 @interface MMBackend (ClientServer)
79 - (NSString *)connectionNameFromServerName:(NSString *)name;
80 - (NSConnection *)connectionForServerName:(NSString *)name;
81 - (NSConnection *)connectionForServerPort:(int)port;
82 - (void)serverConnectionDidDie:(NSNotification *)notification;
83 - (void)addClient:(NSDistantObject *)client;
84 - (NSString *)alternateServerNameForName:(NSString *)name;
89 @implementation MMBackend
91 + (MMBackend *)sharedInstance
93 static MMBackend *singleton = nil;
94 return singleton ? singleton : (singleton = [MMBackend new]);
99 if ((self = [super init])) {
100 fontContainerRef = loadFonts();
102 queue = [[NSMutableArray alloc] init];
103 #if MM_USE_INPUT_QUEUE
104 inputQueue = [[NSMutableArray alloc] init];
106 drawData = [[NSMutableData alloc] initWithCapacity:1024];
107 connectionNameDict = [[NSMutableDictionary alloc] init];
108 clientProxyDict = [[NSMutableDictionary alloc] init];
109 serverReplyDict = [[NSMutableDictionary alloc] init];
111 NSString *path = [[NSBundle mainBundle] pathForResource:@"Colors"
114 colorDict = [[NSDictionary dictionaryWithContentsOfFile:path]
117 NSLog(@"WARNING: Could not locate Colors.plist.");
120 path = [[NSBundle mainBundle] pathForResource:@"SystemColors"
123 sysColorDict = [[NSDictionary dictionaryWithContentsOfFile:path]
126 NSLog(@"WARNING: Could not locate SystemColors.plist.");
135 //NSLog(@"%@ %s", [self className], _cmd);
137 [[NSNotificationCenter defaultCenter] removeObserver:self];
139 [blinkTimer release]; blinkTimer = nil;
140 #if MM_USE_INPUT_QUEUE
141 [inputQueue release]; inputQueue = nil;
143 [alternateServerName release]; alternateServerName = nil;
144 [serverReplyDict release]; serverReplyDict = nil;
145 [clientProxyDict release]; clientProxyDict = nil;
146 [connectionNameDict release]; connectionNameDict = nil;
147 [queue release]; queue = nil;
148 [drawData release]; drawData = nil;
149 [frontendProxy release]; frontendProxy = nil;
150 [connection release]; connection = nil;
151 [sysColorDict release]; sysColorDict = nil;
152 [colorDict release]; colorDict = nil;
157 - (void)setBackgroundColor:(int)color
159 backgroundColor = MM_COLOR_WITH_TRANSP(color,p_transp);
162 - (void)setForegroundColor:(int)color
164 foregroundColor = MM_COLOR(color);
167 - (void)setSpecialColor:(int)color
169 specialColor = MM_COLOR(color);
172 - (void)setDefaultColorsBackground:(int)bg foreground:(int)fg
174 defaultBackgroundColor = MM_COLOR_WITH_TRANSP(bg,p_transp);
175 defaultForegroundColor = MM_COLOR(fg);
177 NSMutableData *data = [NSMutableData data];
179 [data appendBytes:&defaultBackgroundColor length:sizeof(unsigned)];
180 [data appendBytes:&defaultForegroundColor length:sizeof(unsigned)];
182 [self queueMessage:SetDefaultColorsMsgID data:data];
185 - (NSConnection *)connection
188 // NOTE! If the name of the connection changes here it must also be
189 // updated in MMAppController.m.
190 NSString *name = [NSString stringWithFormat:@"%@-connection",
191 [[NSBundle mainBundle] bundleIdentifier]];
193 connection = [NSConnection connectionWithRegisteredName:name host:nil];
197 // NOTE: 'connection' may be nil here.
203 if (![self connection]) {
204 NSBundle *mainBundle = [NSBundle mainBundle];
206 NSString *path = [mainBundle bundlePath];
207 if (![[NSWorkspace sharedWorkspace] launchApplication:path]) {
208 NSLog(@"WARNING: Failed to launch GUI with path %@", path);
212 // HACK! It would be preferable to launch the GUI using NSWorkspace,
213 // however I have not managed to figure out how to pass arguments using
216 // NOTE! Using NSTask to launch the GUI has the negative side-effect
217 // that the GUI won't be activated (or raised) so there is a hack in
218 // MMWindowController which always raises the app when a new window is
220 NSMutableArray *args = [NSMutableArray arrayWithObjects:
221 [NSString stringWithFormat:@"-%@", MMNoWindowKey], @"yes", nil];
222 NSString *exeName = [[mainBundle infoDictionary]
223 objectForKey:@"CFBundleExecutable"];
224 NSString *path = [mainBundle pathForAuxiliaryExecutable:exeName];
226 NSLog(@"ERROR: Could not find MacVim executable in bundle");
230 [NSTask launchedTaskWithLaunchPath:path arguments:args];
233 // HACK! The NSWorkspaceDidLaunchApplicationNotification does not work
234 // for tasks like this, so poll the mach bootstrap server until it
235 // returns a valid connection. Also set a time-out date so that we
236 // don't get stuck doing this forever.
237 NSDate *timeOutDate = [NSDate dateWithTimeIntervalSinceNow:15];
238 while (!connection &&
239 NSOrderedDescending == [timeOutDate compare:[NSDate date]])
241 [[NSRunLoop currentRunLoop]
242 runMode:NSDefaultRunLoopMode
243 beforeDate:[NSDate dateWithTimeIntervalSinceNow:1]];
245 // NOTE: This call will set 'connection' as a side-effect.
250 NSLog(@"WARNING: Timed-out waiting for GUI to launch.");
255 id proxy = [connection rootProxy];
256 [proxy setProtocolForProxy:@protocol(MMAppProtocol)];
258 [[NSNotificationCenter defaultCenter] addObserver:self
259 selector:@selector(connectionDidDie:)
260 name:NSConnectionDidDieNotification object:connection];
262 int pid = [[NSProcessInfo processInfo] processIdentifier];
265 frontendProxy = [proxy connectBackend:self pid:pid];
267 @catch (NSException *e) {
268 NSLog(@"Exception caught when trying to connect backend: \"%@\"", e);
272 [frontendProxy retain];
273 [frontendProxy setProtocolForProxy:@protocol(MMAppProtocol)];
276 return connection && frontendProxy;
279 - (BOOL)openVimWindow
281 [self queueMessage:OpenVimWindowMsgID data:nil];
287 int type = ClearAllDrawType;
289 // Any draw commands in queue are effectively obsolete since this clearAll
290 // will negate any effect they have, therefore we may as well clear the
292 [drawData setLength:0];
294 [drawData appendBytes:&type length:sizeof(int)];
297 - (void)clearBlockFromRow:(int)row1 column:(int)col1
298 toRow:(int)row2 column:(int)col2
300 int type = ClearBlockDrawType;
302 [drawData appendBytes:&type length:sizeof(int)];
304 [drawData appendBytes:&defaultBackgroundColor length:sizeof(unsigned)];
305 [drawData appendBytes:&row1 length:sizeof(int)];
306 [drawData appendBytes:&col1 length:sizeof(int)];
307 [drawData appendBytes:&row2 length:sizeof(int)];
308 [drawData appendBytes:&col2 length:sizeof(int)];
311 - (void)deleteLinesFromRow:(int)row count:(int)count
312 scrollBottom:(int)bottom left:(int)left right:(int)right
314 int type = DeleteLinesDrawType;
316 [drawData appendBytes:&type length:sizeof(int)];
318 [drawData appendBytes:&defaultBackgroundColor length:sizeof(unsigned)];
319 [drawData appendBytes:&row length:sizeof(int)];
320 [drawData appendBytes:&count length:sizeof(int)];
321 [drawData appendBytes:&bottom length:sizeof(int)];
322 [drawData appendBytes:&left length:sizeof(int)];
323 [drawData appendBytes:&right length:sizeof(int)];
326 - (void)replaceString:(char*)s length:(int)len row:(int)row column:(int)col
329 if (len <= 0) return;
331 int type = ReplaceStringDrawType;
333 [drawData appendBytes:&type length:sizeof(int)];
335 [drawData appendBytes:&backgroundColor length:sizeof(unsigned)];
336 [drawData appendBytes:&foregroundColor length:sizeof(unsigned)];
337 [drawData appendBytes:&specialColor length:sizeof(unsigned)];
338 [drawData appendBytes:&row length:sizeof(int)];
339 [drawData appendBytes:&col length:sizeof(int)];
340 [drawData appendBytes:&flags length:sizeof(int)];
341 [drawData appendBytes:&len length:sizeof(int)];
342 [drawData appendBytes:s length:len];
345 - (void)insertLinesFromRow:(int)row count:(int)count
346 scrollBottom:(int)bottom left:(int)left right:(int)right
348 int type = InsertLinesDrawType;
350 [drawData appendBytes:&type length:sizeof(int)];
352 [drawData appendBytes:&defaultBackgroundColor length:sizeof(unsigned)];
353 [drawData appendBytes:&row length:sizeof(int)];
354 [drawData appendBytes:&count length:sizeof(int)];
355 [drawData appendBytes:&bottom length:sizeof(int)];
356 [drawData appendBytes:&left length:sizeof(int)];
357 [drawData appendBytes:&right length:sizeof(int)];
360 - (void)drawCursorAtRow:(int)row column:(int)col shape:(int)shape
361 fraction:(int)percent color:(int)color
363 int type = DrawCursorDrawType;
364 unsigned uc = MM_COLOR(color);
366 [drawData appendBytes:&type length:sizeof(int)];
368 [drawData appendBytes:&uc length:sizeof(unsigned)];
369 [drawData appendBytes:&row length:sizeof(int)];
370 [drawData appendBytes:&col length:sizeof(int)];
371 [drawData appendBytes:&shape length:sizeof(int)];
372 [drawData appendBytes:&percent length:sizeof(int)];
375 - (void)flushQueue:(BOOL)force
377 // NOTE! This method gets called a lot; if we were to flush every time it
378 // was called MacVim would feel unresponsive. So there is a time out which
379 // ensures that the queue isn't flushed too often.
380 if (!force && lastFlushDate && -[lastFlushDate timeIntervalSinceNow]
381 < MMFlushTimeoutInterval
382 && [drawData length] < MMFlushQueueLenHint)
385 if ([drawData length] > 0) {
386 [self queueMessage:BatchDrawMsgID data:[drawData copy]];
387 [drawData setLength:0];
390 if ([queue count] > 0) {
392 [frontendProxy processCommandQueue:queue];
394 @catch (NSException *e) {
395 NSLog(@"Exception caught when processing command queue: \"%@\"", e);
398 [queue removeAllObjects];
400 [lastFlushDate release];
401 lastFlushDate = [[NSDate date] retain];
405 - (BOOL)waitForInput:(int)milliseconds
407 NSDate *date = milliseconds > 0 ?
408 [NSDate dateWithTimeIntervalSinceNow:.001*milliseconds] :
409 [NSDate distantFuture];
411 [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:date];
413 // I know of no way to figure out if the run loop exited because input was
414 // found or because of a time out, so I need to manually indicate when
415 // input was received in processInput:data: and then reset it every time
417 BOOL yn = inputReceived;
425 #ifdef MAC_CLIENTSERVER
426 // The default connection is used for the client/server code.
427 [[NSConnection defaultConnection] setRootObject:nil];
428 [[NSConnection defaultConnection] invalidate];
431 // By invalidating the NSConnection the MMWindowController immediately
432 // finds out that the connection is down and as a result
433 // [MMWindowController connectionDidDie:] is invoked.
434 //NSLog(@"%@ %s", [self className], _cmd);
435 [[NSNotificationCenter defaultCenter] removeObserver:self];
436 [connection invalidate];
438 if (fontContainerRef) {
439 ATSFontDeactivate(fontContainerRef, NULL, kATSOptionFlagsDefault);
440 fontContainerRef = 0;
445 - (void)selectTab:(int)index
447 //NSLog(@"%s%d", _cmd, index);
450 NSData *data = [NSData dataWithBytes:&index length:sizeof(int)];
451 [self queueMessage:SelectTabMsgID data:data];
456 //NSLog(@"%s", _cmd);
458 NSMutableData *data = [NSMutableData data];
460 int idx = tabpage_index(curtab) - 1;
461 [data appendBytes:&idx length:sizeof(int)];
464 for (tp = first_tabpage; tp != NULL; tp = tp->tp_next) {
465 // This function puts the label of the tab in the global 'NameBuff'.
466 get_tabline_label(tp, FALSE);
467 char_u *s = NameBuff;
469 if (len <= 0) continue;
472 s = CONVERT_TO_UTF8(s);
475 // Count the number of windows in the tabpage.
476 //win_T *wp = tp->tp_firstwin;
478 //for (wincount = 0; wp != NULL; wp = wp->w_next, ++wincount);
480 //[data appendBytes:&wincount length:sizeof(int)];
481 [data appendBytes:&len length:sizeof(int)];
482 [data appendBytes:s length:len];
485 CONVERT_TO_UTF8_FREE(s);
489 [self queueMessage:UpdateTabBarMsgID data:data];
492 - (BOOL)tabBarVisible
494 return tabBarVisible;
497 - (void)showTabBar:(BOOL)enable
499 tabBarVisible = enable;
501 int msgid = enable ? ShowTabBarMsgID : HideTabBarMsgID;
502 [self queueMessage:msgid data:nil];
505 - (void)setRows:(int)rows columns:(int)cols
507 //NSLog(@"[VimTask] setRows:%d columns:%d", rows, cols);
509 int dim[] = { rows, cols };
510 NSData *data = [NSData dataWithBytes:&dim length:2*sizeof(int)];
512 [self queueMessage:SetTextDimensionsMsgID data:data];
515 - (void)setWindowTitle:(char *)title
517 NSMutableData *data = [NSMutableData data];
518 int len = strlen(title);
519 if (len <= 0) return;
521 [data appendBytes:&len length:sizeof(int)];
522 [data appendBytes:title length:len];
524 [self queueMessage:SetWindowTitleMsgID data:data];
527 - (char *)browseForFileInDirectory:(char *)dir title:(char *)title
530 //NSLog(@"browseForFileInDirectory:%s title:%s saving:%d", dir, title,
534 NSString *ds = dir ? [NSString stringWithUTF8String:dir] : nil;
535 NSString *ts = title ? [NSString stringWithUTF8String:title] : nil;
537 [frontendProxy showSavePanelForDirectory:ds title:ts saving:saving];
539 // Wait until a reply is sent from MMVimController.
540 [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
541 beforeDate:[NSDate distantFuture]];
543 if (dialogReturn && [dialogReturn isKindOfClass:[NSString class]]) {
544 char_u *ret = (char_u*)[dialogReturn UTF8String];
546 ret = CONVERT_FROM_UTF8(ret);
548 s = vim_strsave(ret);
550 CONVERT_FROM_UTF8_FREE(ret);
554 [dialogReturn release]; dialogReturn = nil;
556 @catch (NSException *e) {
557 NSLog(@"Exception caught when showing save panel: \"%@\"", e);
563 - (oneway void)setDialogReturn:(in bycopy id)obj
565 // NOTE: This is called by
566 // - [MMVimController panelDidEnd:::], and
567 // - [MMVimController alertDidEnd:::],
568 // to indicate that a save/open panel or alert has finished.
570 if (obj != dialogReturn) {
571 [dialogReturn release];
572 dialogReturn = [obj retain];
576 - (int)presentDialogWithType:(int)type title:(char *)title message:(char *)msg
577 buttons:(char *)btns textField:(char *)txtfield
580 NSString *message = nil, *text = nil, *textFieldString = nil;
581 NSArray *buttons = nil;
582 int style = NSInformationalAlertStyle;
584 if (VIM_WARNING == type) style = NSWarningAlertStyle;
585 else if (VIM_ERROR == type) style = NSCriticalAlertStyle;
588 NSString *btnString = [NSString stringWithUTF8String:btns];
589 buttons = [btnString componentsSeparatedByString:@"\n"];
592 message = [NSString stringWithUTF8String:title];
594 text = [NSString stringWithUTF8String:msg];
596 // HACK! If there is a '\n\n' or '\n' sequence in the message, then
597 // make the part up to there into the title. We only do this
598 // because Vim has lots of dialogs without a title and they look
600 // TODO: Fix the actual dialog texts.
601 NSRange eolRange = [text rangeOfString:@"\n\n"];
602 if (NSNotFound == eolRange.location)
603 eolRange = [text rangeOfString:@"\n"];
604 if (NSNotFound != eolRange.location) {
605 message = [text substringToIndex:eolRange.location];
606 text = [text substringFromIndex:NSMaxRange(eolRange)];
611 textFieldString = [NSString stringWithUTF8String:txtfield];
614 [frontendProxy presentDialogWithStyle:style message:message
615 informativeText:text buttonTitles:buttons
616 textFieldString:textFieldString];
618 // Wait until a reply is sent from MMVimController.
619 [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
620 beforeDate:[NSDate distantFuture]];
622 if (dialogReturn && [dialogReturn isKindOfClass:[NSArray class]]
623 && [dialogReturn count]) {
624 retval = [[dialogReturn objectAtIndex:0] intValue];
625 if (txtfield && [dialogReturn count] > 1) {
626 NSString *retString = [dialogReturn objectAtIndex:1];
627 char_u *ret = (char_u*)[retString UTF8String];
629 ret = CONVERT_FROM_UTF8(ret);
631 vim_strncpy((char_u*)txtfield, ret, IOSIZE - 1);
633 CONVERT_FROM_UTF8_FREE(ret);
638 [dialogReturn release]; dialogReturn = nil;
640 @catch (NSException *e) {
641 NSLog(@"Exception caught while showing alert dialog: \"%@\"", e);
647 - (void)addMenuWithTag:(int)tag parent:(int)parentTag name:(char *)name
650 //NSLog(@"addMenuWithTag:%d parent:%d name:%s atIndex:%d", tag, parentTag,
653 int namelen = name ? strlen(name) : 0;
654 NSMutableData *data = [NSMutableData data];
656 [data appendBytes:&tag length:sizeof(int)];
657 [data appendBytes:&parentTag length:sizeof(int)];
658 [data appendBytes:&namelen length:sizeof(int)];
659 if (namelen > 0) [data appendBytes:name length:namelen];
660 [data appendBytes:&index length:sizeof(int)];
662 [self queueMessage:AddMenuMsgID data:data];
665 - (void)addMenuItemWithTag:(int)tag parent:(int)parentTag name:(char *)name
666 tip:(char *)tip icon:(char *)icon
667 keyEquivalent:(int)key modifiers:(int)mods
668 action:(NSString *)action atIndex:(int)index
670 //NSLog(@"addMenuItemWithTag:%d parent:%d name:%s tip:%s atIndex:%d", tag,
671 // parentTag, name, tip, index);
673 int namelen = name ? strlen(name) : 0;
674 int tiplen = tip ? strlen(tip) : 0;
675 int iconlen = icon ? strlen(icon) : 0;
676 int eventFlags = vimModMaskToEventModifierFlags(mods);
677 int actionlen = [action lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
678 NSMutableData *data = [NSMutableData data];
680 key = specialKeyToNSKey(key);
682 [data appendBytes:&tag length:sizeof(int)];
683 [data appendBytes:&parentTag length:sizeof(int)];
684 [data appendBytes:&namelen length:sizeof(int)];
685 if (namelen > 0) [data appendBytes:name length:namelen];
686 [data appendBytes:&tiplen length:sizeof(int)];
687 if (tiplen > 0) [data appendBytes:tip length:tiplen];
688 [data appendBytes:&iconlen length:sizeof(int)];
689 if (iconlen > 0) [data appendBytes:icon length:iconlen];
690 [data appendBytes:&actionlen length:sizeof(int)];
691 if (actionlen > 0) [data appendBytes:[action UTF8String] length:actionlen];
692 [data appendBytes:&index length:sizeof(int)];
693 [data appendBytes:&key length:sizeof(int)];
694 [data appendBytes:&eventFlags length:sizeof(int)];
696 [self queueMessage:AddMenuItemMsgID data:data];
699 - (void)removeMenuItemWithTag:(int)tag
701 NSMutableData *data = [NSMutableData data];
702 [data appendBytes:&tag length:sizeof(int)];
704 [self queueMessage:RemoveMenuItemMsgID data:data];
707 - (void)enableMenuItemWithTag:(int)tag state:(int)enabled
709 NSMutableData *data = [NSMutableData data];
711 [data appendBytes:&tag length:sizeof(int)];
712 [data appendBytes:&enabled length:sizeof(int)];
714 [self queueMessage:EnableMenuItemMsgID data:data];
717 - (void)showPopupMenuWithName:(char *)name atMouseLocation:(BOOL)mouse
719 int len = strlen(name);
720 int row = -1, col = -1;
722 if (len <= 0) return;
724 if (!mouse && curwin) {
725 row = curwin->w_wrow;
726 col = curwin->w_wcol;
729 NSMutableData *data = [NSMutableData data];
731 [data appendBytes:&row length:sizeof(int)];
732 [data appendBytes:&col length:sizeof(int)];
733 [data appendBytes:&len length:sizeof(int)];
734 [data appendBytes:name length:len];
736 [self queueMessage:ShowPopupMenuMsgID data:data];
739 - (void)showToolbar:(int)enable flags:(int)flags
741 NSMutableData *data = [NSMutableData data];
743 [data appendBytes:&enable length:sizeof(int)];
744 [data appendBytes:&flags length:sizeof(int)];
746 [self queueMessage:ShowToolbarMsgID data:data];
749 - (void)createScrollbarWithIdentifier:(long)ident type:(int)type
751 NSMutableData *data = [NSMutableData data];
753 [data appendBytes:&ident length:sizeof(long)];
754 [data appendBytes:&type length:sizeof(int)];
756 [self queueMessage:CreateScrollbarMsgID data:data];
759 - (void)destroyScrollbarWithIdentifier:(long)ident
761 NSMutableData *data = [NSMutableData data];
762 [data appendBytes:&ident length:sizeof(long)];
764 [self queueMessage:DestroyScrollbarMsgID data:data];
767 - (void)showScrollbarWithIdentifier:(long)ident state:(int)visible
769 NSMutableData *data = [NSMutableData data];
771 [data appendBytes:&ident length:sizeof(long)];
772 [data appendBytes:&visible length:sizeof(int)];
774 [self queueMessage:ShowScrollbarMsgID data:data];
777 - (void)setScrollbarPosition:(int)pos length:(int)len identifier:(long)ident
779 NSMutableData *data = [NSMutableData data];
781 [data appendBytes:&ident length:sizeof(long)];
782 [data appendBytes:&pos length:sizeof(int)];
783 [data appendBytes:&len length:sizeof(int)];
785 [self queueMessage:SetScrollbarPositionMsgID data:data];
788 - (void)setScrollbarThumbValue:(long)val size:(long)size max:(long)max
789 identifier:(long)ident
791 float fval = max-size+1 > 0 ? (float)val/(max-size+1) : 0;
792 float prop = (float)size/(max+1);
793 if (fval < 0) fval = 0;
794 else if (fval > 1.0f) fval = 1.0f;
795 if (prop < 0) prop = 0;
796 else if (prop > 1.0f) prop = 1.0f;
798 NSMutableData *data = [NSMutableData data];
800 [data appendBytes:&ident length:sizeof(long)];
801 [data appendBytes:&fval length:sizeof(float)];
802 [data appendBytes:&prop length:sizeof(float)];
804 [self queueMessage:SetScrollbarThumbMsgID data:data];
807 - (BOOL)setFontWithName:(char *)name
809 NSString *fontName = MMDefaultFontName;
810 float size = MMDefaultFontSize;
811 BOOL parseFailed = NO;
814 fontName = [NSString stringWithUTF8String:name];
816 if ([fontName isEqual:@"*"]) {
817 // :set gfn=* shows the font panel.
818 do_cmdline_cmd((char_u*)":action orderFrontFontPanel:");
822 NSArray *components = [fontName componentsSeparatedByString:@":"];
823 if ([components count] == 2) {
824 NSString *sizeString = [components lastObject];
825 if ([sizeString length] > 0
826 && [sizeString characterAtIndex:0] == 'h') {
827 sizeString = [sizeString substringFromIndex:1];
828 if ([sizeString length] > 0) {
829 size = [sizeString floatValue];
830 fontName = [components objectAtIndex:0];
835 } else if ([components count] > 2) {
840 if (!parseFailed && [fontName length] > 0) {
841 if (size < 6 || size > 100) {
842 // Font size 0.0 tells NSFont to use the 'user default size'.
846 NSFont *font = [NSFont fontWithName:fontName size:size];
848 if (!font && MMDefaultFontName == fontName) {
849 // If for some reason the MacVim default font is not in the app
850 // bundle, then fall back on the system default font.
852 font = [NSFont userFixedPitchFontOfSize:size];
853 fontName = [font displayName];
857 //NSLog(@"Setting font '%@' of size %.2f", fontName, size);
859 lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
861 NSMutableData *data = [NSMutableData data];
863 [data appendBytes:&size length:sizeof(float)];
864 [data appendBytes:&len length:sizeof(int)];
865 [data appendBytes:[fontName UTF8String] length:len];
867 [self queueMessage:SetFontMsgID data:data];
873 //NSLog(@"WARNING: Cannot set font with name '%@' of size %.2f",
878 - (void)executeActionWithName:(NSString *)name
880 int len = [name lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
883 NSMutableData *data = [NSMutableData data];
885 [data appendBytes:&len length:sizeof(int)];
886 [data appendBytes:[name UTF8String] length:len];
888 [self queueMessage:ExecuteActionMsgID data:data];
892 - (void)setMouseShape:(int)shape
894 NSMutableData *data = [NSMutableData data];
895 [data appendBytes:&shape length:sizeof(int)];
896 [self queueMessage:SetMouseShapeMsgID data:data];
899 - (void)setBlinkWait:(int)wait on:(int)on off:(int)off
901 // Vim specifies times in milliseconds, whereas Cocoa wants them in
903 blinkWaitInterval = .001f*wait;
904 blinkOnInterval = .001f*on;
905 blinkOffInterval = .001f*off;
911 [blinkTimer invalidate];
912 [blinkTimer release];
916 if (blinkWaitInterval > 0 && blinkOnInterval > 0 && blinkOffInterval > 0
918 blinkState = MMBlinkStateOn;
920 [[NSTimer scheduledTimerWithTimeInterval:blinkWaitInterval
922 selector:@selector(blinkTimerFired:)
923 userInfo:nil repeats:NO] retain];
924 gui_update_cursor(TRUE, FALSE);
925 [self flushQueue:YES];
931 if (MMBlinkStateOff == blinkState) {
932 gui_update_cursor(TRUE, FALSE);
933 [self flushQueue:YES];
936 blinkState = MMBlinkStateNone;
939 - (void)adjustLinespace:(int)linespace
941 NSMutableData *data = [NSMutableData data];
942 [data appendBytes:&linespace length:sizeof(int)];
943 [self queueMessage:AdjustLinespaceMsgID data:data];
948 [self queueMessage:ActivateMsgID data:nil];
951 - (int)lookupColorWithKey:(NSString *)key
953 if (!(key && [key length] > 0))
956 NSString *stripKey = [[[[key lowercaseString]
957 stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]
958 componentsSeparatedByString:@" "]
959 componentsJoinedByString:@""];
961 if (stripKey && [stripKey length] > 0) {
962 // First of all try to lookup key in the color dictionary; note that
963 // all keys in this dictionary are lowercase with no whitespace.
964 id obj = [colorDict objectForKey:stripKey];
965 if (obj) return [obj intValue];
967 // The key was not in the dictionary; is it perhaps of the form
969 if ([stripKey length] > 1 && [stripKey characterAtIndex:0] == '#') {
970 NSScanner *scanner = [NSScanner scannerWithString:stripKey];
971 [scanner setScanLocation:1];
973 if ([scanner scanHexInt:&hex]) {
978 // As a last resort, check if it is one of the system defined colors.
979 // The keys in this dictionary are also lowercase with no whitespace.
980 obj = [sysColorDict objectForKey:stripKey];
982 NSColor *col = [NSColor performSelector:NSSelectorFromString(obj)];
985 col = [col colorUsingColorSpaceName:NSCalibratedRGBColorSpace];
986 [col getRed:&r green:&g blue:&b alpha:&a];
987 return (((int)(r*255+.5f) & 0xff) << 16)
988 + (((int)(g*255+.5f) & 0xff) << 8)
989 + ((int)(b*255+.5f) & 0xff);
994 NSLog(@"WARNING: No color with key %@ found.", stripKey);
998 - (BOOL)hasSpecialKeyWithValue:(NSString *)value
1000 NSEnumerator *e = [[MMBackend specialKeys] objectEnumerator];
1003 while ((obj = [e nextObject])) {
1004 if ([value isEqual:obj])
1011 - (void)enterFullscreen
1013 [self queueMessage:EnterFullscreenMsgID data:nil];
1016 - (void)leaveFullscreen
1018 [self queueMessage:LeaveFullscreenMsgID data:nil];
1021 - (void)updateModifiedFlag
1023 // Notify MacVim if _any_ buffer has changed from unmodified to modified or
1025 int msgid = [self checkForModifiedBuffers]
1026 ? BuffersModifiedMsgID : BuffersNotModifiedMsgID;
1028 [self queueMessage:msgid data:nil];
1031 - (oneway void)processInput:(int)msgid data:(in bycopy NSData *)data
1033 // NOTE: This method might get called whenever the run loop is tended to.
1034 // Thus it might get called whilst input is being processed. Normally this
1035 // is not a problem, but if it gets called often then it might become
1036 // dangerous. E.g. say a message causes the screen to be redrawn and then
1037 // another message is received causing another simultaneous screen redraw;
1038 // this is not good. To deal with this problem at the moment, we simply
1039 // drop messages that are received while other input is being processed.
1040 if (inProcessInput) {
1041 #if MM_USE_INPUT_QUEUE
1042 [inputQueue addObject:[NSNumber numberWithInt:msgid]];
1043 [inputQueue addObject:data];
1045 // Just drop the input
1046 //NSLog(@"WARNING: Dropping input in %s", _cmd);
1049 [self processInputBegin];
1050 [self handleMessage:msgid data:data];
1051 [self processInputEnd];
1055 - (oneway void)processInputAndData:(in bycopy NSArray *)messages
1057 // NOTE: See comment in processInput:data:.
1058 unsigned i, count = [messages count];
1060 NSLog(@"WARNING: [messages count] is odd in %s", _cmd);
1064 if (inProcessInput) {
1065 #if MM_USE_INPUT_QUEUE
1066 [inputQueue addObjectsFromArray:messages];
1068 // Just drop the input
1069 //NSLog(@"WARNING: Dropping input in %s", _cmd);
1072 [self processInputBegin];
1074 for (i = 0; i < count; i += 2) {
1075 int msgid = [[messages objectAtIndex:i] intValue];
1076 id data = [messages objectAtIndex:i+1];
1077 if ([data isEqual:[NSNull null]])
1080 [self handleMessage:msgid data:data];
1083 [self processInputEnd];
1087 - (BOOL)checkForModifiedBuffers
1090 for (buf = firstbuf; buf != NULL; buf = buf->b_next) {
1091 if (bufIsChanged(buf)) {
1099 - (BOOL)starRegisterToPasteboard:(byref NSPasteboard *)pboard
1101 if (VIsual_active && (State & NORMAL) && clip_star.available) {
1102 // If there is no pasteboard, return YES to indicate that there is text
1107 clip_copy_selection();
1109 // Get the text to put on the pasteboard.
1110 long_u llen = 0; char_u *str = 0;
1111 int type = clip_convert_selection(&str, &llen, &clip_star);
1115 // TODO: Avoid overflow.
1116 int len = (int)llen;
1118 if (output_conv.vc_type != CONV_NONE) {
1119 char_u *conv_str = string_convert(&output_conv, str, &len);
1127 NSString *string = [[NSString alloc]
1128 initWithBytes:str length:len encoding:NSUTF8StringEncoding];
1130 NSArray *types = [NSArray arrayWithObject:NSStringPboardType];
1131 [pboard declareTypes:types owner:nil];
1132 BOOL ok = [pboard setString:string forType:NSStringPboardType];
1143 - (oneway void)addReply:(in bycopy NSString *)reply
1144 server:(in byref id <MMVimServerProtocol>)server
1146 //NSLog(@"addReply:%@ server:%@", reply, (id)server);
1148 // Replies might come at any time and in any order so we keep them in an
1149 // array inside a dictionary with the send port used as key.
1151 NSConnection *conn = [(NSDistantObject*)server connectionForProxy];
1152 // HACK! Assume connection uses mach ports.
1153 int port = [(NSMachPort*)[conn sendPort] machPort];
1154 NSNumber *key = [NSNumber numberWithInt:port];
1156 NSMutableArray *replies = [serverReplyDict objectForKey:key];
1158 replies = [NSMutableArray array];
1159 [serverReplyDict setObject:replies forKey:key];
1162 [replies addObject:reply];
1165 - (void)addInput:(in bycopy NSString *)input
1166 client:(in byref id <MMVimClientProtocol>)client
1168 //NSLog(@"addInput:%@ client:%@", input, (id)client);
1170 char_u *s = (char_u*)[input UTF8String];
1173 s = CONVERT_FROM_UTF8(s);
1176 server_to_input_buf(s);
1179 CONVERT_FROM_UTF8_FREE(s);
1182 [self addClient:(id)client];
1184 inputReceived = YES;
1187 - (NSString *)evaluateExpression:(in bycopy NSString *)expr
1188 client:(in byref id <MMVimClientProtocol>)client
1190 //NSLog(@"evaluateExpression:%@ client:%@", expr, (id)client);
1192 NSString *eval = nil;
1193 char_u *s = (char_u*)[expr UTF8String];
1196 s = CONVERT_FROM_UTF8(s);
1199 char_u *res = eval_client_expr_to_string(s);
1202 CONVERT_FROM_UTF8_FREE(s);
1208 s = CONVERT_TO_UTF8(s);
1210 eval = [NSString stringWithUTF8String:(char*)s];
1212 CONVERT_TO_UTF8_FREE(s);
1217 [self addClient:(id)client];
1222 - (void)registerServerWithName:(NSString *)name
1224 NSString *svrName = name;
1225 NSConnection *svrConn = [NSConnection defaultConnection];
1228 for (i = 0; i < MMServerMax; ++i) {
1229 NSString *connName = [self connectionNameFromServerName:svrName];
1231 if ([svrConn registerName:connName]) {
1232 //NSLog(@"Registered server with name: %@", svrName);
1234 // TODO: Set request/reply time-outs to something else?
1236 // Don't wait for requests (time-out means that the message is
1238 [svrConn setRequestTimeout:0];
1239 //[svrConn setReplyTimeout:MMReplyTimeout];
1240 [svrConn setRootObject:self];
1242 char_u *s = (char_u*)[svrName UTF8String];
1244 s = CONVERT_FROM_UTF8(s);
1246 // NOTE: 'serverName' is a global variable
1247 serverName = vim_strsave(s);
1249 CONVERT_FROM_UTF8_FREE(s);
1252 set_vim_var_string(VV_SEND_SERVER, serverName, -1);
1255 need_maketitle = TRUE;
1257 [self queueMessage:SetServerNameMsgID data:
1258 [svrName dataUsingEncoding:NSUTF8StringEncoding]];
1262 svrName = [NSString stringWithFormat:@"%@%d", name, i+1];
1266 - (BOOL)sendToServer:(NSString *)name string:(NSString *)string
1267 reply:(char_u **)reply port:(int *)port expression:(BOOL)expr
1270 // NOTE: If 'name' equals 'serverName' then the request is local (client
1271 // and server are the same). This case is not handled separately, so a
1272 // connection will be set up anyway (this simplifies the code).
1274 NSConnection *conn = [self connectionForServerName:name];
1277 char_u *s = (char_u*)[name UTF8String];
1279 s = CONVERT_FROM_UTF8(s);
1281 EMSG2(_(e_noserver), s);
1283 CONVERT_FROM_UTF8_FREE(s);
1290 // HACK! Assume connection uses mach ports.
1291 *port = [(NSMachPort*)[conn sendPort] machPort];
1294 id proxy = [conn rootProxy];
1295 [proxy setProtocolForProxy:@protocol(MMVimServerProtocol)];
1299 NSString *eval = [proxy evaluateExpression:string client:self];
1302 char_u *r = (char_u*)[eval UTF8String];
1304 r = CONVERT_FROM_UTF8(r);
1306 *reply = vim_strsave(r);
1308 CONVERT_FROM_UTF8_FREE(r);
1311 *reply = vim_strsave((char_u*)_(e_invexprmsg));
1318 [proxy addInput:string client:self];
1321 @catch (NSException *e) {
1322 NSLog(@"WARNING: Caught exception in %s: \"%@\"", _cmd, e);
1329 - (NSArray *)serverList
1331 NSArray *list = nil;
1333 if ([self connection]) {
1334 id proxy = [connection rootProxy];
1335 [proxy setProtocolForProxy:@protocol(MMAppProtocol)];
1338 list = [proxy serverList];
1340 @catch (NSException *e) {
1341 NSLog(@"Exception caught when listing servers: \"%@\"", e);
1344 EMSG(_("E???: No connection to MacVim, server listing not possible."));
1350 - (NSString *)peekForReplyOnPort:(int)port
1352 //NSLog(@"%s%d", _cmd, port);
1354 NSNumber *key = [NSNumber numberWithInt:port];
1355 NSMutableArray *replies = [serverReplyDict objectForKey:key];
1356 if (replies && [replies count]) {
1357 //NSLog(@" %d replies, topmost is: %@", [replies count],
1358 // [replies objectAtIndex:0]);
1359 return [replies objectAtIndex:0];
1362 //NSLog(@" No replies");
1366 - (NSString *)waitForReplyOnPort:(int)port
1368 //NSLog(@"%s%d", _cmd, port);
1370 NSConnection *conn = [self connectionForServerPort:port];
1374 NSNumber *key = [NSNumber numberWithInt:port];
1375 NSMutableArray *replies = nil;
1376 NSString *reply = nil;
1378 // Wait for reply as long as the connection to the server is valid (unless
1379 // user interrupts wait with Ctrl-C).
1380 while (!got_int && [conn isValid] &&
1381 !(replies = [serverReplyDict objectForKey:key])) {
1382 [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
1383 beforeDate:[NSDate distantFuture]];
1387 if ([replies count] > 0) {
1388 reply = [[replies objectAtIndex:0] retain];
1389 //NSLog(@" Got reply: %@", reply);
1390 [replies removeObjectAtIndex:0];
1391 [reply autorelease];
1394 if ([replies count] == 0)
1395 [serverReplyDict removeObjectForKey:key];
1401 - (BOOL)sendReply:(NSString *)reply toPort:(int)port
1403 id client = [clientProxyDict objectForKey:[NSNumber numberWithInt:port]];
1406 //NSLog(@"sendReply:%@ toPort:%d", reply, port);
1407 [client addReply:reply server:self];
1410 @catch (NSException *e) {
1411 NSLog(@"WARNING: Exception caught in %s: \"%@\"", _cmd, e);
1414 EMSG2(_("E???: server2client failed; no client with id 0x%x"), port);
1424 @implementation MMBackend (Private)
1426 - (void)handleMessage:(int)msgid data:(NSData *)data
1428 if (InsertTextMsgID == msgid) {
1429 [self handleInsertText:data];
1430 } else if (KeyDownMsgID == msgid || CmdKeyMsgID == msgid) {
1432 const void *bytes = [data bytes];
1433 int mods = *((int*)bytes); bytes += sizeof(int);
1434 int len = *((int*)bytes); bytes += sizeof(int);
1435 NSString *key = [[NSString alloc] initWithBytes:bytes length:len
1436 encoding:NSUTF8StringEncoding];
1437 mods = eventModifierFlagsToVimModMask(mods);
1439 [self handleKeyDown:key modifiers:mods];
1442 } else if (SelectTabMsgID == msgid) {
1444 const void *bytes = [data bytes];
1445 int idx = *((int*)bytes) + 1;
1446 //NSLog(@"Selecting tab %d", idx);
1447 send_tabline_event(idx);
1448 } else if (CloseTabMsgID == msgid) {
1450 const void *bytes = [data bytes];
1451 int idx = *((int*)bytes) + 1;
1452 //NSLog(@"Closing tab %d", idx);
1453 send_tabline_menu_event(idx, TABLINE_MENU_CLOSE);
1454 } else if (AddNewTabMsgID == msgid) {
1455 //NSLog(@"Adding new tab");
1456 send_tabline_menu_event(0, TABLINE_MENU_NEW);
1457 } else if (DraggedTabMsgID == msgid) {
1459 const void *bytes = [data bytes];
1460 // NOTE! The destination index is 0 based, so do not add 1 to make it 1
1462 int idx = *((int*)bytes);
1465 } else if (ScrollWheelMsgID == msgid) {
1467 const void *bytes = [data bytes];
1469 int row = *((int*)bytes); bytes += sizeof(int);
1470 int col = *((int*)bytes); bytes += sizeof(int);
1471 int flags = *((int*)bytes); bytes += sizeof(int);
1472 float dy = *((float*)bytes); bytes += sizeof(float);
1474 int button = MOUSE_5;
1475 if (dy > 0) button = MOUSE_4;
1477 flags = eventModifierFlagsToVimMouseModMask(flags);
1479 gui_send_mouse_event(button, col, row, NO, flags);
1480 } else if (MouseDownMsgID == msgid) {
1482 const void *bytes = [data bytes];
1484 int row = *((int*)bytes); bytes += sizeof(int);
1485 int col = *((int*)bytes); bytes += sizeof(int);
1486 int button = *((int*)bytes); bytes += sizeof(int);
1487 int flags = *((int*)bytes); bytes += sizeof(int);
1488 int count = *((int*)bytes); bytes += sizeof(int);
1490 button = eventButtonNumberToVimMouseButton(button);
1491 flags = eventModifierFlagsToVimMouseModMask(flags);
1493 gui_send_mouse_event(button, col, row, count>1, flags);
1494 } else if (MouseUpMsgID == msgid) {
1496 const void *bytes = [data bytes];
1498 int row = *((int*)bytes); bytes += sizeof(int);
1499 int col = *((int*)bytes); bytes += sizeof(int);
1500 int flags = *((int*)bytes); bytes += sizeof(int);
1502 flags = eventModifierFlagsToVimMouseModMask(flags);
1504 gui_send_mouse_event(MOUSE_RELEASE, col, row, NO, flags);
1505 } else if (MouseDraggedMsgID == msgid) {
1507 const void *bytes = [data bytes];
1509 int row = *((int*)bytes); bytes += sizeof(int);
1510 int col = *((int*)bytes); bytes += sizeof(int);
1511 int flags = *((int*)bytes); bytes += sizeof(int);
1513 flags = eventModifierFlagsToVimMouseModMask(flags);
1515 gui_send_mouse_event(MOUSE_DRAG, col, row, NO, flags);
1516 } else if (SetTextDimensionsMsgID == msgid) {
1518 const void *bytes = [data bytes];
1519 int rows = *((int*)bytes); bytes += sizeof(int);
1520 int cols = *((int*)bytes); bytes += sizeof(int);
1522 // NOTE! Vim doesn't call gui_mch_set_shellsize() after
1523 // gui_resize_shell(), so we have to manually set the rows and columns
1524 // here. (MacVim doesn't change the rows and columns to avoid
1525 // inconsistent states between Vim and MacVim.)
1526 [self setRows:rows columns:cols];
1528 //NSLog(@"[VimTask] Resizing shell to %dx%d.", cols, rows);
1529 gui_resize_shell(cols, rows);
1530 } else if (ExecuteMenuMsgID == msgid) {
1532 const void *bytes = [data bytes];
1533 int tag = *((int*)bytes); bytes += sizeof(int);
1535 vimmenu_T *menu = (vimmenu_T*)tag;
1536 // TODO! Make sure 'menu' is a valid menu pointer!
1540 } else if (ToggleToolbarMsgID == msgid) {
1541 [self handleToggleToolbar];
1542 } else if (ScrollbarEventMsgID == msgid) {
1543 [self handleScrollbarEvent:data];
1544 } else if (SetFontMsgID == msgid) {
1545 [self handleSetFont:data];
1546 } else if (VimShouldCloseMsgID == msgid) {
1548 } else if (DropFilesMsgID == msgid) {
1549 [self handleDropFiles:data];
1550 } else if (DropStringMsgID == msgid) {
1551 [self handleDropString:data];
1552 } else if (GotFocusMsgID == msgid) {
1554 [self focusChange:YES];
1555 } else if (LostFocusMsgID == msgid) {
1557 [self focusChange:NO];
1558 } else if (MouseMovedMsgID == msgid) {
1559 const void *bytes = [data bytes];
1560 int row = *((int*)bytes); bytes += sizeof(int);
1561 int col = *((int*)bytes); bytes += sizeof(int);
1563 gui_mouse_moved(col, row);
1564 } else if (SetMouseShapeMsgID == msgid) {
1565 const void *bytes = [data bytes];
1566 int shape = *((int*)bytes); bytes += sizeof(int);
1567 update_mouseshape(shape);
1569 NSLog(@"WARNING: Unknown message received (msgid=%d)", msgid);
1573 + (NSDictionary *)specialKeys
1575 static NSDictionary *specialKeys = nil;
1578 NSBundle *mainBundle = [NSBundle mainBundle];
1579 NSString *path = [mainBundle pathForResource:@"SpecialKeys"
1581 specialKeys = [[NSDictionary alloc] initWithContentsOfFile:path];
1587 - (void)handleInsertText:(NSData *)data
1591 NSString *key = [[NSString alloc] initWithData:data
1592 encoding:NSUTF8StringEncoding];
1593 char_u *str = (char_u*)[key UTF8String];
1594 int i, len = [key lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
1597 char_u *conv_str = NULL;
1598 if (input_conv.vc_type != CONV_NONE) {
1599 conv_str = string_convert(&input_conv, str, &len);
1605 for (i = 0; i < len; ++i) {
1606 add_to_input_buf(str+i, 1);
1607 if (CSI == str[i]) {
1608 // NOTE: If the converted string contains the byte CSI, then it
1609 // must be followed by the bytes KS_EXTRA, KE_CSI or things
1611 static char_u extra[2] = { KS_EXTRA, KE_CSI };
1612 add_to_input_buf(extra, 2);
1623 - (void)handleKeyDown:(NSString *)key modifiers:(int)mods
1627 char_u *chars = (char_u*)[key UTF8String];
1629 char_u *conv_str = NULL;
1631 int length = [key lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
1633 // Special keys (arrow keys, function keys, etc.) are stored in a plist so
1634 // that new keys can easily be added.
1635 NSString *specialString = [[MMBackend specialKeys]
1637 if (specialString && [specialString length] > 1) {
1638 //NSLog(@"special key: %@", specialString);
1639 int ikey = TO_SPECIAL([specialString characterAtIndex:0],
1640 [specialString characterAtIndex:1]);
1642 ikey = simplify_key(ikey, &mods);
1647 special[1] = K_SECOND(ikey);
1648 special[2] = K_THIRD(ikey);
1652 } else if (1 == length && TAB == chars[0]) {
1653 // Tab is a trouble child:
1654 // - <Tab> is added to the input buffer as is
1655 // - <S-Tab> is translated to, {CSI,'k','B'} (i.e. 'Back-tab')
1656 // - <M-Tab> should be 0x80|TAB but this is not valid utf-8 so it needs
1657 // to be converted to utf-8
1658 // - <S-M-Tab> is translated to <S-Tab> with ALT modifier
1659 // - <C-Tab> is reserved by Mac OS X
1660 // - <D-Tab> is reserved by Mac OS X
1665 if (mods & MOD_MASK_SHIFT) {
1666 mods &= ~MOD_MASK_SHIFT;
1668 special[1] = K_SECOND(K_S_TAB);
1669 special[2] = K_THIRD(K_S_TAB);
1671 } else if (mods & MOD_MASK_ALT) {
1672 int mtab = 0x80 | TAB;
1675 special[0] = (mtab >> 6) + 0xc0;
1676 special[1] = mtab & 0xbf;
1682 mods &= ~MOD_MASK_ALT;
1684 } else if (length > 0) {
1685 unichar c = [key characterAtIndex:0];
1687 //NSLog(@"non-special: %@ (hex=%x, mods=%d)", key,
1688 // [key characterAtIndex:0], mods);
1690 if (length == 1 && ((c == Ctrl_C && ctrl_c_interrupts)
1691 || (c == intr_char && intr_char != Ctrl_C))) {
1696 // HACK! In most circumstances the Ctrl and Shift modifiers should be
1697 // cleared since they are already added to the key by the AppKit.
1698 // Unfortunately, the only way to deal with when to clear the modifiers
1699 // or not seems to be to have hard-wired rules like this.
1700 if ( !((' ' == c) || (0xa0 == c) || (mods & MOD_MASK_CMD)
1701 || 0x9 == c || 0xd == c) ) {
1702 mods &= ~MOD_MASK_SHIFT;
1703 mods &= ~MOD_MASK_CTRL;
1704 //NSLog(@"clear shift ctrl");
1707 // HACK! All Option+key presses go via 'insert text' messages, except
1708 // for <M-Space>. If the Alt flag is not cleared for <M-Space> it does
1709 // not work to map to it.
1710 if (0xa0 == c && !(mods & MOD_MASK_CMD)) {
1711 //NSLog(@"clear alt");
1712 mods &= ~MOD_MASK_ALT;
1716 if (input_conv.vc_type != CONV_NONE) {
1717 conv_str = string_convert(&input_conv, chars, &length);
1724 if (chars && length > 0) {
1726 //NSLog(@"adding mods: %d", mods);
1728 modChars[1] = KS_MODIFIER;
1730 add_to_input_buf(modChars, 3);
1733 //NSLog(@"add to input buf: 0x%x", chars[0]);
1734 // TODO: Check for CSI bytes?
1735 add_to_input_buf(chars, length);
1744 - (void)queueMessage:(int)msgid data:(NSData *)data
1746 [queue addObject:[NSData dataWithBytes:&msgid length:sizeof(int)]];
1748 [queue addObject:data];
1750 [queue addObject:[NSData data]];
1753 - (void)connectionDidDie:(NSNotification *)notification
1755 // If the main connection to MacVim is lost this means that MacVim was
1756 // either quit (by the user chosing Quit on the MacVim menu), or it has
1757 // crashed. In either case our only option is to quit now.
1758 // TODO: Write backup file?
1760 //NSLog(@"A Vim process lots its connection to MacVim; quitting.");
1764 - (void)blinkTimerFired:(NSTimer *)timer
1766 NSTimeInterval timeInterval = 0;
1768 [blinkTimer release];
1771 if (MMBlinkStateOn == blinkState) {
1772 gui_undraw_cursor();
1773 blinkState = MMBlinkStateOff;
1774 timeInterval = blinkOffInterval;
1775 } else if (MMBlinkStateOff == blinkState) {
1776 gui_update_cursor(TRUE, FALSE);
1777 blinkState = MMBlinkStateOn;
1778 timeInterval = blinkOnInterval;
1781 if (timeInterval > 0) {
1783 [[NSTimer scheduledTimerWithTimeInterval:timeInterval target:self
1784 selector:@selector(blinkTimerFired:)
1785 userInfo:nil repeats:NO] retain];
1786 [self flushQueue:YES];
1790 - (void)focusChange:(BOOL)on
1792 gui_focus_change(on);
1795 - (void)processInputBegin
1797 inProcessInput = YES;
1799 // Don't flush too soon or update speed will suffer.
1800 [lastFlushDate release];
1801 lastFlushDate = [[NSDate date] retain];
1804 - (void)processInputEnd
1806 #if MM_USE_INPUT_QUEUE
1807 int count = [inputQueue count];
1809 // TODO: This is troubling, but it is not hard to get Vim to end up
1810 // here. Why does this happen?
1811 NSLog(@"WARNING: inputQueue has odd number of objects (%d)", count);
1812 [inputQueue removeAllObjects];
1813 } else if (count > 0) {
1814 // TODO: Dispatch these messages? Maybe not; usually when the
1815 // 'inputQueue' is non-empty it means that a LOT of messages has been
1816 // sent simultaneously. The only way this happens is when Vim is being
1817 // tormented, e.g. if the user holds down <D-`> to rapidly switch
1820 for (i = 0; i < count; i+=2) {
1821 int msgid = [[inputQueue objectAtIndex:i] intValue];
1822 NSLog(@"%s: Dropping message %s", _cmd, MessageStrings[msgid]);
1825 [inputQueue removeAllObjects];
1829 inputReceived = YES;
1830 inProcessInput = NO;
1833 - (void)handleToggleToolbar
1835 // If 'go' contains 'T', then remove it, else add it.
1837 char_u go[sizeof(GO_ALL)+2];
1842 p = vim_strchr(go, GO_TOOLBAR);
1846 char_u *end = go + len;
1852 go[len] = GO_TOOLBAR;
1856 set_option_value((char_u*)"guioptions", 0, go, 0);
1858 // Force screen redraw (does it have to be this complicated?).
1859 redraw_all_later(CLEAR);
1860 update_screen(NOT_VALID);
1863 gui_update_cursor(FALSE, FALSE);
1867 - (void)handleScrollbarEvent:(NSData *)data
1871 const void *bytes = [data bytes];
1872 long ident = *((long*)bytes); bytes += sizeof(long);
1873 int hitPart = *((int*)bytes); bytes += sizeof(int);
1874 float fval = *((float*)bytes); bytes += sizeof(float);
1875 scrollbar_T *sb = gui_find_scrollbar(ident);
1878 scrollbar_T *sb_info = sb->wp ? &sb->wp->w_scrollbars[0] : sb;
1879 long value = sb_info->value;
1880 long size = sb_info->size;
1881 long max = sb_info->max;
1882 BOOL isStillDragging = NO;
1883 BOOL updateKnob = YES;
1886 case NSScrollerDecrementPage:
1887 value -= (size > 2 ? size - 2 : 1);
1889 case NSScrollerIncrementPage:
1890 value += (size > 2 ? size - 2 : 1);
1892 case NSScrollerDecrementLine:
1895 case NSScrollerIncrementLine:
1898 case NSScrollerKnob:
1899 isStillDragging = YES;
1901 case NSScrollerKnobSlot:
1902 value = (long)(fval * (max - size + 1));
1909 //NSLog(@"value %d -> %d", sb_info->value, value);
1910 gui_drag_scrollbar(sb, value, isStillDragging);
1913 // Dragging the knob or option+clicking automatically updates
1914 // the knob position (on the actual NSScroller), so we only
1915 // need to set the knob position in the other cases.
1917 // Update both the left&right vertical scrollbars.
1918 long identLeft = sb->wp->w_scrollbars[SBAR_LEFT].ident;
1919 long identRight = sb->wp->w_scrollbars[SBAR_RIGHT].ident;
1920 [self setScrollbarThumbValue:value size:size max:max
1921 identifier:identLeft];
1922 [self setScrollbarThumbValue:value size:size max:max
1923 identifier:identRight];
1925 // Update the horizontal scrollbar.
1926 [self setScrollbarThumbValue:value size:size max:max
1933 - (void)handleSetFont:(NSData *)data
1937 const void *bytes = [data bytes];
1938 float pointSize = *((float*)bytes); bytes += sizeof(float);
1939 //unsigned len = *((unsigned*)bytes); bytes += sizeof(unsigned);
1940 bytes += sizeof(unsigned); // len not used
1942 NSMutableString *name = [NSMutableString stringWithUTF8String:bytes];
1943 [name appendString:[NSString stringWithFormat:@":h%.2f", pointSize]];
1944 char_u *s = (char_u*)[name UTF8String];
1947 s = CONVERT_FROM_UTF8(s);
1950 set_option_value((char_u*)"guifont", 0, s, 0);
1953 CONVERT_FROM_UTF8_FREE(s);
1956 // Force screen redraw (does it have to be this complicated?).
1957 redraw_all_later(CLEAR);
1958 update_screen(NOT_VALID);
1961 gui_update_cursor(FALSE, FALSE);
1965 - (void)handleDropFiles:(NSData *)data
1970 const void *bytes = [data bytes];
1971 const void *end = [data bytes] + [data length];
1972 int n = *((int*)bytes); bytes += sizeof(int);
1974 if (State & CMDLINE) {
1975 // HACK! If Vim is in command line mode then the files names
1976 // should be added to the command line, instead of opening the
1977 // files in tabs. This is taken care of by gui_handle_drop().
1978 char_u **fnames = (char_u **)alloc(n * sizeof(char_u *));
1981 while (bytes < end && i < n) {
1982 int len = *((int*)bytes); bytes += sizeof(int);
1983 char_u *s = (char_u*)bytes;
1985 s = CONVERT_FROM_UTF8(s);
1987 fnames[i++] = vim_strsave(s);
1989 CONVERT_FROM_UTF8_FREE(s);
1994 // NOTE! This function will free 'fnames'.
1995 // HACK! It is assumed that the 'x' and 'y' arguments are
1996 // unused when in command line mode.
1997 gui_handle_drop(0, 0, 0, fnames, i < n ? i : n);
2000 // HACK! I'm not sure how to get Vim to open a list of files in
2001 // tabs, so instead I create a ':tab drop' command with all the
2002 // files to open and execute it.
2003 NSMutableString *cmd = (n > 1)
2004 ? [NSMutableString stringWithString:@":tab drop"]
2005 : [NSMutableString stringWithString:@":drop"];
2008 for (i = 0; i < n && bytes < end; ++i) {
2009 int len = *((int*)bytes); bytes += sizeof(int);
2010 NSString *file = [NSString stringWithUTF8String:bytes];
2011 file = [file stringByEscapingSpecialFilenameCharacters];
2014 [cmd appendString:@" "];
2015 [cmd appendString:file];
2018 // By going to the last tabpage we ensure that the new tabs will
2019 // appear last (if this call is left out, the taborder becomes
2023 char_u *s = (char_u*)[cmd UTF8String];
2025 s = CONVERT_FROM_UTF8(s);
2029 CONVERT_FROM_UTF8_FREE(s);
2032 // Force screen redraw (does it have to be this complicated?).
2033 // (This code was taken from the end of gui_handle_drop().)
2034 update_screen(NOT_VALID);
2037 gui_update_cursor(FALSE, FALSE);
2043 - (void)handleDropString:(NSData *)data
2048 char_u dropkey[3] = { CSI, KS_EXTRA, (char_u)KE_DROP };
2049 const void *bytes = [data bytes];
2050 int len = *((int*)bytes); bytes += sizeof(int);
2051 NSMutableString *string = [NSMutableString stringWithUTF8String:bytes];
2053 // Replace unrecognized end-of-line sequences with \x0a (line feed).
2054 NSRange range = { 0, [string length] };
2055 unsigned n = [string replaceOccurrencesOfString:@"\x0d\x0a"
2056 withString:@"\x0a" options:0
2059 n = [string replaceOccurrencesOfString:@"\x0d" withString:@"\x0a"
2060 options:0 range:range];
2063 len = [string lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
2064 char_u *s = (char_u*)[string UTF8String];
2066 if (input_conv.vc_type != CONV_NONE)
2067 s = string_convert(&input_conv, s, &len);
2069 dnd_yank_drag_data(s, len);
2071 if (input_conv.vc_type != CONV_NONE)
2074 add_to_input_buf(dropkey, sizeof(dropkey));
2078 @end // MMBackend (Private)
2083 @implementation MMBackend (ClientServer)
2085 - (NSString *)connectionNameFromServerName:(NSString *)name
2087 NSString *bundleIdentifier = [[NSBundle mainBundle] bundleIdentifier];
2089 return [[NSString stringWithFormat:@"%@.%@", bundleIdentifier, name]
2093 - (NSConnection *)connectionForServerName:(NSString *)name
2095 // TODO: Try 'name%d' if 'name' fails.
2096 NSString *connName = [self connectionNameFromServerName:name];
2097 NSConnection *svrConn = [connectionNameDict objectForKey:connName];
2100 svrConn = [NSConnection connectionWithRegisteredName:connName
2102 // Try alternate server...
2103 if (!svrConn && alternateServerName) {
2104 //NSLog(@" trying to connect to alternate server: %@",
2105 // alternateServerName);
2106 connName = [self connectionNameFromServerName:alternateServerName];
2107 svrConn = [NSConnection connectionWithRegisteredName:connName
2111 // Try looking for alternate servers...
2113 //NSLog(@" looking for alternate servers...");
2114 NSString *alt = [self alternateServerNameForName:name];
2115 if (alt != alternateServerName) {
2116 //NSLog(@" found alternate server: %@", string);
2117 [alternateServerName release];
2118 alternateServerName = [alt copy];
2122 // Try alternate server again...
2123 if (!svrConn && alternateServerName) {
2124 //NSLog(@" trying to connect to alternate server: %@",
2125 // alternateServerName);
2126 connName = [self connectionNameFromServerName:alternateServerName];
2127 svrConn = [NSConnection connectionWithRegisteredName:connName
2132 [connectionNameDict setObject:svrConn forKey:connName];
2134 //NSLog(@"Adding %@ as connection observer for %@", self, svrConn);
2135 [[NSNotificationCenter defaultCenter] addObserver:self
2136 selector:@selector(serverConnectionDidDie:)
2137 name:NSConnectionDidDieNotification object:svrConn];
2144 - (NSConnection *)connectionForServerPort:(int)port
2147 NSEnumerator *e = [connectionNameDict objectEnumerator];
2149 while ((conn = [e nextObject])) {
2150 // HACK! Assume connection uses mach ports.
2151 if (port == [(NSMachPort*)[conn sendPort] machPort])
2158 - (void)serverConnectionDidDie:(NSNotification *)notification
2160 //NSLog(@"%s%@", _cmd, notification);
2162 NSConnection *svrConn = [notification object];
2164 //NSLog(@"Removing %@ as connection observer from %@", self, svrConn);
2165 [[NSNotificationCenter defaultCenter]
2167 name:NSConnectionDidDieNotification
2170 [connectionNameDict removeObjectsForKeys:
2171 [connectionNameDict allKeysForObject:svrConn]];
2173 // HACK! Assume connection uses mach ports.
2174 int port = [(NSMachPort*)[svrConn sendPort] machPort];
2175 NSNumber *key = [NSNumber numberWithInt:port];
2177 [clientProxyDict removeObjectForKey:key];
2178 [serverReplyDict removeObjectForKey:key];
2181 - (void)addClient:(NSDistantObject *)client
2183 NSConnection *conn = [client connectionForProxy];
2184 // HACK! Assume connection uses mach ports.
2185 int port = [(NSMachPort*)[conn sendPort] machPort];
2186 NSNumber *key = [NSNumber numberWithInt:port];
2188 if (![clientProxyDict objectForKey:key]) {
2189 [client setProtocolForProxy:@protocol(MMVimClientProtocol)];
2190 [clientProxyDict setObject:client forKey:key];
2193 // NOTE: 'clientWindow' is a global variable which is used by <client>
2194 clientWindow = port;
2197 - (NSString *)alternateServerNameForName:(NSString *)name
2199 if (!(name && [name length] > 0))
2202 // Only look for alternates if 'name' doesn't end in a digit.
2203 unichar lastChar = [name characterAtIndex:[name length]-1];
2204 if (lastChar >= '0' && lastChar <= '9')
2207 // Look for alternates among all current servers.
2208 NSArray *list = [self serverList];
2209 if (!(list && [list count] > 0))
2212 // Filter out servers starting with 'name' and ending with a number. The
2213 // (?i) pattern ensures that the match is case insensitive.
2214 NSString *pat = [NSString stringWithFormat:@"(?i)%@[0-9]+\\z", name];
2215 NSPredicate *pred = [NSPredicate predicateWithFormat:
2216 @"SELF MATCHES %@", pat];
2217 list = [list filteredArrayUsingPredicate:pred];
2218 if ([list count] > 0) {
2219 list = [list sortedArrayUsingSelector:@selector(serverNameCompare:)];
2220 return [list objectAtIndex:0];
2226 @end // MMBackend (ClientServer)
2231 @implementation NSString (MMServerNameCompare)
2232 - (NSComparisonResult)serverNameCompare:(NSString *)string
2234 return [self compare:string
2235 options:NSCaseInsensitiveSearch|NSNumericSearch];
2242 static int eventModifierFlagsToVimModMask(int modifierFlags)
2246 if (modifierFlags & NSShiftKeyMask)
2247 modMask |= MOD_MASK_SHIFT;
2248 if (modifierFlags & NSControlKeyMask)
2249 modMask |= MOD_MASK_CTRL;
2250 if (modifierFlags & NSAlternateKeyMask)
2251 modMask |= MOD_MASK_ALT;
2252 if (modifierFlags & NSCommandKeyMask)
2253 modMask |= MOD_MASK_CMD;
2258 static int vimModMaskToEventModifierFlags(int mods)
2262 if (mods & MOD_MASK_SHIFT)
2263 flags |= NSShiftKeyMask;
2264 if (mods & MOD_MASK_CTRL)
2265 flags |= NSControlKeyMask;
2266 if (mods & MOD_MASK_ALT)
2267 flags |= NSAlternateKeyMask;
2268 if (mods & MOD_MASK_CMD)
2269 flags |= NSCommandKeyMask;
2274 static int eventModifierFlagsToVimMouseModMask(int modifierFlags)
2278 if (modifierFlags & NSShiftKeyMask)
2279 modMask |= MOUSE_SHIFT;
2280 if (modifierFlags & NSControlKeyMask)
2281 modMask |= MOUSE_CTRL;
2282 if (modifierFlags & NSAlternateKeyMask)
2283 modMask |= MOUSE_ALT;
2288 static int eventButtonNumberToVimMouseButton(int buttonNumber)
2290 static int mouseButton[] = { MOUSE_LEFT, MOUSE_RIGHT, MOUSE_MIDDLE,
2291 MOUSE_X1, MOUSE_X2 };
2293 return mouseButton[buttonNumber < 5 ? buttonNumber : 0];
2296 static int specialKeyToNSKey(int key)
2298 if (!IS_SPECIAL(key))
2305 { K_UP, NSUpArrowFunctionKey },
2306 { K_DOWN, NSDownArrowFunctionKey },
2307 { K_LEFT, NSLeftArrowFunctionKey },
2308 { K_RIGHT, NSRightArrowFunctionKey },
2309 { K_F1, NSF1FunctionKey },
2310 { K_F2, NSF2FunctionKey },
2311 { K_F3, NSF3FunctionKey },
2312 { K_F4, NSF4FunctionKey },
2313 { K_F5, NSF5FunctionKey },
2314 { K_F6, NSF6FunctionKey },
2315 { K_F7, NSF7FunctionKey },
2316 { K_F8, NSF8FunctionKey },
2317 { K_F9, NSF9FunctionKey },
2318 { K_F10, NSF10FunctionKey },
2319 { K_F11, NSF11FunctionKey },
2320 { K_F12, NSF12FunctionKey },
2321 { K_F13, NSF13FunctionKey },
2322 { K_F14, NSF14FunctionKey },
2323 { K_F15, NSF15FunctionKey },
2324 { K_F16, NSF16FunctionKey },
2325 { K_F17, NSF17FunctionKey },
2326 { K_F18, NSF18FunctionKey },
2327 { K_F19, NSF19FunctionKey },
2328 { K_F20, NSF20FunctionKey },
2329 { K_F21, NSF21FunctionKey },
2330 { K_F22, NSF22FunctionKey },
2331 { K_F23, NSF23FunctionKey },
2332 { K_F24, NSF24FunctionKey },
2333 { K_F25, NSF25FunctionKey },
2334 { K_F26, NSF26FunctionKey },
2335 { K_F27, NSF27FunctionKey },
2336 { K_F28, NSF28FunctionKey },
2337 { K_F29, NSF29FunctionKey },
2338 { K_F30, NSF30FunctionKey },
2339 { K_F31, NSF31FunctionKey },
2340 { K_F32, NSF32FunctionKey },
2341 { K_F33, NSF33FunctionKey },
2342 { K_F34, NSF34FunctionKey },
2343 { K_F35, NSF35FunctionKey },
2344 { K_DEL, NSBackspaceCharacter },
2345 { K_BS, NSDeleteCharacter },
2346 { K_HOME, NSHomeFunctionKey },
2347 { K_END, NSEndFunctionKey },
2348 { K_PAGEUP, NSPageUpFunctionKey },
2349 { K_PAGEDOWN, NSPageDownFunctionKey }
2353 for (i = 0; i < sizeof(sp2ns)/sizeof(sp2ns[0]); ++i) {
2354 if (sp2ns[i].special == key)
2355 return sp2ns[i].nskey;