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.
13 * MMBackend communicates with the frontend (MacVim). It maintains a queue of
14 * output which is flushed to the frontend under controlled circumstances (so
15 * as to maintain a steady framerate). Input from the frontend is also handled
18 * The frontend communicates with the backend via the MMBackendProtocol. In
19 * particular, input is sent to the backend via processInput:data: and Vim
20 * state can be queried from the frontend with evaluateExpression:.
22 * It is very important to realize that all state is held by the backend, the
23 * frontend must either ask for state [MMBackend evaluateExpression:] or wait
24 * for the backend to update [MMVimController processCommandQueue:].
26 * The client/server functionality of Vim is handled by the backend. It sets
27 * up a named NSConnection to which other Vim processes can connect.
34 // NOTE: Colors in MMBackend are stored as unsigned ints on the form 0xaarrggbb
35 // whereas colors in Vim are int without the alpha component. Also note that
36 // 'transp' is assumed to be a value between 0 and 100.
37 #define MM_COLOR(col) ((unsigned)( ((col)&0xffffff) | 0xff000000 ))
38 #define MM_COLOR_WITH_TRANSP(col,transp) \
39 ((unsigned)( ((col)&0xffffff) \
40 | ((((unsigned)((((100-(transp))*255)/100)+.5f))&0xff)<<24) ))
43 // This constant controls how often the command queue may be flushed. If it is
44 // too small the app might feel unresponsive; if it is too large there might be
45 // long periods without the screen updating (e.g. when sourcing a large session
46 // file). (The unit is seconds.)
47 static float MMFlushTimeoutInterval = 0.1f;
48 static int MMFlushQueueLenHint = 80*40;
50 static unsigned MMServerMax = 1000;
52 // TODO: Move to separate file.
53 static int eventModifierFlagsToVimModMask(int modifierFlags);
54 static int vimModMaskToEventModifierFlags(int mods);
55 static int eventModifierFlagsToVimMouseModMask(int modifierFlags);
56 static int eventButtonNumberToVimMouseButton(int buttonNumber);
57 static int specialKeyToNSKey(int key);
67 @interface NSString (MMServerNameCompare)
68 - (NSComparisonResult)serverNameCompare:(NSString *)string;
73 @interface MMBackend (Private)
74 - (void)processInputQueue;
75 - (void)handleInputEvent:(int)msgid data:(NSData *)data;
76 + (NSDictionary *)specialKeys;
77 - (void)handleInsertText:(NSData *)data;
78 - (void)handleKeyDown:(NSString *)key modifiers:(int)mods;
79 - (void)queueMessage:(int)msgid data:(NSData *)data;
80 - (void)connectionDidDie:(NSNotification *)notification;
81 - (void)blinkTimerFired:(NSTimer *)timer;
82 - (void)focusChange:(BOOL)on;
83 - (void)handleToggleToolbar;
84 - (void)handleScrollbarEvent:(NSData *)data;
85 - (void)handleSetFont:(NSData *)data;
86 - (void)handleDropFiles:(NSData *)data;
87 - (void)handleDropString:(NSData *)data;
88 - (BOOL)checkForModifiedBuffers;
89 - (void)addInput:(NSString *)input;
94 @interface MMBackend (ClientServer)
95 - (NSString *)connectionNameFromServerName:(NSString *)name;
96 - (NSConnection *)connectionForServerName:(NSString *)name;
97 - (NSConnection *)connectionForServerPort:(int)port;
98 - (void)serverConnectionDidDie:(NSNotification *)notification;
99 - (void)addClient:(NSDistantObject *)client;
100 - (NSString *)alternateServerNameForName:(NSString *)name;
105 @implementation MMBackend
107 + (MMBackend *)sharedInstance
109 static MMBackend *singleton = nil;
110 return singleton ? singleton : (singleton = [MMBackend new]);
115 if ((self = [super init])) {
116 fontContainerRef = loadFonts();
118 outputQueue = [[NSMutableArray alloc] init];
119 inputQueue = [[NSMutableArray alloc] init];
120 drawData = [[NSMutableData alloc] initWithCapacity:1024];
121 connectionNameDict = [[NSMutableDictionary alloc] init];
122 clientProxyDict = [[NSMutableDictionary alloc] init];
123 serverReplyDict = [[NSMutableDictionary alloc] init];
125 NSString *path = [[NSBundle mainBundle] pathForResource:@"Colors"
128 colorDict = [[NSDictionary dictionaryWithContentsOfFile:path]
131 NSLog(@"WARNING: Could not locate Colors.plist.");
134 path = [[NSBundle mainBundle] pathForResource:@"SystemColors"
137 sysColorDict = [[NSDictionary dictionaryWithContentsOfFile:path]
140 NSLog(@"WARNING: Could not locate SystemColors.plist.");
149 //NSLog(@"%@ %s", [self className], _cmd);
150 [[NSNotificationCenter defaultCenter] removeObserver:self];
152 [oldWideFont release]; oldWideFont = nil;
153 [blinkTimer release]; blinkTimer = nil;
154 [alternateServerName release]; alternateServerName = nil;
155 [serverReplyDict release]; serverReplyDict = nil;
156 [clientProxyDict release]; clientProxyDict = nil;
157 [connectionNameDict release]; connectionNameDict = nil;
158 [inputQueue release]; inputQueue = nil;
159 [outputQueue release]; outputQueue = nil;
160 [drawData release]; drawData = nil;
161 [frontendProxy release]; frontendProxy = nil;
162 [connection release]; connection = nil;
163 [sysColorDict release]; sysColorDict = nil;
164 [colorDict release]; colorDict = nil;
169 - (void)setBackgroundColor:(int)color
171 backgroundColor = MM_COLOR_WITH_TRANSP(color,p_transp);
174 - (void)setForegroundColor:(int)color
176 foregroundColor = MM_COLOR(color);
179 - (void)setSpecialColor:(int)color
181 specialColor = MM_COLOR(color);
184 - (void)setDefaultColorsBackground:(int)bg foreground:(int)fg
186 defaultBackgroundColor = MM_COLOR_WITH_TRANSP(bg,p_transp);
187 defaultForegroundColor = MM_COLOR(fg);
189 NSMutableData *data = [NSMutableData data];
191 [data appendBytes:&defaultBackgroundColor length:sizeof(unsigned)];
192 [data appendBytes:&defaultForegroundColor length:sizeof(unsigned)];
194 [self queueMessage:SetDefaultColorsMsgID data:data];
197 - (NSConnection *)connection
200 // NOTE! If the name of the connection changes here it must also be
201 // updated in MMAppController.m.
202 NSString *name = [NSString stringWithFormat:@"%@-connection",
203 [[NSBundle mainBundle] bundleIdentifier]];
205 connection = [NSConnection connectionWithRegisteredName:name host:nil];
209 // NOTE: 'connection' may be nil here.
215 if (![self connection]) {
216 NSBundle *mainBundle = [NSBundle mainBundle];
218 NSString *path = [mainBundle bundlePath];
219 if (![[NSWorkspace sharedWorkspace] launchApplication:path]) {
220 NSLog(@"WARNING: Failed to launch GUI with path %@", path);
224 // HACK! It would be preferable to launch the GUI using NSWorkspace,
225 // however I have not managed to figure out how to pass arguments using
228 // NOTE! Using NSTask to launch the GUI has the negative side-effect
229 // that the GUI won't be activated (or raised) so there is a hack in
230 // MMWindowController which always raises the app when a new window is
232 NSMutableArray *args = [NSMutableArray arrayWithObjects:
233 [NSString stringWithFormat:@"-%@", MMNoWindowKey], @"yes", nil];
234 NSString *exeName = [[mainBundle infoDictionary]
235 objectForKey:@"CFBundleExecutable"];
236 NSString *path = [mainBundle pathForAuxiliaryExecutable:exeName];
238 NSLog(@"ERROR: Could not find MacVim executable in bundle");
242 [NSTask launchedTaskWithLaunchPath:path arguments:args];
245 // HACK! The NSWorkspaceDidLaunchApplicationNotification does not work
246 // for tasks like this, so poll the mach bootstrap server until it
247 // returns a valid connection. Also set a time-out date so that we
248 // don't get stuck doing this forever.
249 NSDate *timeOutDate = [NSDate dateWithTimeIntervalSinceNow:15];
250 while (!connection &&
251 NSOrderedDescending == [timeOutDate compare:[NSDate date]])
253 [[NSRunLoop currentRunLoop]
254 runMode:NSDefaultRunLoopMode
255 beforeDate:[NSDate dateWithTimeIntervalSinceNow:1]];
257 // NOTE: This call will set 'connection' as a side-effect.
262 NSLog(@"WARNING: Timed-out waiting for GUI to launch.");
267 id proxy = [connection rootProxy];
268 [proxy setProtocolForProxy:@protocol(MMAppProtocol)];
270 [[NSNotificationCenter defaultCenter] addObserver:self
271 selector:@selector(connectionDidDie:)
272 name:NSConnectionDidDieNotification object:connection];
274 int pid = [[NSProcessInfo processInfo] processIdentifier];
277 frontendProxy = [proxy connectBackend:self pid:pid];
279 @catch (NSException *e) {
280 NSLog(@"Exception caught when trying to connect backend: \"%@\"", e);
284 [frontendProxy retain];
285 [frontendProxy setProtocolForProxy:@protocol(MMAppProtocol)];
288 return connection && frontendProxy;
291 - (BOOL)openVimWindow
293 NSLog(@"openVimWindow");
294 [self queueMessage:OpenVimWindowMsgID data:nil];
300 int type = ClearAllDrawType;
302 // Any draw commands in queue are effectively obsolete since this clearAll
303 // will negate any effect they have, therefore we may as well clear the
305 [drawData setLength:0];
307 [drawData appendBytes:&type length:sizeof(int)];
310 - (void)clearBlockFromRow:(int)row1 column:(int)col1
311 toRow:(int)row2 column:(int)col2
313 int type = ClearBlockDrawType;
315 [drawData appendBytes:&type length:sizeof(int)];
317 [drawData appendBytes:&defaultBackgroundColor length:sizeof(unsigned)];
318 [drawData appendBytes:&row1 length:sizeof(int)];
319 [drawData appendBytes:&col1 length:sizeof(int)];
320 [drawData appendBytes:&row2 length:sizeof(int)];
321 [drawData appendBytes:&col2 length:sizeof(int)];
324 - (void)deleteLinesFromRow:(int)row count:(int)count
325 scrollBottom:(int)bottom left:(int)left right:(int)right
327 int type = DeleteLinesDrawType;
329 [drawData appendBytes:&type length:sizeof(int)];
331 [drawData appendBytes:&defaultBackgroundColor length:sizeof(unsigned)];
332 [drawData appendBytes:&row length:sizeof(int)];
333 [drawData appendBytes:&count length:sizeof(int)];
334 [drawData appendBytes:&bottom length:sizeof(int)];
335 [drawData appendBytes:&left length:sizeof(int)];
336 [drawData appendBytes:&right length:sizeof(int)];
339 - (void)drawString:(char*)s length:(int)len row:(int)row column:(int)col
340 cells:(int)cells flags:(int)flags
342 if (len <= 0 || cells <= 0) return;
344 int type = DrawStringDrawType;
346 [drawData appendBytes:&type length:sizeof(int)];
348 [drawData appendBytes:&backgroundColor length:sizeof(unsigned)];
349 [drawData appendBytes:&foregroundColor length:sizeof(unsigned)];
350 [drawData appendBytes:&specialColor length:sizeof(unsigned)];
351 [drawData appendBytes:&row length:sizeof(int)];
352 [drawData appendBytes:&col length:sizeof(int)];
353 [drawData appendBytes:&cells length:sizeof(int)];
354 [drawData appendBytes:&flags length:sizeof(int)];
355 [drawData appendBytes:&len length:sizeof(int)];
356 [drawData appendBytes:s length:len];
359 - (void)insertLinesFromRow:(int)row count:(int)count
360 scrollBottom:(int)bottom left:(int)left right:(int)right
362 int type = InsertLinesDrawType;
364 [drawData appendBytes:&type length:sizeof(int)];
366 [drawData appendBytes:&defaultBackgroundColor length:sizeof(unsigned)];
367 [drawData appendBytes:&row length:sizeof(int)];
368 [drawData appendBytes:&count length:sizeof(int)];
369 [drawData appendBytes:&bottom length:sizeof(int)];
370 [drawData appendBytes:&left length:sizeof(int)];
371 [drawData appendBytes:&right length:sizeof(int)];
374 - (void)drawCursorAtRow:(int)row column:(int)col shape:(int)shape
375 fraction:(int)percent color:(int)color
377 int type = DrawCursorDrawType;
378 unsigned uc = MM_COLOR(color);
380 [drawData appendBytes:&type length:sizeof(int)];
382 [drawData appendBytes:&uc length:sizeof(unsigned)];
383 [drawData appendBytes:&row length:sizeof(int)];
384 [drawData appendBytes:&col length:sizeof(int)];
385 [drawData appendBytes:&shape length:sizeof(int)];
386 [drawData appendBytes:&percent length:sizeof(int)];
391 // Tend to the run loop, returning immediately if there are no events
393 [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
394 beforeDate:[NSDate distantPast]];
396 // Keyboard and mouse input is handled directly, other input is queued and
397 // processed here. This call may enter a blocking loop.
398 if ([inputQueue count] > 0)
399 [self processInputQueue];
402 - (void)flushQueue:(BOOL)force
404 // NOTE! This method gets called a lot; if we were to flush every time it
405 // got called MacVim would feel unresponsive. So there is a time out which
406 // ensures that the queue isn't flushed too often.
407 if (!force && lastFlushDate && -[lastFlushDate timeIntervalSinceNow]
408 < MMFlushTimeoutInterval
409 && [drawData length] < MMFlushQueueLenHint)
412 if ([drawData length] > 0) {
413 // HACK! Detect changes to 'guifontwide'.
414 if (gui.wide_font != (GuiFont)oldWideFont) {
415 [oldWideFont release];
416 oldWideFont = [(NSFont*)gui.wide_font retain];
417 [self setWideFont:oldWideFont];
420 [self queueMessage:BatchDrawMsgID data:[drawData copy]];
421 [drawData setLength:0];
424 if ([outputQueue count] > 0) {
426 [frontendProxy processCommandQueue:outputQueue];
428 @catch (NSException *e) {
429 NSLog(@"Exception caught when processing command queue: \"%@\"", e);
432 [outputQueue removeAllObjects];
434 [lastFlushDate release];
435 lastFlushDate = [[NSDate date] retain];
439 - (BOOL)waitForInput:(int)milliseconds
441 //NSLog(@"|ENTER| %s%d", _cmd, milliseconds);
442 NSDate *date = milliseconds > 0 ?
443 [NSDate dateWithTimeIntervalSinceNow:.001*milliseconds] :
444 [NSDate distantFuture];
446 [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:date];
448 // I know of no way to figure out if the run loop exited because input was
449 // found or because of a time out, so I need to manually indicate when
450 // input was received in processInput:data: and then reset it every time
452 BOOL yn = inputReceived;
455 if ([inputQueue count] > 0)
456 [self processInputQueue];
458 //NSLog(@"|LEAVE| %s input=%d", _cmd, yn);
464 #ifdef MAC_CLIENTSERVER
465 // The default connection is used for the client/server code.
466 [[NSConnection defaultConnection] setRootObject:nil];
467 [[NSConnection defaultConnection] invalidate];
470 // By invalidating the NSConnection the MMWindowController immediately
471 // finds out that the connection is down and as a result
472 // [MMWindowController connectionDidDie:] is invoked.
473 //NSLog(@"%@ %s", [self className], _cmd);
474 [[NSNotificationCenter defaultCenter] removeObserver:self];
475 [connection invalidate];
477 if (fontContainerRef) {
478 ATSFontDeactivate(fontContainerRef, NULL, kATSOptionFlagsDefault);
479 fontContainerRef = 0;
484 - (void)selectTab:(int)index
486 //NSLog(@"%s%d", _cmd, index);
489 NSData *data = [NSData dataWithBytes:&index length:sizeof(int)];
490 [self queueMessage:SelectTabMsgID data:data];
495 //NSLog(@"%s", _cmd);
497 NSMutableData *data = [NSMutableData data];
499 int idx = tabpage_index(curtab) - 1;
500 [data appendBytes:&idx length:sizeof(int)];
503 for (tp = first_tabpage; tp != NULL; tp = tp->tp_next) {
504 // This function puts the label of the tab in the global 'NameBuff'.
505 get_tabline_label(tp, FALSE);
506 char_u *s = NameBuff;
508 if (len <= 0) continue;
511 s = CONVERT_TO_UTF8(s);
514 // Count the number of windows in the tabpage.
515 //win_T *wp = tp->tp_firstwin;
517 //for (wincount = 0; wp != NULL; wp = wp->w_next, ++wincount);
519 //[data appendBytes:&wincount length:sizeof(int)];
520 [data appendBytes:&len length:sizeof(int)];
521 [data appendBytes:s length:len];
524 CONVERT_TO_UTF8_FREE(s);
528 [self queueMessage:UpdateTabBarMsgID data:data];
531 - (BOOL)tabBarVisible
533 return tabBarVisible;
536 - (void)showTabBar:(BOOL)enable
538 tabBarVisible = enable;
540 int msgid = enable ? ShowTabBarMsgID : HideTabBarMsgID;
541 [self queueMessage:msgid data:nil];
544 - (void)setRows:(int)rows columns:(int)cols
546 //NSLog(@"[VimTask] setRows:%d columns:%d", rows, cols);
548 int dim[] = { rows, cols };
549 NSData *data = [NSData dataWithBytes:&dim length:2*sizeof(int)];
551 [self queueMessage:SetTextDimensionsMsgID data:data];
554 - (void)setWindowTitle:(char *)title
556 NSMutableData *data = [NSMutableData data];
557 int len = strlen(title);
558 if (len <= 0) return;
560 [data appendBytes:&len length:sizeof(int)];
561 [data appendBytes:title length:len];
563 [self queueMessage:SetWindowTitleMsgID data:data];
566 - (char *)browseForFileInDirectory:(char *)dir title:(char *)title
569 //NSLog(@"browseForFileInDirectory:%s title:%s saving:%d", dir, title,
573 NSString *ds = dir ? [NSString stringWithUTF8String:dir] : nil;
574 NSString *ts = title ? [NSString stringWithUTF8String:title] : nil;
576 [frontendProxy showSavePanelForDirectory:ds title:ts saving:saving];
578 // Wait until a reply is sent from MMVimController.
579 [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
580 beforeDate:[NSDate distantFuture]];
582 if (dialogReturn && [dialogReturn isKindOfClass:[NSString class]]) {
583 char_u *ret = (char_u*)[dialogReturn UTF8String];
585 ret = CONVERT_FROM_UTF8(ret);
587 s = vim_strsave(ret);
589 CONVERT_FROM_UTF8_FREE(ret);
593 [dialogReturn release]; dialogReturn = nil;
595 @catch (NSException *e) {
596 NSLog(@"Exception caught when showing save panel: \"%@\"", e);
602 - (oneway void)setDialogReturn:(in bycopy id)obj
604 // NOTE: This is called by
605 // - [MMVimController panelDidEnd:::], and
606 // - [MMVimController alertDidEnd:::],
607 // to indicate that a save/open panel or alert has finished.
609 if (obj != dialogReturn) {
610 [dialogReturn release];
611 dialogReturn = [obj retain];
615 - (int)presentDialogWithType:(int)type title:(char *)title message:(char *)msg
616 buttons:(char *)btns textField:(char *)txtfield
619 NSString *message = nil, *text = nil, *textFieldString = nil;
620 NSArray *buttons = nil;
621 int style = NSInformationalAlertStyle;
623 if (VIM_WARNING == type) style = NSWarningAlertStyle;
624 else if (VIM_ERROR == type) style = NSCriticalAlertStyle;
627 NSString *btnString = [NSString stringWithUTF8String:btns];
628 buttons = [btnString componentsSeparatedByString:@"\n"];
631 message = [NSString stringWithUTF8String:title];
633 text = [NSString stringWithUTF8String:msg];
635 // HACK! If there is a '\n\n' or '\n' sequence in the message, then
636 // make the part up to there into the title. We only do this
637 // because Vim has lots of dialogs without a title and they look
639 // TODO: Fix the actual dialog texts.
640 NSRange eolRange = [text rangeOfString:@"\n\n"];
641 if (NSNotFound == eolRange.location)
642 eolRange = [text rangeOfString:@"\n"];
643 if (NSNotFound != eolRange.location) {
644 message = [text substringToIndex:eolRange.location];
645 text = [text substringFromIndex:NSMaxRange(eolRange)];
650 textFieldString = [NSString stringWithUTF8String:txtfield];
653 [frontendProxy presentDialogWithStyle:style message:message
654 informativeText:text buttonTitles:buttons
655 textFieldString:textFieldString];
657 // Wait until a reply is sent from MMVimController.
658 [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
659 beforeDate:[NSDate distantFuture]];
661 if (dialogReturn && [dialogReturn isKindOfClass:[NSArray class]]
662 && [dialogReturn count]) {
663 retval = [[dialogReturn objectAtIndex:0] intValue];
664 if (txtfield && [dialogReturn count] > 1) {
665 NSString *retString = [dialogReturn objectAtIndex:1];
666 char_u *ret = (char_u*)[retString UTF8String];
668 ret = CONVERT_FROM_UTF8(ret);
670 vim_strncpy((char_u*)txtfield, ret, IOSIZE - 1);
672 CONVERT_FROM_UTF8_FREE(ret);
677 [dialogReturn release]; dialogReturn = nil;
679 @catch (NSException *e) {
680 NSLog(@"Exception caught while showing alert dialog: \"%@\"", e);
686 - (void)addMenuWithTag:(int)tag parent:(int)parentTag name:(char *)name
689 //NSLog(@"addMenuWithTag:%d parent:%d name:%s atIndex:%d", tag, parentTag,
692 int namelen = name ? strlen(name) : 0;
693 NSMutableData *data = [NSMutableData data];
695 [data appendBytes:&tag length:sizeof(int)];
696 [data appendBytes:&parentTag length:sizeof(int)];
697 [data appendBytes:&namelen length:sizeof(int)];
698 if (namelen > 0) [data appendBytes:name length:namelen];
699 [data appendBytes:&index length:sizeof(int)];
701 [self queueMessage:AddMenuMsgID data:data];
704 - (void)addMenuItemWithTag:(int)tag parent:(int)parentTag name:(char *)name
705 tip:(char *)tip icon:(char *)icon
706 keyEquivalent:(int)key modifiers:(int)mods
707 action:(NSString *)action atIndex:(int)index
709 //NSLog(@"addMenuItemWithTag:%d parent:%d name:%s tip:%s atIndex:%d", tag,
710 // parentTag, name, tip, index);
712 int namelen = name ? strlen(name) : 0;
713 int tiplen = tip ? strlen(tip) : 0;
714 int iconlen = icon ? strlen(icon) : 0;
715 int eventFlags = vimModMaskToEventModifierFlags(mods);
716 int actionlen = [action lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
717 NSMutableData *data = [NSMutableData data];
719 key = specialKeyToNSKey(key);
721 [data appendBytes:&tag length:sizeof(int)];
722 [data appendBytes:&parentTag length:sizeof(int)];
723 [data appendBytes:&namelen length:sizeof(int)];
724 if (namelen > 0) [data appendBytes:name length:namelen];
725 [data appendBytes:&tiplen length:sizeof(int)];
726 if (tiplen > 0) [data appendBytes:tip length:tiplen];
727 [data appendBytes:&iconlen length:sizeof(int)];
728 if (iconlen > 0) [data appendBytes:icon length:iconlen];
729 [data appendBytes:&actionlen length:sizeof(int)];
730 if (actionlen > 0) [data appendBytes:[action UTF8String] length:actionlen];
731 [data appendBytes:&index length:sizeof(int)];
732 [data appendBytes:&key length:sizeof(int)];
733 [data appendBytes:&eventFlags length:sizeof(int)];
735 [self queueMessage:AddMenuItemMsgID data:data];
738 - (void)removeMenuItemWithTag:(int)tag
740 NSMutableData *data = [NSMutableData data];
741 [data appendBytes:&tag length:sizeof(int)];
743 [self queueMessage:RemoveMenuItemMsgID data:data];
746 - (void)enableMenuItemWithTag:(int)tag state:(int)enabled
748 NSMutableData *data = [NSMutableData data];
750 [data appendBytes:&tag length:sizeof(int)];
751 [data appendBytes:&enabled length:sizeof(int)];
753 [self queueMessage:EnableMenuItemMsgID data:data];
756 - (void)showPopupMenuWithName:(char *)name atMouseLocation:(BOOL)mouse
758 int len = strlen(name);
759 int row = -1, col = -1;
761 if (len <= 0) return;
763 if (!mouse && curwin) {
764 row = curwin->w_wrow;
765 col = curwin->w_wcol;
768 NSMutableData *data = [NSMutableData data];
770 [data appendBytes:&row length:sizeof(int)];
771 [data appendBytes:&col length:sizeof(int)];
772 [data appendBytes:&len length:sizeof(int)];
773 [data appendBytes:name length:len];
775 [self queueMessage:ShowPopupMenuMsgID data:data];
778 - (void)showToolbar:(int)enable flags:(int)flags
780 NSMutableData *data = [NSMutableData data];
782 [data appendBytes:&enable length:sizeof(int)];
783 [data appendBytes:&flags length:sizeof(int)];
785 [self queueMessage:ShowToolbarMsgID data:data];
788 - (void)createScrollbarWithIdentifier:(long)ident type:(int)type
790 NSMutableData *data = [NSMutableData data];
792 [data appendBytes:&ident length:sizeof(long)];
793 [data appendBytes:&type length:sizeof(int)];
795 [self queueMessage:CreateScrollbarMsgID data:data];
798 - (void)destroyScrollbarWithIdentifier:(long)ident
800 NSMutableData *data = [NSMutableData data];
801 [data appendBytes:&ident length:sizeof(long)];
803 [self queueMessage:DestroyScrollbarMsgID data:data];
806 - (void)showScrollbarWithIdentifier:(long)ident state:(int)visible
808 NSMutableData *data = [NSMutableData data];
810 [data appendBytes:&ident length:sizeof(long)];
811 [data appendBytes:&visible length:sizeof(int)];
813 [self queueMessage:ShowScrollbarMsgID data:data];
816 - (void)setScrollbarPosition:(int)pos length:(int)len identifier:(long)ident
818 NSMutableData *data = [NSMutableData data];
820 [data appendBytes:&ident length:sizeof(long)];
821 [data appendBytes:&pos length:sizeof(int)];
822 [data appendBytes:&len length:sizeof(int)];
824 [self queueMessage:SetScrollbarPositionMsgID data:data];
827 - (void)setScrollbarThumbValue:(long)val size:(long)size max:(long)max
828 identifier:(long)ident
830 float fval = max-size+1 > 0 ? (float)val/(max-size+1) : 0;
831 float prop = (float)size/(max+1);
832 if (fval < 0) fval = 0;
833 else if (fval > 1.0f) fval = 1.0f;
834 if (prop < 0) prop = 0;
835 else if (prop > 1.0f) prop = 1.0f;
837 NSMutableData *data = [NSMutableData data];
839 [data appendBytes:&ident length:sizeof(long)];
840 [data appendBytes:&fval length:sizeof(float)];
841 [data appendBytes:&prop length:sizeof(float)];
843 [self queueMessage:SetScrollbarThumbMsgID data:data];
846 - (void)setFont:(NSFont *)font
848 NSString *fontName = [font displayName];
849 float size = [font pointSize];
850 int len = [fontName lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
852 NSMutableData *data = [NSMutableData data];
854 [data appendBytes:&size length:sizeof(float)];
855 [data appendBytes:&len length:sizeof(int)];
856 [data appendBytes:[fontName UTF8String] length:len];
858 [self queueMessage:SetFontMsgID data:data];
862 - (void)setWideFont:(NSFont *)font
864 NSString *fontName = [font displayName];
865 float size = [font pointSize];
866 int len = [fontName lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
867 NSMutableData *data = [NSMutableData data];
869 [data appendBytes:&size length:sizeof(float)];
870 [data appendBytes:&len length:sizeof(int)];
872 [data appendBytes:[fontName UTF8String] length:len];
874 [self queueMessage:SetWideFontMsgID data:data];
877 - (void)executeActionWithName:(NSString *)name
879 int len = [name lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
882 NSMutableData *data = [NSMutableData data];
884 [data appendBytes:&len length:sizeof(int)];
885 [data appendBytes:[name UTF8String] length:len];
887 [self queueMessage:ExecuteActionMsgID data:data];
891 - (void)setMouseShape:(int)shape
893 NSMutableData *data = [NSMutableData data];
894 [data appendBytes:&shape length:sizeof(int)];
895 [self queueMessage:SetMouseShapeMsgID data:data];
898 - (void)setBlinkWait:(int)wait on:(int)on off:(int)off
900 // Vim specifies times in milliseconds, whereas Cocoa wants them in
902 blinkWaitInterval = .001f*wait;
903 blinkOnInterval = .001f*on;
904 blinkOffInterval = .001f*off;
910 [blinkTimer invalidate];
911 [blinkTimer release];
915 if (blinkWaitInterval > 0 && blinkOnInterval > 0 && blinkOffInterval > 0
917 blinkState = MMBlinkStateOn;
919 [[NSTimer scheduledTimerWithTimeInterval:blinkWaitInterval
921 selector:@selector(blinkTimerFired:)
922 userInfo:nil repeats:NO] retain];
923 gui_update_cursor(TRUE, FALSE);
924 [self flushQueue:YES];
930 if (MMBlinkStateOff == blinkState) {
931 gui_update_cursor(TRUE, FALSE);
932 [self flushQueue:YES];
935 blinkState = MMBlinkStateNone;
938 - (void)adjustLinespace:(int)linespace
940 NSMutableData *data = [NSMutableData data];
941 [data appendBytes:&linespace length:sizeof(int)];
942 [self queueMessage:AdjustLinespaceMsgID data:data];
947 [self queueMessage:ActivateMsgID data:nil];
950 - (void)setPreEditRow:(int)row column:(int)col
952 NSMutableData *data = [NSMutableData data];
953 [data appendBytes:&row length:sizeof(int)];
954 [data appendBytes:&col length:sizeof(int)];
955 [self queueMessage:SetPreEditPositionMsgID data:data];
958 - (int)lookupColorWithKey:(NSString *)key
960 if (!(key && [key length] > 0))
963 NSString *stripKey = [[[[key lowercaseString]
964 stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]
965 componentsSeparatedByString:@" "]
966 componentsJoinedByString:@""];
968 if (stripKey && [stripKey length] > 0) {
969 // First of all try to lookup key in the color dictionary; note that
970 // all keys in this dictionary are lowercase with no whitespace.
971 id obj = [colorDict objectForKey:stripKey];
972 if (obj) return [obj intValue];
974 // The key was not in the dictionary; is it perhaps of the form
976 if ([stripKey length] > 1 && [stripKey characterAtIndex:0] == '#') {
977 NSScanner *scanner = [NSScanner scannerWithString:stripKey];
978 [scanner setScanLocation:1];
980 if ([scanner scanHexInt:&hex]) {
985 // As a last resort, check if it is one of the system defined colors.
986 // The keys in this dictionary are also lowercase with no whitespace.
987 obj = [sysColorDict objectForKey:stripKey];
989 NSColor *col = [NSColor performSelector:NSSelectorFromString(obj)];
992 col = [col colorUsingColorSpaceName:NSCalibratedRGBColorSpace];
993 [col getRed:&r green:&g blue:&b alpha:&a];
994 return (((int)(r*255+.5f) & 0xff) << 16)
995 + (((int)(g*255+.5f) & 0xff) << 8)
996 + ((int)(b*255+.5f) & 0xff);
1001 NSLog(@"WARNING: No color with key %@ found.", stripKey);
1005 - (BOOL)hasSpecialKeyWithValue:(NSString *)value
1007 NSEnumerator *e = [[MMBackend specialKeys] objectEnumerator];
1010 while ((obj = [e nextObject])) {
1011 if ([value isEqual:obj])
1018 - (void)enterFullscreen
1020 [self queueMessage:EnterFullscreenMsgID data:nil];
1023 - (void)leaveFullscreen
1025 [self queueMessage:LeaveFullscreenMsgID data:nil];
1028 - (void)updateModifiedFlag
1030 // Notify MacVim if _any_ buffer has changed from unmodified to modified or
1032 int msgid = [self checkForModifiedBuffers]
1033 ? BuffersModifiedMsgID : BuffersNotModifiedMsgID;
1035 [self queueMessage:msgid data:nil];
1038 - (oneway void)processInput:(int)msgid data:(in bycopy NSData *)data
1040 // NOTE: This method might get called whenever the run loop is tended to.
1041 // Normal keyboard and mouse input is added to input buffers, so there is
1042 // no risk in handling these events directly (they return immediately, and
1043 // do not call any other Vim functions). However, other events such
1044 // as 'VimShouldCloseMsgID' may enter blocking loops that wait for key
1045 // events which would cause this method to be called recursively. This
1046 // in turn leads to various difficulties that we do not want to have to
1047 // deal with. To avoid recursive calls here we add all events except
1048 // keyboard and mouse events to an input queue which is processed whenever
1049 // gui_mch_update() is called (see processInputQueue).
1051 //NSLog(@"%s%s", _cmd, MessageStrings[msgid]);
1053 // Don't flush too soon after receiving input or update speed will suffer.
1054 [lastFlushDate release];
1055 lastFlushDate = [[NSDate date] retain];
1057 // Handle keyboard and mouse input now. All other events are queued.
1058 if (InsertTextMsgID == msgid) {
1059 [self handleInsertText:data];
1060 } else if (KeyDownMsgID == msgid || CmdKeyMsgID == msgid) {
1062 const void *bytes = [data bytes];
1063 int mods = *((int*)bytes); bytes += sizeof(int);
1064 int len = *((int*)bytes); bytes += sizeof(int);
1065 NSString *key = [[NSString alloc] initWithBytes:bytes length:len
1066 encoding:NSUTF8StringEncoding];
1067 mods = eventModifierFlagsToVimModMask(mods);
1069 [self handleKeyDown:key modifiers:mods];
1072 } else if (ScrollWheelMsgID == msgid) {
1074 const void *bytes = [data bytes];
1076 int row = *((int*)bytes); bytes += sizeof(int);
1077 int col = *((int*)bytes); bytes += sizeof(int);
1078 int flags = *((int*)bytes); bytes += sizeof(int);
1079 float dy = *((float*)bytes); bytes += sizeof(float);
1081 int button = MOUSE_5;
1082 if (dy > 0) button = MOUSE_4;
1084 flags = eventModifierFlagsToVimMouseModMask(flags);
1086 gui_send_mouse_event(button, col, row, NO, flags);
1087 } else if (MouseDownMsgID == msgid) {
1089 const void *bytes = [data bytes];
1091 int row = *((int*)bytes); bytes += sizeof(int);
1092 int col = *((int*)bytes); bytes += sizeof(int);
1093 int button = *((int*)bytes); bytes += sizeof(int);
1094 int flags = *((int*)bytes); bytes += sizeof(int);
1095 int count = *((int*)bytes); bytes += sizeof(int);
1097 button = eventButtonNumberToVimMouseButton(button);
1099 flags = eventModifierFlagsToVimMouseModMask(flags);
1100 gui_send_mouse_event(button, col, row, count>1, flags);
1102 } else if (MouseUpMsgID == msgid) {
1104 const void *bytes = [data bytes];
1106 int row = *((int*)bytes); bytes += sizeof(int);
1107 int col = *((int*)bytes); bytes += sizeof(int);
1108 int flags = *((int*)bytes); bytes += sizeof(int);
1110 flags = eventModifierFlagsToVimMouseModMask(flags);
1112 gui_send_mouse_event(MOUSE_RELEASE, col, row, NO, flags);
1113 } else if (MouseDraggedMsgID == msgid) {
1115 const void *bytes = [data bytes];
1117 int row = *((int*)bytes); bytes += sizeof(int);
1118 int col = *((int*)bytes); bytes += sizeof(int);
1119 int flags = *((int*)bytes); bytes += sizeof(int);
1121 flags = eventModifierFlagsToVimMouseModMask(flags);
1123 gui_send_mouse_event(MOUSE_DRAG, col, row, NO, flags);
1124 } else if (MouseMovedMsgID == msgid) {
1125 const void *bytes = [data bytes];
1126 int row = *((int*)bytes); bytes += sizeof(int);
1127 int col = *((int*)bytes); bytes += sizeof(int);
1129 gui_mouse_moved(col, row);
1130 } else if (AddInputMsgID == msgid) {
1131 NSString *string = [[NSString alloc] initWithData:data
1132 encoding:NSUTF8StringEncoding];
1134 [self addInput:string];
1138 // Not keyboard or mouse event, queue it and handle later.
1139 //NSLog(@"Add event %s to input event queue", MessageStrings[msgid]);
1140 [inputQueue addObject:[NSNumber numberWithInt:msgid]];
1141 [inputQueue addObject:(data ? (id)data : (id)[NSNull null])];
1144 // See waitForInput: for an explanation of this flag.
1145 inputReceived = YES;
1148 - (oneway void)processInputAndData:(in bycopy NSArray *)messages
1150 // TODO: Get rid of this method?
1151 //NSLog(@"%s%@", _cmd, messages);
1153 unsigned i, count = [messages count];
1154 for (i = 0; i < count; i += 2) {
1155 int msgid = [[messages objectAtIndex:i] intValue];
1156 id data = [messages objectAtIndex:i+1];
1157 if ([data isEqual:[NSNull null]])
1160 [self processInput:msgid data:data];
1164 - (NSString *)evaluateExpression:(in bycopy NSString *)expr
1166 NSString *eval = nil;
1167 char_u *s = (char_u*)[expr UTF8String];
1170 s = CONVERT_FROM_UTF8(s);
1173 char_u *res = eval_client_expr_to_string(s);
1176 CONVERT_FROM_UTF8_FREE(s);
1182 s = CONVERT_TO_UTF8(s);
1184 eval = [NSString stringWithUTF8String:(char*)s];
1186 CONVERT_TO_UTF8_FREE(s);
1194 - (BOOL)starRegisterToPasteboard:(byref NSPasteboard *)pboard
1196 if (VIsual_active && (State & NORMAL) && clip_star.available) {
1197 // If there is no pasteboard, return YES to indicate that there is text
1202 clip_copy_selection();
1204 // Get the text to put on the pasteboard.
1205 long_u llen = 0; char_u *str = 0;
1206 int type = clip_convert_selection(&str, &llen, &clip_star);
1210 // TODO: Avoid overflow.
1211 int len = (int)llen;
1213 if (output_conv.vc_type != CONV_NONE) {
1214 char_u *conv_str = string_convert(&output_conv, str, &len);
1222 NSString *string = [[NSString alloc]
1223 initWithBytes:str length:len encoding:NSUTF8StringEncoding];
1225 NSArray *types = [NSArray arrayWithObject:NSStringPboardType];
1226 [pboard declareTypes:types owner:nil];
1227 BOOL ok = [pboard setString:string forType:NSStringPboardType];
1238 - (oneway void)addReply:(in bycopy NSString *)reply
1239 server:(in byref id <MMVimServerProtocol>)server
1241 //NSLog(@"addReply:%@ server:%@", reply, (id)server);
1243 // Replies might come at any time and in any order so we keep them in an
1244 // array inside a dictionary with the send port used as key.
1246 NSConnection *conn = [(NSDistantObject*)server connectionForProxy];
1247 // HACK! Assume connection uses mach ports.
1248 int port = [(NSMachPort*)[conn sendPort] machPort];
1249 NSNumber *key = [NSNumber numberWithInt:port];
1251 NSMutableArray *replies = [serverReplyDict objectForKey:key];
1253 replies = [NSMutableArray array];
1254 [serverReplyDict setObject:replies forKey:key];
1257 [replies addObject:reply];
1260 - (void)addInput:(in bycopy NSString *)input
1261 client:(in byref id <MMVimClientProtocol>)client
1263 //NSLog(@"addInput:%@ client:%@", input, (id)client);
1265 [self addInput:input];
1266 [self addClient:(id)client];
1268 inputReceived = YES;
1271 - (NSString *)evaluateExpression:(in bycopy NSString *)expr
1272 client:(in byref id <MMVimClientProtocol>)client
1274 [self addClient:(id)client];
1275 return [self evaluateExpression:expr];
1278 - (void)registerServerWithName:(NSString *)name
1280 NSString *svrName = name;
1281 NSConnection *svrConn = [NSConnection defaultConnection];
1284 for (i = 0; i < MMServerMax; ++i) {
1285 NSString *connName = [self connectionNameFromServerName:svrName];
1287 if ([svrConn registerName:connName]) {
1288 //NSLog(@"Registered server with name: %@", svrName);
1290 // TODO: Set request/reply time-outs to something else?
1292 // Don't wait for requests (time-out means that the message is
1294 [svrConn setRequestTimeout:0];
1295 //[svrConn setReplyTimeout:MMReplyTimeout];
1296 [svrConn setRootObject:self];
1298 char_u *s = (char_u*)[svrName UTF8String];
1300 s = CONVERT_FROM_UTF8(s);
1302 // NOTE: 'serverName' is a global variable
1303 serverName = vim_strsave(s);
1305 CONVERT_FROM_UTF8_FREE(s);
1308 set_vim_var_string(VV_SEND_SERVER, serverName, -1);
1311 need_maketitle = TRUE;
1313 [self queueMessage:SetServerNameMsgID data:
1314 [svrName dataUsingEncoding:NSUTF8StringEncoding]];
1318 svrName = [NSString stringWithFormat:@"%@%d", name, i+1];
1322 - (BOOL)sendToServer:(NSString *)name string:(NSString *)string
1323 reply:(char_u **)reply port:(int *)port expression:(BOOL)expr
1326 // NOTE: If 'name' equals 'serverName' then the request is local (client
1327 // and server are the same). This case is not handled separately, so a
1328 // connection will be set up anyway (this simplifies the code).
1330 NSConnection *conn = [self connectionForServerName:name];
1333 char_u *s = (char_u*)[name UTF8String];
1335 s = CONVERT_FROM_UTF8(s);
1337 EMSG2(_(e_noserver), s);
1339 CONVERT_FROM_UTF8_FREE(s);
1346 // HACK! Assume connection uses mach ports.
1347 *port = [(NSMachPort*)[conn sendPort] machPort];
1350 id proxy = [conn rootProxy];
1351 [proxy setProtocolForProxy:@protocol(MMVimServerProtocol)];
1355 NSString *eval = [proxy evaluateExpression:string client:self];
1358 char_u *r = (char_u*)[eval UTF8String];
1360 r = CONVERT_FROM_UTF8(r);
1362 *reply = vim_strsave(r);
1364 CONVERT_FROM_UTF8_FREE(r);
1367 *reply = vim_strsave((char_u*)_(e_invexprmsg));
1374 [proxy addInput:string client:self];
1377 @catch (NSException *e) {
1378 NSLog(@"WARNING: Caught exception in %s: \"%@\"", _cmd, e);
1385 - (NSArray *)serverList
1387 NSArray *list = nil;
1389 if ([self connection]) {
1390 id proxy = [connection rootProxy];
1391 [proxy setProtocolForProxy:@protocol(MMAppProtocol)];
1394 list = [proxy serverList];
1396 @catch (NSException *e) {
1397 NSLog(@"Exception caught when listing servers: \"%@\"", e);
1400 EMSG(_("E???: No connection to MacVim, server listing not possible."));
1406 - (NSString *)peekForReplyOnPort:(int)port
1408 //NSLog(@"%s%d", _cmd, port);
1410 NSNumber *key = [NSNumber numberWithInt:port];
1411 NSMutableArray *replies = [serverReplyDict objectForKey:key];
1412 if (replies && [replies count]) {
1413 //NSLog(@" %d replies, topmost is: %@", [replies count],
1414 // [replies objectAtIndex:0]);
1415 return [replies objectAtIndex:0];
1418 //NSLog(@" No replies");
1422 - (NSString *)waitForReplyOnPort:(int)port
1424 //NSLog(@"%s%d", _cmd, port);
1426 NSConnection *conn = [self connectionForServerPort:port];
1430 NSNumber *key = [NSNumber numberWithInt:port];
1431 NSMutableArray *replies = nil;
1432 NSString *reply = nil;
1434 // Wait for reply as long as the connection to the server is valid (unless
1435 // user interrupts wait with Ctrl-C).
1436 while (!got_int && [conn isValid] &&
1437 !(replies = [serverReplyDict objectForKey:key])) {
1438 [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
1439 beforeDate:[NSDate distantFuture]];
1443 if ([replies count] > 0) {
1444 reply = [[replies objectAtIndex:0] retain];
1445 //NSLog(@" Got reply: %@", reply);
1446 [replies removeObjectAtIndex:0];
1447 [reply autorelease];
1450 if ([replies count] == 0)
1451 [serverReplyDict removeObjectForKey:key];
1457 - (BOOL)sendReply:(NSString *)reply toPort:(int)port
1459 id client = [clientProxyDict objectForKey:[NSNumber numberWithInt:port]];
1462 //NSLog(@"sendReply:%@ toPort:%d", reply, port);
1463 [client addReply:reply server:self];
1466 @catch (NSException *e) {
1467 NSLog(@"WARNING: Exception caught in %s: \"%@\"", _cmd, e);
1470 EMSG2(_("E???: server2client failed; no client with id 0x%x"), port);
1480 @implementation MMBackend (Private)
1482 - (void)processInputQueue
1484 // NOTE: One of the input events may cause this method to be called
1485 // recursively, so copy the input queue to a local variable and clear it
1486 // before starting to process input events (otherwise we could get stuck in
1487 // an endless loop).
1488 NSArray *q = [inputQueue copy];
1489 unsigned i, count = [q count];
1491 [inputQueue removeAllObjects];
1493 for (i = 0; i < count-1; i += 2) {
1494 int msgid = [[q objectAtIndex:i] intValue];
1495 id data = [q objectAtIndex:i+1];
1496 if ([data isEqual:[NSNull null]])
1499 //NSLog(@"(%d) %s:%s", i, _cmd, MessageStrings[msgid]);
1500 [self handleInputEvent:msgid data:data];
1504 //NSLog(@"Clear input event queue");
1507 - (void)handleInputEvent:(int)msgid data:(NSData *)data
1509 // NOTE: Be careful with what you do in this method. Ideally, a message
1510 // should be handled by adding something to the input buffer and returning
1511 // immediately. If you call a Vim function then it should not enter a loop
1512 // waiting for key presses or in any other way block the process. The
1513 // reason for this being that only one message can be processed at a time,
1514 // so if another message is received while processing, then the new message
1515 // is dropped. See also the comment in processInput:data:.
1517 //NSLog(@"%s%s", _cmd, MessageStrings[msgid]);
1519 if (SelectTabMsgID == msgid) {
1521 const void *bytes = [data bytes];
1522 int idx = *((int*)bytes) + 1;
1523 //NSLog(@"Selecting tab %d", idx);
1524 send_tabline_event(idx);
1525 } else if (CloseTabMsgID == msgid) {
1527 const void *bytes = [data bytes];
1528 int idx = *((int*)bytes) + 1;
1529 //NSLog(@"Closing tab %d", idx);
1530 send_tabline_menu_event(idx, TABLINE_MENU_CLOSE);
1531 } else if (AddNewTabMsgID == msgid) {
1532 //NSLog(@"Adding new tab");
1533 send_tabline_menu_event(0, TABLINE_MENU_NEW);
1534 } else if (DraggedTabMsgID == msgid) {
1536 const void *bytes = [data bytes];
1537 // NOTE! The destination index is 0 based, so do not add 1 to make it 1
1539 int idx = *((int*)bytes);
1542 } else if (SetTextDimensionsMsgID == msgid) {
1544 const void *bytes = [data bytes];
1545 int rows = *((int*)bytes); bytes += sizeof(int);
1546 int cols = *((int*)bytes); bytes += sizeof(int);
1548 // NOTE! Vim doesn't call gui_mch_set_shellsize() after
1549 // gui_resize_shell(), so we have to manually set the rows and columns
1550 // here. (MacVim doesn't change the rows and columns to avoid
1551 // inconsistent states between Vim and MacVim.)
1552 [self setRows:rows columns:cols];
1554 //NSLog(@"[VimTask] Resizing shell to %dx%d.", cols, rows);
1555 gui_resize_shell(cols, rows);
1556 } else if (ExecuteMenuMsgID == msgid) {
1558 const void *bytes = [data bytes];
1559 int tag = *((int*)bytes); bytes += sizeof(int);
1561 vimmenu_T *menu = (vimmenu_T*)tag;
1562 // TODO! Make sure 'menu' is a valid menu pointer!
1566 } else if (ToggleToolbarMsgID == msgid) {
1567 [self handleToggleToolbar];
1568 } else if (ScrollbarEventMsgID == msgid) {
1569 [self handleScrollbarEvent:data];
1570 } else if (SetFontMsgID == msgid) {
1571 [self handleSetFont:data];
1572 } else if (VimShouldCloseMsgID == msgid) {
1574 } else if (DropFilesMsgID == msgid) {
1575 [self handleDropFiles:data];
1576 } else if (DropStringMsgID == msgid) {
1577 [self handleDropString:data];
1578 } else if (GotFocusMsgID == msgid) {
1580 [self focusChange:YES];
1581 } else if (LostFocusMsgID == msgid) {
1583 [self focusChange:NO];
1584 } else if (SetMouseShapeMsgID == msgid) {
1585 const void *bytes = [data bytes];
1586 int shape = *((int*)bytes); bytes += sizeof(int);
1587 update_mouseshape(shape);
1589 NSLog(@"WARNING: Unknown message received (msgid=%d)", msgid);
1593 + (NSDictionary *)specialKeys
1595 static NSDictionary *specialKeys = nil;
1598 NSBundle *mainBundle = [NSBundle mainBundle];
1599 NSString *path = [mainBundle pathForResource:@"SpecialKeys"
1601 specialKeys = [[NSDictionary alloc] initWithContentsOfFile:path];
1607 - (void)handleInsertText:(NSData *)data
1611 NSString *key = [[NSString alloc] initWithData:data
1612 encoding:NSUTF8StringEncoding];
1613 char_u *str = (char_u*)[key UTF8String];
1614 int i, len = [key lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
1617 char_u *conv_str = NULL;
1618 if (input_conv.vc_type != CONV_NONE) {
1619 conv_str = string_convert(&input_conv, str, &len);
1625 for (i = 0; i < len; ++i) {
1626 add_to_input_buf(str+i, 1);
1627 if (CSI == str[i]) {
1628 // NOTE: If the converted string contains the byte CSI, then it
1629 // must be followed by the bytes KS_EXTRA, KE_CSI or things
1631 static char_u extra[2] = { KS_EXTRA, KE_CSI };
1632 add_to_input_buf(extra, 2);
1643 - (void)handleKeyDown:(NSString *)key modifiers:(int)mods
1647 char_u *chars = (char_u*)[key UTF8String];
1649 char_u *conv_str = NULL;
1651 int length = [key lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
1653 // Special keys (arrow keys, function keys, etc.) are stored in a plist so
1654 // that new keys can easily be added.
1655 NSString *specialString = [[MMBackend specialKeys]
1657 if (specialString && [specialString length] > 1) {
1658 //NSLog(@"special key: %@", specialString);
1659 int ikey = TO_SPECIAL([specialString characterAtIndex:0],
1660 [specialString characterAtIndex:1]);
1662 ikey = simplify_key(ikey, &mods);
1667 special[1] = K_SECOND(ikey);
1668 special[2] = K_THIRD(ikey);
1672 } else if (1 == length && TAB == chars[0]) {
1673 // Tab is a trouble child:
1674 // - <Tab> is added to the input buffer as is
1675 // - <S-Tab> is translated to, {CSI,'k','B'} (i.e. 'Back-tab')
1676 // - <M-Tab> should be 0x80|TAB but this is not valid utf-8 so it needs
1677 // to be converted to utf-8
1678 // - <S-M-Tab> is translated to <S-Tab> with ALT modifier
1679 // - <C-Tab> is reserved by Mac OS X
1680 // - <D-Tab> is reserved by Mac OS X
1685 if (mods & MOD_MASK_SHIFT) {
1686 mods &= ~MOD_MASK_SHIFT;
1688 special[1] = K_SECOND(K_S_TAB);
1689 special[2] = K_THIRD(K_S_TAB);
1691 } else if (mods & MOD_MASK_ALT) {
1692 int mtab = 0x80 | TAB;
1696 special[0] = (mtab >> 6) + 0xc0;
1697 special[1] = mtab & 0xbf;
1705 mods &= ~MOD_MASK_ALT;
1707 } else if (length > 0) {
1708 unichar c = [key characterAtIndex:0];
1710 //NSLog(@"non-special: %@ (hex=%x, mods=%d)", key,
1711 // [key characterAtIndex:0], mods);
1713 if (length == 1 && ((c == Ctrl_C && ctrl_c_interrupts)
1714 || (c == intr_char && intr_char != Ctrl_C))) {
1719 // HACK! In most circumstances the Ctrl and Shift modifiers should be
1720 // cleared since they are already added to the key by the AppKit.
1721 // Unfortunately, the only way to deal with when to clear the modifiers
1722 // or not seems to be to have hard-wired rules like this.
1723 if ( !((' ' == c) || (0xa0 == c) || (mods & MOD_MASK_CMD)
1724 || 0x9 == c || 0xd == c) ) {
1725 mods &= ~MOD_MASK_SHIFT;
1726 mods &= ~MOD_MASK_CTRL;
1727 //NSLog(@"clear shift ctrl");
1730 // HACK! All Option+key presses go via 'insert text' messages, except
1731 // for <M-Space>. If the Alt flag is not cleared for <M-Space> it does
1732 // not work to map to it.
1733 if (0xa0 == c && !(mods & MOD_MASK_CMD)) {
1734 //NSLog(@"clear alt");
1735 mods &= ~MOD_MASK_ALT;
1739 if (input_conv.vc_type != CONV_NONE) {
1740 conv_str = string_convert(&input_conv, chars, &length);
1747 if (chars && length > 0) {
1749 //NSLog(@"adding mods: %d", mods);
1751 modChars[1] = KS_MODIFIER;
1753 add_to_input_buf(modChars, 3);
1756 //NSLog(@"add to input buf: 0x%x", chars[0]);
1757 // TODO: Check for CSI bytes?
1758 add_to_input_buf(chars, length);
1767 - (void)queueMessage:(int)msgid data:(NSData *)data
1769 [outputQueue addObject:[NSData dataWithBytes:&msgid length:sizeof(int)]];
1771 [outputQueue addObject:data];
1773 [outputQueue addObject:[NSData data]];
1776 - (void)connectionDidDie:(NSNotification *)notification
1778 // If the main connection to MacVim is lost this means that MacVim was
1779 // either quit (by the user chosing Quit on the MacVim menu), or it has
1780 // crashed. In either case our only option is to quit now.
1781 // TODO: Write backup file?
1783 //NSLog(@"A Vim process lost its connection to MacVim; quitting.");
1787 - (void)blinkTimerFired:(NSTimer *)timer
1789 NSTimeInterval timeInterval = 0;
1791 [blinkTimer release];
1794 if (MMBlinkStateOn == blinkState) {
1795 gui_undraw_cursor();
1796 blinkState = MMBlinkStateOff;
1797 timeInterval = blinkOffInterval;
1798 } else if (MMBlinkStateOff == blinkState) {
1799 gui_update_cursor(TRUE, FALSE);
1800 blinkState = MMBlinkStateOn;
1801 timeInterval = blinkOnInterval;
1804 if (timeInterval > 0) {
1806 [[NSTimer scheduledTimerWithTimeInterval:timeInterval target:self
1807 selector:@selector(blinkTimerFired:)
1808 userInfo:nil repeats:NO] retain];
1809 [self flushQueue:YES];
1813 - (void)focusChange:(BOOL)on
1815 gui_focus_change(on);
1818 - (void)handleToggleToolbar
1820 // If 'go' contains 'T', then remove it, else add it.
1822 char_u go[sizeof(GO_ALL)+2];
1827 p = vim_strchr(go, GO_TOOLBAR);
1831 char_u *end = go + len;
1837 go[len] = GO_TOOLBAR;
1841 set_option_value((char_u*)"guioptions", 0, go, 0);
1843 // Force screen redraw (does it have to be this complicated?).
1844 redraw_all_later(CLEAR);
1845 update_screen(NOT_VALID);
1848 gui_update_cursor(FALSE, FALSE);
1852 - (void)handleScrollbarEvent:(NSData *)data
1856 const void *bytes = [data bytes];
1857 long ident = *((long*)bytes); bytes += sizeof(long);
1858 int hitPart = *((int*)bytes); bytes += sizeof(int);
1859 float fval = *((float*)bytes); bytes += sizeof(float);
1860 scrollbar_T *sb = gui_find_scrollbar(ident);
1863 scrollbar_T *sb_info = sb->wp ? &sb->wp->w_scrollbars[0] : sb;
1864 long value = sb_info->value;
1865 long size = sb_info->size;
1866 long max = sb_info->max;
1867 BOOL isStillDragging = NO;
1868 BOOL updateKnob = YES;
1871 case NSScrollerDecrementPage:
1872 value -= (size > 2 ? size - 2 : 1);
1874 case NSScrollerIncrementPage:
1875 value += (size > 2 ? size - 2 : 1);
1877 case NSScrollerDecrementLine:
1880 case NSScrollerIncrementLine:
1883 case NSScrollerKnob:
1884 isStillDragging = YES;
1886 case NSScrollerKnobSlot:
1887 value = (long)(fval * (max - size + 1));
1894 //NSLog(@"value %d -> %d", sb_info->value, value);
1895 gui_drag_scrollbar(sb, value, isStillDragging);
1898 // Dragging the knob or option+clicking automatically updates
1899 // the knob position (on the actual NSScroller), so we only
1900 // need to set the knob position in the other cases.
1902 // Update both the left&right vertical scrollbars.
1903 long identLeft = sb->wp->w_scrollbars[SBAR_LEFT].ident;
1904 long identRight = sb->wp->w_scrollbars[SBAR_RIGHT].ident;
1905 [self setScrollbarThumbValue:value size:size max:max
1906 identifier:identLeft];
1907 [self setScrollbarThumbValue:value size:size max:max
1908 identifier:identRight];
1910 // Update the horizontal scrollbar.
1911 [self setScrollbarThumbValue:value size:size max:max
1918 - (void)handleSetFont:(NSData *)data
1922 const void *bytes = [data bytes];
1923 float pointSize = *((float*)bytes); bytes += sizeof(float);
1924 //unsigned len = *((unsigned*)bytes); bytes += sizeof(unsigned);
1925 bytes += sizeof(unsigned); // len not used
1927 NSMutableString *name = [NSMutableString stringWithUTF8String:bytes];
1928 [name appendString:[NSString stringWithFormat:@":h%.2f", pointSize]];
1929 char_u *s = (char_u*)[name UTF8String];
1932 s = CONVERT_FROM_UTF8(s);
1935 set_option_value((char_u*)"guifont", 0, s, 0);
1938 CONVERT_FROM_UTF8_FREE(s);
1941 // Force screen redraw (does it have to be this complicated?).
1942 redraw_all_later(CLEAR);
1943 update_screen(NOT_VALID);
1946 gui_update_cursor(FALSE, FALSE);
1950 - (void)handleDropFiles:(NSData *)data
1955 const void *bytes = [data bytes];
1956 const void *end = [data bytes] + [data length];
1957 int n = *((int*)bytes); bytes += sizeof(int);
1959 if (State & CMDLINE) {
1960 // HACK! If Vim is in command line mode then the files names
1961 // should be added to the command line, instead of opening the
1962 // files in tabs. This is taken care of by gui_handle_drop().
1963 char_u **fnames = (char_u **)alloc(n * sizeof(char_u *));
1966 while (bytes < end && i < n) {
1967 int len = *((int*)bytes); bytes += sizeof(int);
1968 char_u *s = (char_u*)bytes;
1970 s = CONVERT_FROM_UTF8(s);
1972 fnames[i++] = vim_strsave(s);
1974 CONVERT_FROM_UTF8_FREE(s);
1979 // NOTE! This function will free 'fnames'.
1980 // HACK! It is assumed that the 'x' and 'y' arguments are
1981 // unused when in command line mode.
1982 gui_handle_drop(0, 0, 0, fnames, i < n ? i : n);
1985 // HACK! I'm not sure how to get Vim to open a list of files in
1986 // tabs, so instead I create a ':tab drop' command with all the
1987 // files to open and execute it.
1988 NSMutableString *cmd = [NSMutableString stringWithString:@":tab drop"];
1991 for (i = 0; i < n && bytes < end; ++i) {
1992 int len = *((int*)bytes); bytes += sizeof(int);
1993 NSString *file = [NSString stringWithUTF8String:bytes];
1994 file = [file stringByEscapingSpecialFilenameCharacters];
1997 [cmd appendString:@" "];
1998 [cmd appendString:file];
2001 // By going to the last tabpage we ensure that the new tabs will
2002 // appear last (if this call is left out, the taborder becomes
2006 char_u *s = (char_u*)[cmd UTF8String];
2008 s = CONVERT_FROM_UTF8(s);
2012 CONVERT_FROM_UTF8_FREE(s);
2015 // Force screen redraw (does it have to be this complicated?).
2016 // (This code was taken from the end of gui_handle_drop().)
2017 update_screen(NOT_VALID);
2020 gui_update_cursor(FALSE, FALSE);
2027 - (void)handleDropString:(NSData *)data
2032 char_u dropkey[3] = { CSI, KS_EXTRA, (char_u)KE_DROP };
2033 const void *bytes = [data bytes];
2034 int len = *((int*)bytes); bytes += sizeof(int);
2035 NSMutableString *string = [NSMutableString stringWithUTF8String:bytes];
2037 // Replace unrecognized end-of-line sequences with \x0a (line feed).
2038 NSRange range = { 0, [string length] };
2039 unsigned n = [string replaceOccurrencesOfString:@"\x0d\x0a"
2040 withString:@"\x0a" options:0
2043 n = [string replaceOccurrencesOfString:@"\x0d" withString:@"\x0a"
2044 options:0 range:range];
2047 len = [string lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
2048 char_u *s = (char_u*)[string UTF8String];
2050 if (input_conv.vc_type != CONV_NONE)
2051 s = string_convert(&input_conv, s, &len);
2053 dnd_yank_drag_data(s, len);
2055 if (input_conv.vc_type != CONV_NONE)
2058 add_to_input_buf(dropkey, sizeof(dropkey));
2062 - (BOOL)checkForModifiedBuffers
2065 for (buf = firstbuf; buf != NULL; buf = buf->b_next) {
2066 if (bufIsChanged(buf)) {
2074 - (void)addInput:(NSString *)input
2076 char_u *s = (char_u*)[input UTF8String];
2079 s = CONVERT_FROM_UTF8(s);
2082 server_to_input_buf(s);
2085 CONVERT_FROM_UTF8_FREE(s);
2089 @end // MMBackend (Private)
2094 @implementation MMBackend (ClientServer)
2096 - (NSString *)connectionNameFromServerName:(NSString *)name
2098 NSString *bundleIdentifier = [[NSBundle mainBundle] bundleIdentifier];
2100 return [[NSString stringWithFormat:@"%@.%@", bundleIdentifier, name]
2104 - (NSConnection *)connectionForServerName:(NSString *)name
2106 // TODO: Try 'name%d' if 'name' fails.
2107 NSString *connName = [self connectionNameFromServerName:name];
2108 NSConnection *svrConn = [connectionNameDict objectForKey:connName];
2111 svrConn = [NSConnection connectionWithRegisteredName:connName
2113 // Try alternate server...
2114 if (!svrConn && alternateServerName) {
2115 //NSLog(@" trying to connect to alternate server: %@",
2116 // alternateServerName);
2117 connName = [self connectionNameFromServerName:alternateServerName];
2118 svrConn = [NSConnection connectionWithRegisteredName:connName
2122 // Try looking for alternate servers...
2124 //NSLog(@" looking for alternate servers...");
2125 NSString *alt = [self alternateServerNameForName:name];
2126 if (alt != alternateServerName) {
2127 //NSLog(@" found alternate server: %@", string);
2128 [alternateServerName release];
2129 alternateServerName = [alt copy];
2133 // Try alternate server again...
2134 if (!svrConn && alternateServerName) {
2135 //NSLog(@" trying to connect to alternate server: %@",
2136 // alternateServerName);
2137 connName = [self connectionNameFromServerName:alternateServerName];
2138 svrConn = [NSConnection connectionWithRegisteredName:connName
2143 [connectionNameDict setObject:svrConn forKey:connName];
2145 //NSLog(@"Adding %@ as connection observer for %@", self, svrConn);
2146 [[NSNotificationCenter defaultCenter] addObserver:self
2147 selector:@selector(serverConnectionDidDie:)
2148 name:NSConnectionDidDieNotification object:svrConn];
2155 - (NSConnection *)connectionForServerPort:(int)port
2158 NSEnumerator *e = [connectionNameDict objectEnumerator];
2160 while ((conn = [e nextObject])) {
2161 // HACK! Assume connection uses mach ports.
2162 if (port == [(NSMachPort*)[conn sendPort] machPort])
2169 - (void)serverConnectionDidDie:(NSNotification *)notification
2171 //NSLog(@"%s%@", _cmd, notification);
2173 NSConnection *svrConn = [notification object];
2175 //NSLog(@"Removing %@ as connection observer from %@", self, svrConn);
2176 [[NSNotificationCenter defaultCenter]
2178 name:NSConnectionDidDieNotification
2181 [connectionNameDict removeObjectsForKeys:
2182 [connectionNameDict allKeysForObject:svrConn]];
2184 // HACK! Assume connection uses mach ports.
2185 int port = [(NSMachPort*)[svrConn sendPort] machPort];
2186 NSNumber *key = [NSNumber numberWithInt:port];
2188 [clientProxyDict removeObjectForKey:key];
2189 [serverReplyDict removeObjectForKey:key];
2192 - (void)addClient:(NSDistantObject *)client
2194 NSConnection *conn = [client connectionForProxy];
2195 // HACK! Assume connection uses mach ports.
2196 int port = [(NSMachPort*)[conn sendPort] machPort];
2197 NSNumber *key = [NSNumber numberWithInt:port];
2199 if (![clientProxyDict objectForKey:key]) {
2200 [client setProtocolForProxy:@protocol(MMVimClientProtocol)];
2201 [clientProxyDict setObject:client forKey:key];
2204 // NOTE: 'clientWindow' is a global variable which is used by <client>
2205 clientWindow = port;
2208 - (NSString *)alternateServerNameForName:(NSString *)name
2210 if (!(name && [name length] > 0))
2213 // Only look for alternates if 'name' doesn't end in a digit.
2214 unichar lastChar = [name characterAtIndex:[name length]-1];
2215 if (lastChar >= '0' && lastChar <= '9')
2218 // Look for alternates among all current servers.
2219 NSArray *list = [self serverList];
2220 if (!(list && [list count] > 0))
2223 // Filter out servers starting with 'name' and ending with a number. The
2224 // (?i) pattern ensures that the match is case insensitive.
2225 NSString *pat = [NSString stringWithFormat:@"(?i)%@[0-9]+\\z", name];
2226 NSPredicate *pred = [NSPredicate predicateWithFormat:
2227 @"SELF MATCHES %@", pat];
2228 list = [list filteredArrayUsingPredicate:pred];
2229 if ([list count] > 0) {
2230 list = [list sortedArrayUsingSelector:@selector(serverNameCompare:)];
2231 return [list objectAtIndex:0];
2237 @end // MMBackend (ClientServer)
2242 @implementation NSString (MMServerNameCompare)
2243 - (NSComparisonResult)serverNameCompare:(NSString *)string
2245 return [self compare:string
2246 options:NSCaseInsensitiveSearch|NSNumericSearch];
2253 static int eventModifierFlagsToVimModMask(int modifierFlags)
2257 if (modifierFlags & NSShiftKeyMask)
2258 modMask |= MOD_MASK_SHIFT;
2259 if (modifierFlags & NSControlKeyMask)
2260 modMask |= MOD_MASK_CTRL;
2261 if (modifierFlags & NSAlternateKeyMask)
2262 modMask |= MOD_MASK_ALT;
2263 if (modifierFlags & NSCommandKeyMask)
2264 modMask |= MOD_MASK_CMD;
2269 static int vimModMaskToEventModifierFlags(int mods)
2273 if (mods & MOD_MASK_SHIFT)
2274 flags |= NSShiftKeyMask;
2275 if (mods & MOD_MASK_CTRL)
2276 flags |= NSControlKeyMask;
2277 if (mods & MOD_MASK_ALT)
2278 flags |= NSAlternateKeyMask;
2279 if (mods & MOD_MASK_CMD)
2280 flags |= NSCommandKeyMask;
2285 static int eventModifierFlagsToVimMouseModMask(int modifierFlags)
2289 if (modifierFlags & NSShiftKeyMask)
2290 modMask |= MOUSE_SHIFT;
2291 if (modifierFlags & NSControlKeyMask)
2292 modMask |= MOUSE_CTRL;
2293 if (modifierFlags & NSAlternateKeyMask)
2294 modMask |= MOUSE_ALT;
2299 static int eventButtonNumberToVimMouseButton(int buttonNumber)
2301 static int mouseButton[] = { MOUSE_LEFT, MOUSE_RIGHT, MOUSE_MIDDLE };
2303 return (buttonNumber >= 0 && buttonNumber < 3)
2304 ? mouseButton[buttonNumber] : -1;
2307 static int specialKeyToNSKey(int key)
2309 if (!IS_SPECIAL(key))
2316 { K_UP, NSUpArrowFunctionKey },
2317 { K_DOWN, NSDownArrowFunctionKey },
2318 { K_LEFT, NSLeftArrowFunctionKey },
2319 { K_RIGHT, NSRightArrowFunctionKey },
2320 { K_F1, NSF1FunctionKey },
2321 { K_F2, NSF2FunctionKey },
2322 { K_F3, NSF3FunctionKey },
2323 { K_F4, NSF4FunctionKey },
2324 { K_F5, NSF5FunctionKey },
2325 { K_F6, NSF6FunctionKey },
2326 { K_F7, NSF7FunctionKey },
2327 { K_F8, NSF8FunctionKey },
2328 { K_F9, NSF9FunctionKey },
2329 { K_F10, NSF10FunctionKey },
2330 { K_F11, NSF11FunctionKey },
2331 { K_F12, NSF12FunctionKey },
2332 { K_F13, NSF13FunctionKey },
2333 { K_F14, NSF14FunctionKey },
2334 { K_F15, NSF15FunctionKey },
2335 { K_F16, NSF16FunctionKey },
2336 { K_F17, NSF17FunctionKey },
2337 { K_F18, NSF18FunctionKey },
2338 { K_F19, NSF19FunctionKey },
2339 { K_F20, NSF20FunctionKey },
2340 { K_F21, NSF21FunctionKey },
2341 { K_F22, NSF22FunctionKey },
2342 { K_F23, NSF23FunctionKey },
2343 { K_F24, NSF24FunctionKey },
2344 { K_F25, NSF25FunctionKey },
2345 { K_F26, NSF26FunctionKey },
2346 { K_F27, NSF27FunctionKey },
2347 { K_F28, NSF28FunctionKey },
2348 { K_F29, NSF29FunctionKey },
2349 { K_F30, NSF30FunctionKey },
2350 { K_F31, NSF31FunctionKey },
2351 { K_F32, NSF32FunctionKey },
2352 { K_F33, NSF33FunctionKey },
2353 { K_F34, NSF34FunctionKey },
2354 { K_F35, NSF35FunctionKey },
2355 { K_DEL, NSBackspaceCharacter },
2356 { K_BS, NSDeleteCharacter },
2357 { K_HOME, NSHomeFunctionKey },
2358 { K_END, NSEndFunctionKey },
2359 { K_PAGEUP, NSPageUpFunctionKey },
2360 { K_PAGEDOWN, NSPageDownFunctionKey }
2364 for (i = 0; i < sizeof(sp2ns)/sizeof(sp2ns[0]); ++i) {
2365 if (sp2ns[i].special == key)
2366 return sp2ns[i].nskey;