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 eventModifierFlagsToVimMouseModMask(int modifierFlags);
55 static int eventButtonNumberToVimMouseButton(int buttonNumber);
58 vimmenu_T *menu_for_descriptor(NSArray *desc);
67 static NSString *MMSymlinkWarningString =
68 @"\n\n\tMost likely this is because you have symlinked directly to\n"
69 "\tthe Vim binary, which Cocoa does not allow. Please use an\n"
70 "\talias or the mvim shell script instead. If you have not used\n"
71 "\ta symlink, then your MacVim.app bundle is incomplete.\n\n";
75 @interface NSString (MMServerNameCompare)
76 - (NSComparisonResult)serverNameCompare:(NSString *)string;
82 @interface MMBackend (Private)
83 - (void)waitForDialogReturn;
84 - (void)queueVimStateMessage;
85 - (void)processInputQueue;
86 - (void)handleInputEvent:(int)msgid data:(NSData *)data;
87 + (NSDictionary *)specialKeys;
88 - (void)handleInsertText:(NSData *)data;
89 - (void)handleKeyDown:(NSString *)key modifiers:(int)mods;
90 - (void)queueMessage:(int)msgid data:(NSData *)data;
91 - (void)connectionDidDie:(NSNotification *)notification;
92 - (void)blinkTimerFired:(NSTimer *)timer;
93 - (void)focusChange:(BOOL)on;
94 - (void)handleToggleToolbar;
95 - (void)handleScrollbarEvent:(NSData *)data;
96 - (void)handleSetFont:(NSData *)data;
97 - (void)handleDropFiles:(NSData *)data;
98 - (void)handleDropString:(NSData *)data;
99 - (void)handleOdbEdit:(NSData *)data;
100 - (void)handleXcodeMod:(NSData *)data;
101 - (BOOL)checkForModifiedBuffers;
102 - (void)addInput:(NSString *)input;
107 @interface MMBackend (ClientServer)
108 - (NSString *)connectionNameFromServerName:(NSString *)name;
109 - (NSConnection *)connectionForServerName:(NSString *)name;
110 - (NSConnection *)connectionForServerPort:(int)port;
111 - (void)serverConnectionDidDie:(NSNotification *)notification;
112 - (void)addClient:(NSDistantObject *)client;
113 - (NSString *)alternateServerNameForName:(NSString *)name;
118 @implementation MMBackend
120 + (MMBackend *)sharedInstance
122 static MMBackend *singleton = nil;
123 return singleton ? singleton : (singleton = [MMBackend new]);
129 if (!self) return nil;
131 fontContainerRef = loadFonts();
133 outputQueue = [[NSMutableArray alloc] init];
134 inputQueue = [[NSMutableArray alloc] init];
135 drawData = [[NSMutableData alloc] initWithCapacity:1024];
136 connectionNameDict = [[NSMutableDictionary alloc] init];
137 clientProxyDict = [[NSMutableDictionary alloc] init];
138 serverReplyDict = [[NSMutableDictionary alloc] init];
140 NSBundle *mainBundle = [NSBundle mainBundle];
141 NSString *path = [mainBundle pathForResource:@"Colors" ofType:@"plist"];
143 colorDict = [[NSDictionary dictionaryWithContentsOfFile:path] retain];
145 path = [mainBundle pathForResource:@"SystemColors" ofType:@"plist"];
147 sysColorDict = [[NSDictionary dictionaryWithContentsOfFile:path]
150 path = [mainBundle pathForResource:@"Actions" ofType:@"plist"];
152 actionDict = [[NSDictionary dictionaryWithContentsOfFile:path] retain];
154 if (!(colorDict && sysColorDict && actionDict))
155 NSLog(@"ERROR: Failed to load dictionaries.%@",
156 MMSymlinkWarningString);
163 //NSLog(@"%@ %s", [self className], _cmd);
164 [[NSNotificationCenter defaultCenter] removeObserver:self];
166 [oldWideFont release]; oldWideFont = nil;
167 [blinkTimer release]; blinkTimer = nil;
168 [alternateServerName release]; alternateServerName = nil;
169 [serverReplyDict release]; serverReplyDict = nil;
170 [clientProxyDict release]; clientProxyDict = nil;
171 [connectionNameDict release]; connectionNameDict = nil;
172 [inputQueue release]; inputQueue = nil;
173 [outputQueue release]; outputQueue = nil;
174 [drawData release]; drawData = nil;
175 [frontendProxy release]; frontendProxy = nil;
176 [connection release]; connection = nil;
177 [actionDict release]; actionDict = nil;
178 [sysColorDict release]; sysColorDict = nil;
179 [colorDict release]; colorDict = nil;
184 - (void)setBackgroundColor:(int)color
186 backgroundColor = MM_COLOR_WITH_TRANSP(color,p_transp);
189 - (void)setForegroundColor:(int)color
191 foregroundColor = MM_COLOR(color);
194 - (void)setSpecialColor:(int)color
196 specialColor = MM_COLOR(color);
199 - (void)setDefaultColorsBackground:(int)bg foreground:(int)fg
201 defaultBackgroundColor = MM_COLOR_WITH_TRANSP(bg,p_transp);
202 defaultForegroundColor = MM_COLOR(fg);
204 NSMutableData *data = [NSMutableData data];
206 [data appendBytes:&defaultBackgroundColor length:sizeof(unsigned)];
207 [data appendBytes:&defaultForegroundColor length:sizeof(unsigned)];
209 [self queueMessage:SetDefaultColorsMsgID data:data];
212 - (NSConnection *)connection
215 // NOTE! If the name of the connection changes here it must also be
216 // updated in MMAppController.m.
217 NSString *name = [NSString stringWithFormat:@"%@-connection",
218 [[NSBundle mainBundle] bundleIdentifier]];
220 connection = [NSConnection connectionWithRegisteredName:name host:nil];
224 // NOTE: 'connection' may be nil here.
228 - (NSDictionary *)actionDict
233 - (void)queueMessage:(int)msgid properties:(NSDictionary *)props
235 [self queueMessage:msgid data:[props dictionaryAsData]];
240 if (![self connection]) {
241 NSBundle *mainBundle = [NSBundle mainBundle];
246 // Launch MacVim using Launch Services (NSWorkspace would be nicer, but
247 // the API to pass Apple Event parameters is broken on 10.4).
248 NSString *path = [mainBundle bundlePath];
249 status = FSPathMakeRef((const UInt8 *)[path UTF8String], &ref, NULL);
250 if (noErr == status) {
251 // Pass parameter to the 'Open' Apple Event that tells MacVim not
252 // to open an untitled window.
253 NSAppleEventDescriptor *desc =
254 [NSAppleEventDescriptor recordDescriptor];
255 [desc setParamDescriptor:
256 [NSAppleEventDescriptor descriptorWithBoolean:NO]
257 forKeyword:keyMMUntitledWindow];
259 LSLaunchFSRefSpec spec = { &ref, 0, NULL, [desc aeDesc],
260 kLSLaunchDefaults, NULL };
261 status = LSOpenFromRefSpec(&spec, NULL);
264 if (noErr != status) {
265 NSLog(@"ERROR: Failed to launch MacVim (path=%@).%@",
266 path, MMSymlinkWarningString);
270 // Launch MacVim using NSTask. For some reason the above code using
271 // Launch Services sometimes fails on LSOpenFromRefSpec() (when it
272 // fails, the dock icon starts bouncing and never stops). It seems
273 // like rebuilding the Launch Services database takes care of this
274 // problem, but the NSTask way seems more stable so stick with it.
276 // NOTE! Using NSTask to launch the GUI has the negative side-effect
277 // that the GUI won't be activated (or raised) so there is a hack in
278 // MMAppController which raises the app when a new window is opened.
279 NSMutableArray *args = [NSMutableArray arrayWithObjects:
280 [NSString stringWithFormat:@"-%@", MMNoWindowKey], @"yes", nil];
281 NSString *exeName = [[mainBundle infoDictionary]
282 objectForKey:@"CFBundleExecutable"];
283 NSString *path = [mainBundle pathForAuxiliaryExecutable:exeName];
285 NSLog(@"ERROR: Could not find MacVim executable in bundle.%@",
286 MMSymlinkWarningString);
290 [NSTask launchedTaskWithLaunchPath:path arguments:args];
293 // HACK! Poll the mach bootstrap server until it returns a valid
294 // connection to detect that MacVim has finished launching. Also set a
295 // time-out date so that we don't get stuck doing this forever.
296 NSDate *timeOutDate = [NSDate dateWithTimeIntervalSinceNow:10];
297 while (![self connection] &&
298 NSOrderedDescending == [timeOutDate compare:[NSDate date]])
299 [[NSRunLoop currentRunLoop]
300 runMode:NSDefaultRunLoopMode
301 beforeDate:[NSDate dateWithTimeIntervalSinceNow:1]];
303 // NOTE: [self connection] will set 'connection' as a side-effect.
305 NSLog(@"WARNING: Timed-out waiting for GUI to launch.");
312 [[NSNotificationCenter defaultCenter] addObserver:self
313 selector:@selector(connectionDidDie:)
314 name:NSConnectionDidDieNotification object:connection];
316 id proxy = [connection rootProxy];
317 [proxy setProtocolForProxy:@protocol(MMAppProtocol)];
319 int pid = [[NSProcessInfo processInfo] processIdentifier];
321 frontendProxy = [proxy connectBackend:self pid:pid];
323 [frontendProxy retain];
324 [frontendProxy setProtocolForProxy:@protocol(MMAppProtocol)];
328 @catch (NSException *e) {
329 NSLog(@"Exception caught when trying to connect backend: \"%@\"", e);
335 - (BOOL)openVimWindow
337 [self queueMessage:OpenVimWindowMsgID data:nil];
343 int type = ClearAllDrawType;
345 // Any draw commands in queue are effectively obsolete since this clearAll
346 // will negate any effect they have, therefore we may as well clear the
348 [drawData setLength:0];
350 [drawData appendBytes:&type length:sizeof(int)];
353 - (void)clearBlockFromRow:(int)row1 column:(int)col1
354 toRow:(int)row2 column:(int)col2
356 int type = ClearBlockDrawType;
358 [drawData appendBytes:&type length:sizeof(int)];
360 [drawData appendBytes:&defaultBackgroundColor length:sizeof(unsigned)];
361 [drawData appendBytes:&row1 length:sizeof(int)];
362 [drawData appendBytes:&col1 length:sizeof(int)];
363 [drawData appendBytes:&row2 length:sizeof(int)];
364 [drawData appendBytes:&col2 length:sizeof(int)];
367 - (void)deleteLinesFromRow:(int)row count:(int)count
368 scrollBottom:(int)bottom left:(int)left right:(int)right
370 int type = DeleteLinesDrawType;
372 [drawData appendBytes:&type length:sizeof(int)];
374 [drawData appendBytes:&defaultBackgroundColor length:sizeof(unsigned)];
375 [drawData appendBytes:&row length:sizeof(int)];
376 [drawData appendBytes:&count length:sizeof(int)];
377 [drawData appendBytes:&bottom length:sizeof(int)];
378 [drawData appendBytes:&left length:sizeof(int)];
379 [drawData appendBytes:&right length:sizeof(int)];
382 - (void)drawString:(char*)s length:(int)len row:(int)row column:(int)col
383 cells:(int)cells flags:(int)flags
385 if (len <= 0 || cells <= 0) return;
387 int type = DrawStringDrawType;
389 [drawData appendBytes:&type length:sizeof(int)];
391 [drawData appendBytes:&backgroundColor length:sizeof(unsigned)];
392 [drawData appendBytes:&foregroundColor length:sizeof(unsigned)];
393 [drawData appendBytes:&specialColor length:sizeof(unsigned)];
394 [drawData appendBytes:&row length:sizeof(int)];
395 [drawData appendBytes:&col length:sizeof(int)];
396 [drawData appendBytes:&cells length:sizeof(int)];
397 [drawData appendBytes:&flags length:sizeof(int)];
398 [drawData appendBytes:&len length:sizeof(int)];
399 [drawData appendBytes:s length:len];
402 - (void)insertLinesFromRow:(int)row count:(int)count
403 scrollBottom:(int)bottom left:(int)left right:(int)right
405 int type = InsertLinesDrawType;
407 [drawData appendBytes:&type length:sizeof(int)];
409 [drawData appendBytes:&defaultBackgroundColor length:sizeof(unsigned)];
410 [drawData appendBytes:&row length:sizeof(int)];
411 [drawData appendBytes:&count length:sizeof(int)];
412 [drawData appendBytes:&bottom length:sizeof(int)];
413 [drawData appendBytes:&left length:sizeof(int)];
414 [drawData appendBytes:&right length:sizeof(int)];
417 - (void)drawCursorAtRow:(int)row column:(int)col shape:(int)shape
418 fraction:(int)percent color:(int)color
420 int type = DrawCursorDrawType;
421 unsigned uc = MM_COLOR(color);
423 [drawData appendBytes:&type length:sizeof(int)];
425 [drawData appendBytes:&uc length:sizeof(unsigned)];
426 [drawData appendBytes:&row length:sizeof(int)];
427 [drawData appendBytes:&col length:sizeof(int)];
428 [drawData appendBytes:&shape length:sizeof(int)];
429 [drawData appendBytes:&percent length:sizeof(int)];
432 - (void)drawInvertedRectAtRow:(int)row column:(int)col numRows:(int)nr
433 numColumns:(int)nc invert:(int)invert
435 int type = DrawInvertedRectDrawType;
436 [drawData appendBytes:&type length:sizeof(int)];
438 [drawData appendBytes:&row length:sizeof(int)];
439 [drawData appendBytes:&col length:sizeof(int)];
440 [drawData appendBytes:&nr length:sizeof(int)];
441 [drawData appendBytes:&nc length:sizeof(int)];
442 [drawData appendBytes:&invert length:sizeof(int)];
447 // Tend to the run loop, returning immediately if there are no events
449 [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
450 beforeDate:[NSDate distantPast]];
453 // Keyboard and mouse input is handled directly, other input is queued and
454 // processed here. This call may enter a blocking loop.
455 if ([inputQueue count] > 0)
456 [self processInputQueue];
460 - (void)flushQueue:(BOOL)force
462 // NOTE! This method gets called a lot; if we were to flush every time it
463 // got called MacVim would feel unresponsive. So there is a time out which
464 // ensures that the queue isn't flushed too often.
465 if (!force && lastFlushDate
466 && -[lastFlushDate timeIntervalSinceNow] < MMFlushTimeoutInterval
467 && [drawData length] < MMFlushQueueLenHint)
470 if ([drawData length] > 0) {
471 // HACK! Detect changes to 'guifontwide'.
472 if (gui.wide_font != (GuiFont)oldWideFont) {
473 [oldWideFont release];
474 oldWideFont = [(NSFont*)gui.wide_font retain];
475 [self setWideFont:oldWideFont];
478 int type = SetCursorPosDrawType;
479 [drawData appendBytes:&type length:sizeof(type)];
480 [drawData appendBytes:&gui.row length:sizeof(gui.row)];
481 [drawData appendBytes:&gui.col length:sizeof(gui.col)];
483 [self queueMessage:BatchDrawMsgID data:[drawData copy]];
484 [drawData setLength:0];
487 if ([outputQueue count] > 0 || force) {
488 // When 'force' is set we always update the Vim state to ensure that
489 // MacVim has a copy of the latest state (since 'force' is typically
490 // set just before Vim takes a nap whilst waiting for input).
491 [self queueVimStateMessage];
494 [frontendProxy processCommandQueue:outputQueue];
496 @catch (NSException *e) {
497 NSLog(@"Exception caught when processing command queue: \"%@\"", e);
500 [outputQueue removeAllObjects];
502 [lastFlushDate release];
503 lastFlushDate = [[NSDate date] retain];
507 - (BOOL)waitForInput:(int)milliseconds
509 //NSLog(@"|ENTER| %s%d", _cmd, milliseconds);
511 // Only start the run loop if the input queue is empty, otherwise process
512 // the input first so that the input on queue isn't delayed.
513 if ([inputQueue count]) {
516 NSDate *date = milliseconds > 0 ?
517 [NSDate dateWithTimeIntervalSinceNow:.001*milliseconds] :
518 [NSDate distantFuture];
520 [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
524 // I know of no way to figure out if the run loop exited because input was
525 // found or because of a time out, so I need to manually indicate when
526 // input was received in processInput:data: and then reset it every time
528 BOOL yn = inputReceived;
531 // Keyboard and mouse input is handled directly, other input is queued and
532 // processed here. This call may enter a blocking loop.
533 if ([inputQueue count] > 0)
534 [self processInputQueue];
536 //NSLog(@"|LEAVE| %s input=%d", _cmd, yn);
542 #ifdef MAC_CLIENTSERVER
543 // The default connection is used for the client/server code.
544 [[NSConnection defaultConnection] setRootObject:nil];
545 [[NSConnection defaultConnection] invalidate];
548 // By invalidating the NSConnection the MMWindowController immediately
549 // finds out that the connection is down and as a result
550 // [MMWindowController connectionDidDie:] is invoked.
551 //NSLog(@"%@ %s", [self className], _cmd);
552 [[NSNotificationCenter defaultCenter] removeObserver:self];
553 [connection invalidate];
555 if (fontContainerRef) {
556 ATSFontDeactivate(fontContainerRef, NULL, kATSOptionFlagsDefault);
557 fontContainerRef = 0;
562 - (void)selectTab:(int)index
564 //NSLog(@"%s%d", _cmd, index);
567 NSData *data = [NSData dataWithBytes:&index length:sizeof(int)];
568 [self queueMessage:SelectTabMsgID data:data];
573 //NSLog(@"%s", _cmd);
575 NSMutableData *data = [NSMutableData data];
577 int idx = tabpage_index(curtab) - 1;
578 [data appendBytes:&idx length:sizeof(int)];
581 for (tp = first_tabpage; tp != NULL; tp = tp->tp_next) {
582 // This function puts the label of the tab in the global 'NameBuff'.
583 get_tabline_label(tp, FALSE);
584 char_u *s = NameBuff;
586 if (len <= 0) continue;
589 s = CONVERT_TO_UTF8(s);
592 // Count the number of windows in the tabpage.
593 //win_T *wp = tp->tp_firstwin;
595 //for (wincount = 0; wp != NULL; wp = wp->w_next, ++wincount);
597 //[data appendBytes:&wincount length:sizeof(int)];
598 [data appendBytes:&len length:sizeof(int)];
599 [data appendBytes:s length:len];
602 CONVERT_TO_UTF8_FREE(s);
606 [self queueMessage:UpdateTabBarMsgID data:data];
609 - (BOOL)tabBarVisible
611 return tabBarVisible;
614 - (void)showTabBar:(BOOL)enable
616 tabBarVisible = enable;
618 int msgid = enable ? ShowTabBarMsgID : HideTabBarMsgID;
619 [self queueMessage:msgid data:nil];
622 - (void)setRows:(int)rows columns:(int)cols
624 //NSLog(@"[VimTask] setRows:%d columns:%d", rows, cols);
626 int dim[] = { rows, cols };
627 NSData *data = [NSData dataWithBytes:&dim length:2*sizeof(int)];
629 [self queueMessage:SetTextDimensionsMsgID data:data];
632 - (void)setWindowTitle:(char *)title
634 NSMutableData *data = [NSMutableData data];
635 int len = strlen(title);
636 if (len <= 0) return;
638 [data appendBytes:&len length:sizeof(int)];
639 [data appendBytes:title length:len];
641 [self queueMessage:SetWindowTitleMsgID data:data];
644 - (void)setDocumentFilename:(char *)filename
646 NSMutableData *data = [NSMutableData data];
647 int len = filename ? strlen(filename) : 0;
649 [data appendBytes:&len length:sizeof(int)];
651 [data appendBytes:filename length:len];
653 [self queueMessage:SetDocumentFilenameMsgID data:data];
656 - (char *)browseForFileWithAttributes:(NSDictionary *)attr
661 [frontendProxy showSavePanelWithAttributes:attr];
663 [self waitForDialogReturn];
665 if (dialogReturn && [dialogReturn isKindOfClass:[NSString class]]) {
666 char_u *ret = (char_u*)[dialogReturn UTF8String];
668 ret = CONVERT_FROM_UTF8(ret);
670 s = vim_strsave(ret);
672 CONVERT_FROM_UTF8_FREE(ret);
676 [dialogReturn release]; dialogReturn = nil;
678 @catch (NSException *e) {
679 NSLog(@"Exception caught when showing save panel: \"%@\"", e);
685 - (oneway void)setDialogReturn:(in bycopy id)obj
687 // NOTE: This is called by
688 // - [MMVimController panelDidEnd:::], and
689 // - [MMVimController alertDidEnd:::],
690 // to indicate that a save/open panel or alert has finished.
692 // We want to distinguish between "no dialog return yet" and "dialog
693 // returned nothing". The former can be tested with dialogReturn == nil,
694 // the latter with dialogReturn == [NSNull null].
695 if (!obj) obj = [NSNull null];
697 if (obj != dialogReturn) {
698 [dialogReturn release];
699 dialogReturn = [obj retain];
703 - (int)showDialogWithAttributes:(NSDictionary *)attr textField:(char *)txtfield
708 [frontendProxy presentDialogWithAttributes:attr];
710 [self waitForDialogReturn];
712 if (dialogReturn && [dialogReturn isKindOfClass:[NSArray class]]
713 && [dialogReturn count]) {
714 retval = [[dialogReturn objectAtIndex:0] intValue];
715 if (txtfield && [dialogReturn count] > 1) {
716 NSString *retString = [dialogReturn objectAtIndex:1];
717 char_u *ret = (char_u*)[retString UTF8String];
719 ret = CONVERT_FROM_UTF8(ret);
721 vim_strncpy((char_u*)txtfield, ret, IOSIZE - 1);
723 CONVERT_FROM_UTF8_FREE(ret);
728 [dialogReturn release]; dialogReturn = nil;
730 @catch (NSException *e) {
731 NSLog(@"Exception caught while showing alert dialog: \"%@\"", e);
737 - (void)showToolbar:(int)enable flags:(int)flags
739 NSMutableData *data = [NSMutableData data];
741 [data appendBytes:&enable length:sizeof(int)];
742 [data appendBytes:&flags length:sizeof(int)];
744 [self queueMessage:ShowToolbarMsgID data:data];
747 - (void)createScrollbarWithIdentifier:(long)ident type:(int)type
749 NSMutableData *data = [NSMutableData data];
751 [data appendBytes:&ident length:sizeof(long)];
752 [data appendBytes:&type length:sizeof(int)];
754 [self queueMessage:CreateScrollbarMsgID data:data];
757 - (void)destroyScrollbarWithIdentifier:(long)ident
759 NSMutableData *data = [NSMutableData data];
760 [data appendBytes:&ident length:sizeof(long)];
762 [self queueMessage:DestroyScrollbarMsgID data:data];
765 - (void)showScrollbarWithIdentifier:(long)ident state:(int)visible
767 NSMutableData *data = [NSMutableData data];
769 [data appendBytes:&ident length:sizeof(long)];
770 [data appendBytes:&visible length:sizeof(int)];
772 [self queueMessage:ShowScrollbarMsgID data:data];
775 - (void)setScrollbarPosition:(int)pos length:(int)len identifier:(long)ident
777 NSMutableData *data = [NSMutableData data];
779 [data appendBytes:&ident length:sizeof(long)];
780 [data appendBytes:&pos length:sizeof(int)];
781 [data appendBytes:&len length:sizeof(int)];
783 [self queueMessage:SetScrollbarPositionMsgID data:data];
786 - (void)setScrollbarThumbValue:(long)val size:(long)size max:(long)max
787 identifier:(long)ident
789 float fval = max-size+1 > 0 ? (float)val/(max-size+1) : 0;
790 float prop = (float)size/(max+1);
791 if (fval < 0) fval = 0;
792 else if (fval > 1.0f) fval = 1.0f;
793 if (prop < 0) prop = 0;
794 else if (prop > 1.0f) prop = 1.0f;
796 NSMutableData *data = [NSMutableData data];
798 [data appendBytes:&ident length:sizeof(long)];
799 [data appendBytes:&fval length:sizeof(float)];
800 [data appendBytes:&prop length:sizeof(float)];
802 [self queueMessage:SetScrollbarThumbMsgID data:data];
805 - (void)setFont:(NSFont *)font
807 NSString *fontName = [font displayName];
808 float size = [font pointSize];
809 int len = [fontName lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
811 NSMutableData *data = [NSMutableData data];
813 [data appendBytes:&size length:sizeof(float)];
814 [data appendBytes:&len length:sizeof(int)];
815 [data appendBytes:[fontName UTF8String] length:len];
817 [self queueMessage:SetFontMsgID data:data];
821 - (void)setWideFont:(NSFont *)font
823 NSString *fontName = [font displayName];
824 float size = [font pointSize];
825 int len = [fontName lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
826 NSMutableData *data = [NSMutableData data];
828 [data appendBytes:&size length:sizeof(float)];
829 [data appendBytes:&len length:sizeof(int)];
831 [data appendBytes:[fontName UTF8String] length:len];
833 [self queueMessage:SetWideFontMsgID data:data];
836 - (void)executeActionWithName:(NSString *)name
838 int len = [name lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
841 NSMutableData *data = [NSMutableData data];
843 [data appendBytes:&len length:sizeof(int)];
844 [data appendBytes:[name UTF8String] length:len];
846 [self queueMessage:ExecuteActionMsgID data:data];
850 - (void)setMouseShape:(int)shape
852 NSMutableData *data = [NSMutableData data];
853 [data appendBytes:&shape length:sizeof(int)];
854 [self queueMessage:SetMouseShapeMsgID data:data];
857 - (void)setBlinkWait:(int)wait on:(int)on off:(int)off
859 // Vim specifies times in milliseconds, whereas Cocoa wants them in
861 blinkWaitInterval = .001f*wait;
862 blinkOnInterval = .001f*on;
863 blinkOffInterval = .001f*off;
869 [blinkTimer invalidate];
870 [blinkTimer release];
874 if (blinkWaitInterval > 0 && blinkOnInterval > 0 && blinkOffInterval > 0
876 blinkState = MMBlinkStateOn;
878 [[NSTimer scheduledTimerWithTimeInterval:blinkWaitInterval
880 selector:@selector(blinkTimerFired:)
881 userInfo:nil repeats:NO] retain];
882 gui_update_cursor(TRUE, FALSE);
883 [self flushQueue:YES];
889 if (MMBlinkStateOff == blinkState) {
890 gui_update_cursor(TRUE, FALSE);
891 [self flushQueue:YES];
894 blinkState = MMBlinkStateNone;
897 - (void)adjustLinespace:(int)linespace
899 NSMutableData *data = [NSMutableData data];
900 [data appendBytes:&linespace length:sizeof(int)];
901 [self queueMessage:AdjustLinespaceMsgID data:data];
906 [self queueMessage:ActivateMsgID data:nil];
909 - (void)setPreEditRow:(int)row column:(int)col
911 NSMutableData *data = [NSMutableData data];
912 [data appendBytes:&row length:sizeof(int)];
913 [data appendBytes:&col length:sizeof(int)];
914 [self queueMessage:SetPreEditPositionMsgID data:data];
917 - (int)lookupColorWithKey:(NSString *)key
919 if (!(key && [key length] > 0))
922 NSString *stripKey = [[[[key lowercaseString]
923 stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]
924 componentsSeparatedByString:@" "]
925 componentsJoinedByString:@""];
927 if (stripKey && [stripKey length] > 0) {
928 // First of all try to lookup key in the color dictionary; note that
929 // all keys in this dictionary are lowercase with no whitespace.
930 id obj = [colorDict objectForKey:stripKey];
931 if (obj) return [obj intValue];
933 // The key was not in the dictionary; is it perhaps of the form
935 if ([stripKey length] > 1 && [stripKey characterAtIndex:0] == '#') {
936 NSScanner *scanner = [NSScanner scannerWithString:stripKey];
937 [scanner setScanLocation:1];
939 if ([scanner scanHexInt:&hex]) {
944 // As a last resort, check if it is one of the system defined colors.
945 // The keys in this dictionary are also lowercase with no whitespace.
946 obj = [sysColorDict objectForKey:stripKey];
948 NSColor *col = [NSColor performSelector:NSSelectorFromString(obj)];
951 col = [col colorUsingColorSpaceName:NSCalibratedRGBColorSpace];
952 [col getRed:&r green:&g blue:&b alpha:&a];
953 return (((int)(r*255+.5f) & 0xff) << 16)
954 + (((int)(g*255+.5f) & 0xff) << 8)
955 + ((int)(b*255+.5f) & 0xff);
960 //NSLog(@"WARNING: No color with key %@ found.", stripKey);
964 - (BOOL)hasSpecialKeyWithValue:(NSString *)value
966 NSEnumerator *e = [[MMBackend specialKeys] objectEnumerator];
969 while ((obj = [e nextObject])) {
970 if ([value isEqual:obj])
977 - (void)enterFullscreen:(int)fuoptions background:(int)bg
979 NSMutableData *data = [NSMutableData data];
980 [data appendBytes:&fuoptions length:sizeof(int)];
982 [data appendBytes:&bg length:sizeof(int)];
983 [self queueMessage:EnterFullscreenMsgID data:data];
986 - (void)leaveFullscreen
988 [self queueMessage:LeaveFullscreenMsgID data:nil];
991 - (void)setAntialias:(BOOL)antialias
993 int msgid = antialias ? EnableAntialiasMsgID : DisableAntialiasMsgID;
995 [self queueMessage:msgid data:nil];
998 - (void)updateModifiedFlag
1000 // Notify MacVim if _any_ buffer has changed from unmodified to modified or
1002 int msgid = [self checkForModifiedBuffers]
1003 ? BuffersModifiedMsgID : BuffersNotModifiedMsgID;
1005 [self queueMessage:msgid data:nil];
1008 - (oneway void)processInput:(int)msgid data:(in bycopy NSData *)data
1010 // NOTE: This method might get called whenever the run loop is tended to.
1011 // Normal keyboard and mouse input is added to input buffers, so there is
1012 // no risk in handling these events directly (they return immediately, and
1013 // do not call any other Vim functions). However, other events such
1014 // as 'VimShouldCloseMsgID' may enter blocking loops that wait for key
1015 // events which would cause this method to be called recursively. This
1016 // in turn leads to various difficulties that we do not want to have to
1017 // deal with. To avoid recursive calls here we add all events except
1018 // keyboard and mouse events to an input queue which is processed whenever
1019 // gui_mch_update() is called (see processInputQueue).
1021 //NSLog(@"%s%s", _cmd, MessageStrings[msgid]);
1023 // Don't flush too soon after receiving input or update speed will suffer.
1024 [lastFlushDate release];
1025 lastFlushDate = [[NSDate date] retain];
1027 // Handle keyboard and mouse input now. All other events are queued.
1028 if (InsertTextMsgID == msgid) {
1029 [self handleInsertText:data];
1030 } else if (KeyDownMsgID == msgid || CmdKeyMsgID == msgid) {
1032 const void *bytes = [data bytes];
1033 int mods = *((int*)bytes); bytes += sizeof(int);
1034 int len = *((int*)bytes); bytes += sizeof(int);
1035 NSString *key = [[NSString alloc] initWithBytes:bytes length:len
1036 encoding:NSUTF8StringEncoding];
1037 mods = eventModifierFlagsToVimModMask(mods);
1039 [self handleKeyDown:key modifiers:mods];
1042 } else if (ScrollWheelMsgID == msgid) {
1044 const void *bytes = [data bytes];
1046 int row = *((int*)bytes); bytes += sizeof(int);
1047 int col = *((int*)bytes); bytes += sizeof(int);
1048 int flags = *((int*)bytes); bytes += sizeof(int);
1049 float dy = *((float*)bytes); bytes += sizeof(float);
1051 int button = MOUSE_5;
1052 if (dy > 0) button = MOUSE_4;
1054 flags = eventModifierFlagsToVimMouseModMask(flags);
1056 int numLines = (int)round(dy);
1057 if (numLines < 0) numLines = -numLines;
1058 if (numLines == 0) numLines = 1;
1060 #ifdef FEAT_GUI_SCROLL_WHEEL_FORCE
1061 gui.scroll_wheel_force = numLines;
1064 gui_send_mouse_event(button, col, row, NO, flags);
1065 } else if (MouseDownMsgID == msgid) {
1067 const void *bytes = [data bytes];
1069 int row = *((int*)bytes); bytes += sizeof(int);
1070 int col = *((int*)bytes); bytes += sizeof(int);
1071 int button = *((int*)bytes); bytes += sizeof(int);
1072 int flags = *((int*)bytes); bytes += sizeof(int);
1073 int count = *((int*)bytes); bytes += sizeof(int);
1075 button = eventButtonNumberToVimMouseButton(button);
1077 flags = eventModifierFlagsToVimMouseModMask(flags);
1078 gui_send_mouse_event(button, col, row, count>1, flags);
1080 } else if (MouseUpMsgID == msgid) {
1082 const void *bytes = [data bytes];
1084 int row = *((int*)bytes); bytes += sizeof(int);
1085 int col = *((int*)bytes); bytes += sizeof(int);
1086 int flags = *((int*)bytes); bytes += sizeof(int);
1088 flags = eventModifierFlagsToVimMouseModMask(flags);
1090 gui_send_mouse_event(MOUSE_RELEASE, col, row, NO, flags);
1091 } else if (MouseDraggedMsgID == msgid) {
1093 const void *bytes = [data bytes];
1095 int row = *((int*)bytes); bytes += sizeof(int);
1096 int col = *((int*)bytes); bytes += sizeof(int);
1097 int flags = *((int*)bytes); bytes += sizeof(int);
1099 flags = eventModifierFlagsToVimMouseModMask(flags);
1101 gui_send_mouse_event(MOUSE_DRAG, col, row, NO, flags);
1102 } else if (MouseMovedMsgID == msgid) {
1103 const void *bytes = [data bytes];
1104 int row = *((int*)bytes); bytes += sizeof(int);
1105 int col = *((int*)bytes); bytes += sizeof(int);
1107 gui_mouse_moved(col, row);
1108 } else if (AddInputMsgID == msgid) {
1109 NSString *string = [[NSString alloc] initWithData:data
1110 encoding:NSUTF8StringEncoding];
1112 [self addInput:string];
1115 } else if (TerminateNowMsgID == msgid) {
1116 isTerminating = YES;
1118 // Not keyboard or mouse event, queue it and handle later.
1119 //NSLog(@"Add event %s to input event queue", MessageStrings[msgid]);
1120 [inputQueue addObject:[NSNumber numberWithInt:msgid]];
1121 [inputQueue addObject:(data ? (id)data : (id)[NSNull null])];
1124 // See waitForInput: for an explanation of this flag.
1125 inputReceived = YES;
1128 - (oneway void)processInputAndData:(in bycopy NSArray *)messages
1130 // TODO: Get rid of this method?
1131 //NSLog(@"%s%@", _cmd, messages);
1133 unsigned i, count = [messages count];
1134 for (i = 0; i < count; i += 2) {
1135 int msgid = [[messages objectAtIndex:i] intValue];
1136 id data = [messages objectAtIndex:i+1];
1137 if ([data isEqual:[NSNull null]])
1140 [self processInput:msgid data:data];
1144 - (NSString *)evaluateExpression:(in bycopy NSString *)expr
1146 NSString *eval = nil;
1147 char_u *s = (char_u*)[expr UTF8String];
1150 s = CONVERT_FROM_UTF8(s);
1153 char_u *res = eval_client_expr_to_string(s);
1156 CONVERT_FROM_UTF8_FREE(s);
1162 s = CONVERT_TO_UTF8(s);
1164 eval = [NSString stringWithUTF8String:(char*)s];
1166 CONVERT_TO_UTF8_FREE(s);
1174 - (BOOL)starRegisterToPasteboard:(byref NSPasteboard *)pboard
1176 // TODO: This method should share code with clip_mch_request_selection().
1178 if (VIsual_active && (State & NORMAL) && clip_star.available) {
1179 // If there is no pasteboard, return YES to indicate that there is text
1184 clip_copy_selection();
1186 // Get the text to put on the pasteboard.
1187 long_u llen = 0; char_u *str = 0;
1188 int type = clip_convert_selection(&str, &llen, &clip_star);
1192 // TODO: Avoid overflow.
1193 int len = (int)llen;
1195 if (output_conv.vc_type != CONV_NONE) {
1196 char_u *conv_str = string_convert(&output_conv, str, &len);
1204 NSString *string = [[NSString alloc]
1205 initWithBytes:str length:len encoding:NSUTF8StringEncoding];
1207 NSArray *types = [NSArray arrayWithObject:NSStringPboardType];
1208 [pboard declareTypes:types owner:nil];
1209 BOOL ok = [pboard setString:string forType:NSStringPboardType];
1220 - (oneway void)addReply:(in bycopy NSString *)reply
1221 server:(in byref id <MMVimServerProtocol>)server
1223 //NSLog(@"addReply:%@ server:%@", reply, (id)server);
1225 // Replies might come at any time and in any order so we keep them in an
1226 // array inside a dictionary with the send port used as key.
1228 NSConnection *conn = [(NSDistantObject*)server connectionForProxy];
1229 // HACK! Assume connection uses mach ports.
1230 int port = [(NSMachPort*)[conn sendPort] machPort];
1231 NSNumber *key = [NSNumber numberWithInt:port];
1233 NSMutableArray *replies = [serverReplyDict objectForKey:key];
1235 replies = [NSMutableArray array];
1236 [serverReplyDict setObject:replies forKey:key];
1239 [replies addObject:reply];
1242 - (void)addInput:(in bycopy NSString *)input
1243 client:(in byref id <MMVimClientProtocol>)client
1245 //NSLog(@"addInput:%@ client:%@", input, (id)client);
1247 [self addInput:input];
1248 [self addClient:(id)client];
1250 inputReceived = YES;
1253 - (NSString *)evaluateExpression:(in bycopy NSString *)expr
1254 client:(in byref id <MMVimClientProtocol>)client
1256 [self addClient:(id)client];
1257 return [self evaluateExpression:expr];
1260 - (void)registerServerWithName:(NSString *)name
1262 NSString *svrName = name;
1263 NSConnection *svrConn = [NSConnection defaultConnection];
1266 for (i = 0; i < MMServerMax; ++i) {
1267 NSString *connName = [self connectionNameFromServerName:svrName];
1269 if ([svrConn registerName:connName]) {
1270 //NSLog(@"Registered server with name: %@", svrName);
1272 // TODO: Set request/reply time-outs to something else?
1274 // Don't wait for requests (time-out means that the message is
1276 [svrConn setRequestTimeout:0];
1277 //[svrConn setReplyTimeout:MMReplyTimeout];
1278 [svrConn setRootObject:self];
1280 char_u *s = (char_u*)[svrName UTF8String];
1282 s = CONVERT_FROM_UTF8(s);
1284 // NOTE: 'serverName' is a global variable
1285 serverName = vim_strsave(s);
1287 CONVERT_FROM_UTF8_FREE(s);
1290 set_vim_var_string(VV_SEND_SERVER, serverName, -1);
1293 need_maketitle = TRUE;
1295 [self queueMessage:SetServerNameMsgID data:
1296 [svrName dataUsingEncoding:NSUTF8StringEncoding]];
1300 svrName = [NSString stringWithFormat:@"%@%d", name, i+1];
1304 - (BOOL)sendToServer:(NSString *)name string:(NSString *)string
1305 reply:(char_u **)reply port:(int *)port expression:(BOOL)expr
1308 // NOTE: If 'name' equals 'serverName' then the request is local (client
1309 // and server are the same). This case is not handled separately, so a
1310 // connection will be set up anyway (this simplifies the code).
1312 NSConnection *conn = [self connectionForServerName:name];
1315 char_u *s = (char_u*)[name UTF8String];
1317 s = CONVERT_FROM_UTF8(s);
1319 EMSG2(_(e_noserver), s);
1321 CONVERT_FROM_UTF8_FREE(s);
1328 // HACK! Assume connection uses mach ports.
1329 *port = [(NSMachPort*)[conn sendPort] machPort];
1332 id proxy = [conn rootProxy];
1333 [proxy setProtocolForProxy:@protocol(MMVimServerProtocol)];
1337 NSString *eval = [proxy evaluateExpression:string client:self];
1340 char_u *r = (char_u*)[eval UTF8String];
1342 r = CONVERT_FROM_UTF8(r);
1344 *reply = vim_strsave(r);
1346 CONVERT_FROM_UTF8_FREE(r);
1349 *reply = vim_strsave((char_u*)_(e_invexprmsg));
1356 [proxy addInput:string client:self];
1359 @catch (NSException *e) {
1360 NSLog(@"WARNING: Caught exception in %s: \"%@\"", _cmd, e);
1367 - (NSArray *)serverList
1369 NSArray *list = nil;
1371 if ([self connection]) {
1372 id proxy = [connection rootProxy];
1373 [proxy setProtocolForProxy:@protocol(MMAppProtocol)];
1376 list = [proxy serverList];
1378 @catch (NSException *e) {
1379 NSLog(@"Exception caught when listing servers: \"%@\"", e);
1382 EMSG(_("E???: No connection to MacVim, server listing not possible."));
1388 - (NSString *)peekForReplyOnPort:(int)port
1390 //NSLog(@"%s%d", _cmd, port);
1392 NSNumber *key = [NSNumber numberWithInt:port];
1393 NSMutableArray *replies = [serverReplyDict objectForKey:key];
1394 if (replies && [replies count]) {
1395 //NSLog(@" %d replies, topmost is: %@", [replies count],
1396 // [replies objectAtIndex:0]);
1397 return [replies objectAtIndex:0];
1400 //NSLog(@" No replies");
1404 - (NSString *)waitForReplyOnPort:(int)port
1406 //NSLog(@"%s%d", _cmd, port);
1408 NSConnection *conn = [self connectionForServerPort:port];
1412 NSNumber *key = [NSNumber numberWithInt:port];
1413 NSMutableArray *replies = nil;
1414 NSString *reply = nil;
1416 // Wait for reply as long as the connection to the server is valid (unless
1417 // user interrupts wait with Ctrl-C).
1418 while (!got_int && [conn isValid] &&
1419 !(replies = [serverReplyDict objectForKey:key])) {
1420 [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
1421 beforeDate:[NSDate distantFuture]];
1425 if ([replies count] > 0) {
1426 reply = [[replies objectAtIndex:0] retain];
1427 //NSLog(@" Got reply: %@", reply);
1428 [replies removeObjectAtIndex:0];
1429 [reply autorelease];
1432 if ([replies count] == 0)
1433 [serverReplyDict removeObjectForKey:key];
1439 - (BOOL)sendReply:(NSString *)reply toPort:(int)port
1441 id client = [clientProxyDict objectForKey:[NSNumber numberWithInt:port]];
1444 //NSLog(@"sendReply:%@ toPort:%d", reply, port);
1445 [client addReply:reply server:self];
1448 @catch (NSException *e) {
1449 NSLog(@"WARNING: Exception caught in %s: \"%@\"", _cmd, e);
1452 EMSG2(_("E???: server2client failed; no client with id 0x%x"), port);
1462 @implementation MMBackend (Private)
1464 - (void)waitForDialogReturn
1466 // Keep processing the run loop until a dialog returns. To avoid getting
1467 // stuck in an endless loop (could happen if the setDialogReturn: message
1468 // was lost) we also do some paranoia checks.
1470 // Note that in Cocoa the user can still resize windows and select menu
1471 // items while a sheet is being displayed, so we can't just wait for the
1472 // first message to arrive and assume that is the setDialogReturn: call.
1474 while (nil == dialogReturn && !got_int && [connection isValid]
1476 [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
1477 beforeDate:[NSDate distantFuture]];
1479 // Search for any resize messages on the input queue. All other messages
1480 // on the input queue are dropped. The reason why we single out resize
1481 // messages is because the user may have resized the window while a sheet
1483 int i, count = [inputQueue count];
1485 id textDimData = nil;
1487 for (i = count-2; i >= 0; i -= 2) {
1488 int msgid = [[inputQueue objectAtIndex:i] intValue];
1489 if (SetTextDimensionsMsgID == msgid) {
1490 textDimData = [[inputQueue objectAtIndex:i+1] retain];
1496 [inputQueue removeAllObjects];
1499 [inputQueue addObject:
1500 [NSNumber numberWithInt:SetTextDimensionsMsgID]];
1501 [inputQueue addObject:textDimData];
1502 [textDimData release];
1507 - (void)queueVimStateMessage
1509 // NOTE: This is the place to add Vim state that needs to be accessed from
1510 // MacVim. Do not add state that could potentially require lots of memory
1511 // since this message gets sent each time the output queue is forcibly
1512 // flushed (e.g. storing the currently selected text would be a bad idea).
1513 // We take this approach of "pushing" the state to MacVim to avoid having
1514 // to make synchronous calls from MacVim to Vim in order to get state.
1516 NSDictionary *vimState = [NSDictionary dictionaryWithObjectsAndKeys:
1517 [[NSFileManager defaultManager] currentDirectoryPath], @"pwd",
1518 [NSNumber numberWithInt:p_mh], @"p_mh",
1521 [self queueMessage:SetVimStateMsgID data:[vimState dictionaryAsData]];
1524 - (void)processInputQueue
1526 if ([inputQueue count] == 0) return;
1528 // NOTE: One of the input events may cause this method to be called
1529 // recursively, so copy the input queue to a local variable and clear it
1530 // before starting to process input events (otherwise we could get stuck in
1531 // an endless loop).
1532 NSArray *q = [inputQueue copy];
1533 unsigned i, count = [q count];
1535 [inputQueue removeAllObjects];
1537 for (i = 0; i < count-1; i += 2) {
1538 int msgid = [[q objectAtIndex:i] intValue];
1539 id data = [q objectAtIndex:i+1];
1540 if ([data isEqual:[NSNull null]])
1543 //NSLog(@"(%d) %s:%s", i, _cmd, MessageStrings[msgid]);
1544 [self handleInputEvent:msgid data:data];
1548 //NSLog(@"Clear input event queue");
1551 - (void)handleInputEvent:(int)msgid data:(NSData *)data
1553 // NOTE: Be careful with what you do in this method. Ideally, a message
1554 // should be handled by adding something to the input buffer and returning
1555 // immediately. If you call a Vim function then it should not enter a loop
1556 // waiting for key presses or in any other way block the process. The
1557 // reason for this being that only one message can be processed at a time,
1558 // so if another message is received while processing, then the new message
1559 // is dropped. See also the comment in processInput:data:.
1561 //NSLog(@"%s%s", _cmd, MessageStrings[msgid]);
1563 if (SelectTabMsgID == msgid) {
1565 const void *bytes = [data bytes];
1566 int idx = *((int*)bytes) + 1;
1567 //NSLog(@"Selecting tab %d", idx);
1568 send_tabline_event(idx);
1569 } else if (CloseTabMsgID == msgid) {
1571 const void *bytes = [data bytes];
1572 int idx = *((int*)bytes) + 1;
1573 //NSLog(@"Closing tab %d", idx);
1574 send_tabline_menu_event(idx, TABLINE_MENU_CLOSE);
1575 } else if (AddNewTabMsgID == msgid) {
1576 //NSLog(@"Adding new tab");
1577 send_tabline_menu_event(0, TABLINE_MENU_NEW);
1578 } else if (DraggedTabMsgID == msgid) {
1580 const void *bytes = [data bytes];
1581 // NOTE! The destination index is 0 based, so do not add 1 to make it 1
1583 int idx = *((int*)bytes);
1586 } else if (SetTextDimensionsMsgID == msgid || LiveResizeMsgID == msgid) {
1588 const void *bytes = [data bytes];
1589 int rows = *((int*)bytes); bytes += sizeof(int);
1590 int cols = *((int*)bytes); bytes += sizeof(int);
1592 // NOTE! Vim doesn't call gui_mch_set_shellsize() after
1593 // gui_resize_shell(), so we have to manually set the rows and columns
1594 // here. (MacVim doesn't change the rows and columns to avoid
1595 // inconsistent states between Vim and MacVim.)
1596 [self queueMessage:msgid data:data];
1598 //NSLog(@"[VimTask] Resizing shell to %dx%d.", cols, rows);
1599 gui_resize_shell(cols, rows);
1600 } else if (ExecuteMenuMsgID == msgid) {
1601 NSDictionary *attrs = [NSDictionary dictionaryWithData:data];
1603 NSArray *desc = [attrs objectForKey:@"descriptor"];
1604 vimmenu_T *menu = menu_for_descriptor(desc);
1608 } else if (ToggleToolbarMsgID == msgid) {
1609 [self handleToggleToolbar];
1610 } else if (ScrollbarEventMsgID == msgid) {
1611 [self handleScrollbarEvent:data];
1612 } else if (SetFontMsgID == msgid) {
1613 [self handleSetFont:data];
1614 } else if (VimShouldCloseMsgID == msgid) {
1616 } else if (DropFilesMsgID == msgid) {
1617 [self handleDropFiles:data];
1618 } else if (DropStringMsgID == msgid) {
1619 [self handleDropString:data];
1620 } else if (GotFocusMsgID == msgid) {
1622 [self focusChange:YES];
1623 } else if (LostFocusMsgID == msgid) {
1625 [self focusChange:NO];
1626 } else if (SetMouseShapeMsgID == msgid) {
1627 const void *bytes = [data bytes];
1628 int shape = *((int*)bytes); bytes += sizeof(int);
1629 update_mouseshape(shape);
1630 } else if (ODBEditMsgID == msgid) {
1631 [self handleOdbEdit:data];
1632 } else if (XcodeModMsgID == msgid) {
1633 [self handleXcodeMod:data];
1635 NSLog(@"WARNING: Unknown message received (msgid=%d)", msgid);
1639 + (NSDictionary *)specialKeys
1641 static NSDictionary *specialKeys = nil;
1644 NSBundle *mainBundle = [NSBundle mainBundle];
1645 NSString *path = [mainBundle pathForResource:@"SpecialKeys"
1647 specialKeys = [[NSDictionary alloc] initWithContentsOfFile:path];
1653 - (void)handleInsertText:(NSData *)data
1657 NSString *key = [[NSString alloc] initWithData:data
1658 encoding:NSUTF8StringEncoding];
1659 char_u *str = (char_u*)[key UTF8String];
1660 int i, len = [key lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
1663 char_u *conv_str = NULL;
1664 if (input_conv.vc_type != CONV_NONE) {
1665 conv_str = string_convert(&input_conv, str, &len);
1671 if (len == 1 && ((str[0] == Ctrl_C && ctrl_c_interrupts)
1672 || (str[0] == intr_char && intr_char != Ctrl_C))) {
1677 for (i = 0; i < len; ++i) {
1678 add_to_input_buf(str+i, 1);
1679 if (CSI == str[i]) {
1680 // NOTE: If the converted string contains the byte CSI, then it
1681 // must be followed by the bytes KS_EXTRA, KE_CSI or things
1683 static char_u extra[2] = { KS_EXTRA, KE_CSI };
1684 add_to_input_buf(extra, 2);
1695 - (void)handleKeyDown:(NSString *)key modifiers:(int)mods
1699 char_u *chars = (char_u*)[key UTF8String];
1701 char_u *conv_str = NULL;
1703 int length = [key lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
1705 // Special keys (arrow keys, function keys, etc.) are stored in a plist so
1706 // that new keys can easily be added.
1707 NSString *specialString = [[MMBackend specialKeys]
1709 if (specialString && [specialString length] > 1) {
1710 //NSLog(@"special key: %@", specialString);
1711 int ikey = TO_SPECIAL([specialString characterAtIndex:0],
1712 [specialString characterAtIndex:1]);
1714 ikey = simplify_key(ikey, &mods);
1719 special[1] = K_SECOND(ikey);
1720 special[2] = K_THIRD(ikey);
1724 } else if (1 == length && TAB == chars[0]) {
1725 // Tab is a trouble child:
1726 // - <Tab> is added to the input buffer as is
1727 // - <S-Tab> is translated to, {CSI,'k','B'} (i.e. 'Back-tab')
1728 // - <M-Tab> should be 0x80|TAB but this is not valid utf-8 so it needs
1729 // to be converted to utf-8
1730 // - <S-M-Tab> is translated to <S-Tab> with ALT modifier
1731 // - <C-Tab> is reserved by Mac OS X
1732 // - <D-Tab> is reserved by Mac OS X
1737 if (mods & MOD_MASK_SHIFT) {
1738 mods &= ~MOD_MASK_SHIFT;
1740 special[1] = K_SECOND(K_S_TAB);
1741 special[2] = K_THIRD(K_S_TAB);
1743 } else if (mods & MOD_MASK_ALT) {
1744 int mtab = 0x80 | TAB;
1748 special[0] = (mtab >> 6) + 0xc0;
1749 special[1] = mtab & 0xbf;
1757 mods &= ~MOD_MASK_ALT;
1759 } else if (length > 0) {
1760 unichar c = [key characterAtIndex:0];
1762 //NSLog(@"non-special: %@ (hex=%x, mods=%d)", key,
1763 // [key characterAtIndex:0], mods);
1765 if (length == 1 && ((c == Ctrl_C && ctrl_c_interrupts)
1766 || (c == intr_char && intr_char != Ctrl_C))) {
1771 // HACK! In most circumstances the Ctrl and Shift modifiers should be
1772 // cleared since they are already added to the key by the AppKit.
1773 // Unfortunately, the only way to deal with when to clear the modifiers
1774 // or not seems to be to have hard-wired rules like this.
1775 if ( !((' ' == c) || (0xa0 == c) || (mods & MOD_MASK_CMD)
1776 || 0x9 == c || 0xd == c || ESC == c) ) {
1777 mods &= ~MOD_MASK_SHIFT;
1778 mods &= ~MOD_MASK_CTRL;
1779 //NSLog(@"clear shift ctrl");
1782 // HACK! All Option+key presses go via 'insert text' messages, except
1783 // for <M-Space>. If the Alt flag is not cleared for <M-Space> it does
1784 // not work to map to it.
1785 if (0xa0 == c && !(mods & MOD_MASK_CMD)) {
1786 //NSLog(@"clear alt");
1787 mods &= ~MOD_MASK_ALT;
1791 if (input_conv.vc_type != CONV_NONE) {
1792 conv_str = string_convert(&input_conv, chars, &length);
1799 if (chars && length > 0) {
1801 //NSLog(@"adding mods: %d", mods);
1803 modChars[1] = KS_MODIFIER;
1805 add_to_input_buf(modChars, 3);
1808 //NSLog(@"add to input buf: 0x%x", chars[0]);
1809 // TODO: Check for CSI bytes?
1810 add_to_input_buf(chars, length);
1819 - (void)queueMessage:(int)msgid data:(NSData *)data
1821 //if (msgid != EnableMenuItemMsgID)
1822 // NSLog(@"queueMessage:%s", MessageStrings[msgid]);
1824 [outputQueue addObject:[NSData dataWithBytes:&msgid length:sizeof(int)]];
1826 [outputQueue addObject:data];
1828 [outputQueue addObject:[NSData data]];
1831 - (void)connectionDidDie:(NSNotification *)notification
1833 // If the main connection to MacVim is lost this means that MacVim was
1834 // either quit (by the user chosing Quit on the MacVim menu), or it has
1835 // crashed. In the former case the flag 'isTerminating' is set and we then
1836 // quit cleanly; in the latter case we make sure the swap files are left
1839 //NSLog(@"%s isTerminating=%d", _cmd, isTerminating);
1843 getout_preserve_modified(1);
1846 - (void)blinkTimerFired:(NSTimer *)timer
1848 NSTimeInterval timeInterval = 0;
1850 [blinkTimer release];
1853 if (MMBlinkStateOn == blinkState) {
1854 gui_undraw_cursor();
1855 blinkState = MMBlinkStateOff;
1856 timeInterval = blinkOffInterval;
1857 } else if (MMBlinkStateOff == blinkState) {
1858 gui_update_cursor(TRUE, FALSE);
1859 blinkState = MMBlinkStateOn;
1860 timeInterval = blinkOnInterval;
1863 if (timeInterval > 0) {
1865 [[NSTimer scheduledTimerWithTimeInterval:timeInterval target:self
1866 selector:@selector(blinkTimerFired:)
1867 userInfo:nil repeats:NO] retain];
1868 [self flushQueue:YES];
1872 - (void)focusChange:(BOOL)on
1874 gui_focus_change(on);
1877 - (void)handleToggleToolbar
1879 // If 'go' contains 'T', then remove it, else add it.
1881 char_u go[sizeof(GO_ALL)+2];
1886 p = vim_strchr(go, GO_TOOLBAR);
1890 char_u *end = go + len;
1896 go[len] = GO_TOOLBAR;
1900 set_option_value((char_u*)"guioptions", 0, go, 0);
1902 // Force screen redraw (does it have to be this complicated?).
1903 redraw_all_later(CLEAR);
1904 update_screen(NOT_VALID);
1907 gui_update_cursor(FALSE, FALSE);
1911 - (void)handleScrollbarEvent:(NSData *)data
1915 const void *bytes = [data bytes];
1916 long ident = *((long*)bytes); bytes += sizeof(long);
1917 int hitPart = *((int*)bytes); bytes += sizeof(int);
1918 float fval = *((float*)bytes); bytes += sizeof(float);
1919 scrollbar_T *sb = gui_find_scrollbar(ident);
1922 scrollbar_T *sb_info = sb->wp ? &sb->wp->w_scrollbars[0] : sb;
1923 long value = sb_info->value;
1924 long size = sb_info->size;
1925 long max = sb_info->max;
1926 BOOL isStillDragging = NO;
1927 BOOL updateKnob = YES;
1930 case NSScrollerDecrementPage:
1931 value -= (size > 2 ? size - 2 : 1);
1933 case NSScrollerIncrementPage:
1934 value += (size > 2 ? size - 2 : 1);
1936 case NSScrollerDecrementLine:
1939 case NSScrollerIncrementLine:
1942 case NSScrollerKnob:
1943 isStillDragging = YES;
1945 case NSScrollerKnobSlot:
1946 value = (long)(fval * (max - size + 1));
1953 //NSLog(@"value %d -> %d", sb_info->value, value);
1954 gui_drag_scrollbar(sb, value, isStillDragging);
1957 // Dragging the knob or option+clicking automatically updates
1958 // the knob position (on the actual NSScroller), so we only
1959 // need to set the knob position in the other cases.
1961 // Update both the left&right vertical scrollbars.
1962 long identLeft = sb->wp->w_scrollbars[SBAR_LEFT].ident;
1963 long identRight = sb->wp->w_scrollbars[SBAR_RIGHT].ident;
1964 [self setScrollbarThumbValue:value size:size max:max
1965 identifier:identLeft];
1966 [self setScrollbarThumbValue:value size:size max:max
1967 identifier:identRight];
1969 // Update the horizontal scrollbar.
1970 [self setScrollbarThumbValue:value size:size max:max
1977 - (void)handleSetFont:(NSData *)data
1981 const void *bytes = [data bytes];
1982 float pointSize = *((float*)bytes); bytes += sizeof(float);
1983 //unsigned len = *((unsigned*)bytes); bytes += sizeof(unsigned);
1984 bytes += sizeof(unsigned); // len not used
1986 NSMutableString *name = [NSMutableString stringWithUTF8String:bytes];
1987 [name appendString:[NSString stringWithFormat:@":h%.2f", pointSize]];
1988 char_u *s = (char_u*)[name UTF8String];
1991 s = CONVERT_FROM_UTF8(s);
1994 set_option_value((char_u*)"guifont", 0, s, 0);
1997 CONVERT_FROM_UTF8_FREE(s);
2000 // Force screen redraw (does it have to be this complicated?).
2001 redraw_all_later(CLEAR);
2002 update_screen(NOT_VALID);
2005 gui_update_cursor(FALSE, FALSE);
2009 - (void)handleDropFiles:(NSData *)data
2011 // TODO: Get rid of this method; instead use Vim script directly. At the
2012 // moment I know how to do this to open files in tabs, but I'm not sure how
2013 // to add the filenames to the command line when in command line mode.
2018 const void *bytes = [data bytes];
2019 const void *end = [data bytes] + [data length];
2020 BOOL forceOpen = *((BOOL*)bytes); bytes += sizeof(BOOL);
2021 int n = *((int*)bytes); bytes += sizeof(int);
2023 if (!forceOpen && (State & CMDLINE)) {
2024 // HACK! If Vim is in command line mode then the files names
2025 // should be added to the command line, instead of opening the
2026 // files in tabs (unless forceOpen is set). This is taken care of by
2027 // gui_handle_drop().
2028 char_u **fnames = (char_u **)alloc(n * sizeof(char_u *));
2031 while (bytes < end && i < n) {
2032 int len = *((int*)bytes); bytes += sizeof(int);
2033 char_u *s = (char_u*)bytes;
2035 s = CONVERT_FROM_UTF8(s);
2037 fnames[i++] = vim_strsave(s);
2039 CONVERT_FROM_UTF8_FREE(s);
2044 // NOTE! This function will free 'fnames'.
2045 // HACK! It is assumed that the 'x' and 'y' arguments are
2046 // unused when in command line mode.
2047 gui_handle_drop(0, 0, 0, fnames, i < n ? i : n);
2050 // HACK! I'm not sure how to get Vim to open a list of files in
2051 // tabs, so instead I create a ':tab drop' command with all the
2052 // files to open and execute it.
2053 NSMutableString *cmd = [NSMutableString stringWithString:@":tab drop"];
2056 for (i = 0; i < n && bytes < end; ++i) {
2057 int len = *((int*)bytes); bytes += sizeof(int);
2058 NSString *file = [NSString stringWithUTF8String:bytes];
2059 file = [file stringByEscapingSpecialFilenameCharacters];
2062 [cmd appendString:@" "];
2063 [cmd appendString:file];
2066 // By going to the last tabpage we ensure that the new tabs will
2067 // appear last (if this call is left out, the taborder becomes
2071 char_u *s = (char_u*)[cmd UTF8String];
2073 s = CONVERT_FROM_UTF8(s);
2077 CONVERT_FROM_UTF8_FREE(s);
2080 // Force screen redraw (does it have to be this complicated?).
2081 // (This code was taken from the end of gui_handle_drop().)
2082 update_screen(NOT_VALID);
2085 gui_update_cursor(FALSE, FALSE);
2092 - (void)handleDropString:(NSData *)data
2097 char_u dropkey[3] = { CSI, KS_EXTRA, (char_u)KE_DROP };
2098 const void *bytes = [data bytes];
2099 int len = *((int*)bytes); bytes += sizeof(int);
2100 NSMutableString *string = [NSMutableString stringWithUTF8String:bytes];
2102 // Replace unrecognized end-of-line sequences with \x0a (line feed).
2103 NSRange range = { 0, [string length] };
2104 unsigned n = [string replaceOccurrencesOfString:@"\x0d\x0a"
2105 withString:@"\x0a" options:0
2108 n = [string replaceOccurrencesOfString:@"\x0d" withString:@"\x0a"
2109 options:0 range:range];
2112 len = [string lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
2113 char_u *s = (char_u*)[string UTF8String];
2115 if (input_conv.vc_type != CONV_NONE)
2116 s = string_convert(&input_conv, s, &len);
2118 dnd_yank_drag_data(s, len);
2120 if (input_conv.vc_type != CONV_NONE)
2123 add_to_input_buf(dropkey, sizeof(dropkey));
2127 - (void)handleOdbEdit:(NSData *)data
2129 #ifdef FEAT_ODB_EDITOR
2130 const void *bytes = [data bytes];
2132 OSType serverID = *((OSType*)bytes); bytes += sizeof(OSType);
2134 char_u *path = NULL;
2135 int pathLen = *((int*)bytes); bytes += sizeof(int);
2137 path = (char_u*)bytes;
2140 path = CONVERT_FROM_UTF8(path);
2144 NSAppleEventDescriptor *token = nil;
2145 DescType tokenType = *((DescType*)bytes); bytes += sizeof(DescType);
2146 int descLen = *((int*)bytes); bytes += sizeof(int);
2148 token = [NSAppleEventDescriptor descriptorWithDescriptorType:tokenType
2154 unsigned i, numFiles = *((unsigned*)bytes); bytes += sizeof(unsigned);
2155 for (i = 0; i < numFiles; ++i) {
2156 int len = *((int*)bytes); bytes += sizeof(int);
2157 char_u *filename = (char_u*)bytes;
2159 filename = CONVERT_FROM_UTF8(filename);
2161 buf_T *buf = buflist_findname(filename);
2163 if (buf->b_odb_token) {
2164 [(NSAppleEventDescriptor*)(buf->b_odb_token) release];
2165 buf->b_odb_token = NULL;
2168 if (buf->b_odb_fname) {
2169 vim_free(buf->b_odb_fname);
2170 buf->b_odb_fname = NULL;
2173 buf->b_odb_server_id = serverID;
2176 buf->b_odb_token = [token retain];
2178 buf->b_odb_fname = vim_strsave(path);
2180 NSLog(@"WARNING: Could not find buffer '%s' for ODB editing.",
2185 CONVERT_FROM_UTF8_FREE(filename);
2190 CONVERT_FROM_UTF8_FREE(path);
2192 #endif // FEAT_ODB_EDITOR
2195 - (void)handleXcodeMod:(NSData *)data
2198 const void *bytes = [data bytes];
2199 DescType type = *((DescType*)bytes); bytes += sizeof(DescType);
2200 unsigned len = *((unsigned*)bytes); bytes += sizeof(unsigned);
2204 NSAppleEventDescriptor *replyEvent = [NSAppleEventDescriptor
2205 descriptorWithDescriptorType:type
2211 - (BOOL)checkForModifiedBuffers
2214 for (buf = firstbuf; buf != NULL; buf = buf->b_next) {
2215 if (bufIsChanged(buf)) {
2223 - (void)addInput:(NSString *)input
2225 char_u *s = (char_u*)[input UTF8String];
2228 s = CONVERT_FROM_UTF8(s);
2231 server_to_input_buf(s);
2234 CONVERT_FROM_UTF8_FREE(s);
2238 @end // MMBackend (Private)
2243 @implementation MMBackend (ClientServer)
2245 - (NSString *)connectionNameFromServerName:(NSString *)name
2247 NSString *bundleIdentifier = [[NSBundle mainBundle] bundleIdentifier];
2249 return [[NSString stringWithFormat:@"%@.%@", bundleIdentifier, name]
2253 - (NSConnection *)connectionForServerName:(NSString *)name
2255 // TODO: Try 'name%d' if 'name' fails.
2256 NSString *connName = [self connectionNameFromServerName:name];
2257 NSConnection *svrConn = [connectionNameDict objectForKey:connName];
2260 svrConn = [NSConnection connectionWithRegisteredName:connName
2262 // Try alternate server...
2263 if (!svrConn && alternateServerName) {
2264 //NSLog(@" trying to connect to alternate server: %@",
2265 // alternateServerName);
2266 connName = [self connectionNameFromServerName:alternateServerName];
2267 svrConn = [NSConnection connectionWithRegisteredName:connName
2271 // Try looking for alternate servers...
2273 //NSLog(@" looking for alternate servers...");
2274 NSString *alt = [self alternateServerNameForName:name];
2275 if (alt != alternateServerName) {
2276 //NSLog(@" found alternate server: %@", string);
2277 [alternateServerName release];
2278 alternateServerName = [alt copy];
2282 // Try alternate server again...
2283 if (!svrConn && alternateServerName) {
2284 //NSLog(@" trying to connect to alternate server: %@",
2285 // alternateServerName);
2286 connName = [self connectionNameFromServerName:alternateServerName];
2287 svrConn = [NSConnection connectionWithRegisteredName:connName
2292 [connectionNameDict setObject:svrConn forKey:connName];
2294 //NSLog(@"Adding %@ as connection observer for %@", self, svrConn);
2295 [[NSNotificationCenter defaultCenter] addObserver:self
2296 selector:@selector(serverConnectionDidDie:)
2297 name:NSConnectionDidDieNotification object:svrConn];
2304 - (NSConnection *)connectionForServerPort:(int)port
2307 NSEnumerator *e = [connectionNameDict objectEnumerator];
2309 while ((conn = [e nextObject])) {
2310 // HACK! Assume connection uses mach ports.
2311 if (port == [(NSMachPort*)[conn sendPort] machPort])
2318 - (void)serverConnectionDidDie:(NSNotification *)notification
2320 //NSLog(@"%s%@", _cmd, notification);
2322 NSConnection *svrConn = [notification object];
2324 //NSLog(@"Removing %@ as connection observer from %@", self, svrConn);
2325 [[NSNotificationCenter defaultCenter]
2327 name:NSConnectionDidDieNotification
2330 [connectionNameDict removeObjectsForKeys:
2331 [connectionNameDict allKeysForObject:svrConn]];
2333 // HACK! Assume connection uses mach ports.
2334 int port = [(NSMachPort*)[svrConn sendPort] machPort];
2335 NSNumber *key = [NSNumber numberWithInt:port];
2337 [clientProxyDict removeObjectForKey:key];
2338 [serverReplyDict removeObjectForKey:key];
2341 - (void)addClient:(NSDistantObject *)client
2343 NSConnection *conn = [client connectionForProxy];
2344 // HACK! Assume connection uses mach ports.
2345 int port = [(NSMachPort*)[conn sendPort] machPort];
2346 NSNumber *key = [NSNumber numberWithInt:port];
2348 if (![clientProxyDict objectForKey:key]) {
2349 [client setProtocolForProxy:@protocol(MMVimClientProtocol)];
2350 [clientProxyDict setObject:client forKey:key];
2353 // NOTE: 'clientWindow' is a global variable which is used by <client>
2354 clientWindow = port;
2357 - (NSString *)alternateServerNameForName:(NSString *)name
2359 if (!(name && [name length] > 0))
2362 // Only look for alternates if 'name' doesn't end in a digit.
2363 unichar lastChar = [name characterAtIndex:[name length]-1];
2364 if (lastChar >= '0' && lastChar <= '9')
2367 // Look for alternates among all current servers.
2368 NSArray *list = [self serverList];
2369 if (!(list && [list count] > 0))
2372 // Filter out servers starting with 'name' and ending with a number. The
2373 // (?i) pattern ensures that the match is case insensitive.
2374 NSString *pat = [NSString stringWithFormat:@"(?i)%@[0-9]+\\z", name];
2375 NSPredicate *pred = [NSPredicate predicateWithFormat:
2376 @"SELF MATCHES %@", pat];
2377 list = [list filteredArrayUsingPredicate:pred];
2378 if ([list count] > 0) {
2379 list = [list sortedArrayUsingSelector:@selector(serverNameCompare:)];
2380 return [list objectAtIndex:0];
2386 @end // MMBackend (ClientServer)
2391 @implementation NSString (MMServerNameCompare)
2392 - (NSComparisonResult)serverNameCompare:(NSString *)string
2394 return [self compare:string
2395 options:NSCaseInsensitiveSearch|NSNumericSearch];
2402 static int eventModifierFlagsToVimModMask(int modifierFlags)
2406 if (modifierFlags & NSShiftKeyMask)
2407 modMask |= MOD_MASK_SHIFT;
2408 if (modifierFlags & NSControlKeyMask)
2409 modMask |= MOD_MASK_CTRL;
2410 if (modifierFlags & NSAlternateKeyMask)
2411 modMask |= MOD_MASK_ALT;
2412 if (modifierFlags & NSCommandKeyMask)
2413 modMask |= MOD_MASK_CMD;
2418 static int eventModifierFlagsToVimMouseModMask(int modifierFlags)
2422 if (modifierFlags & NSShiftKeyMask)
2423 modMask |= MOUSE_SHIFT;
2424 if (modifierFlags & NSControlKeyMask)
2425 modMask |= MOUSE_CTRL;
2426 if (modifierFlags & NSAlternateKeyMask)
2427 modMask |= MOUSE_ALT;
2432 static int eventButtonNumberToVimMouseButton(int buttonNumber)
2434 static int mouseButton[] = { MOUSE_LEFT, MOUSE_RIGHT, MOUSE_MIDDLE };
2436 return (buttonNumber >= 0 && buttonNumber < 3)
2437 ? mouseButton[buttonNumber] : -1;