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);
60 static id evalExprCocoa(NSString * expr, NSString ** errstr);
68 static NSString *MMSymlinkWarningString =
69 @"\n\n\tMost likely this is because you have symlinked directly to\n"
70 "\tthe Vim binary, which Cocoa does not allow. Please use an\n"
71 "\talias or the mvim shell script instead. If you have not used\n"
72 "\ta symlink, then your MacVim.app bundle is incomplete.\n\n";
76 @interface NSString (MMServerNameCompare)
77 - (NSComparisonResult)serverNameCompare:(NSString *)string;
83 @interface MMBackend (Private)
84 - (void)waitForDialogReturn;
85 - (void)queueVimStateMessage;
86 - (void)processInputQueue;
87 - (void)handleInputEvent:(int)msgid data:(NSData *)data;
88 + (NSDictionary *)specialKeys;
89 - (void)handleInsertText:(NSData *)data;
90 - (void)handleKeyDown:(NSString *)key modifiers:(int)mods;
91 - (void)queueMessage:(int)msgid data:(NSData *)data;
92 - (void)connectionDidDie:(NSNotification *)notification;
93 - (void)blinkTimerFired:(NSTimer *)timer;
94 - (void)focusChange:(BOOL)on;
95 - (void)handleToggleToolbar;
96 - (void)handleScrollbarEvent:(NSData *)data;
97 - (void)handleSetFont:(NSData *)data;
98 - (void)handleDropFiles:(NSData *)data;
99 - (void)handleDropString:(NSData *)data;
100 - (void)handleOdbEdit:(NSData *)data;
101 - (void)handleXcodeMod:(NSData *)data;
102 - (BOOL)checkForModifiedBuffers;
103 - (void)addInput:(NSString *)input;
108 @interface MMBackend (ClientServer)
109 - (NSString *)connectionNameFromServerName:(NSString *)name;
110 - (NSConnection *)connectionForServerName:(NSString *)name;
111 - (NSConnection *)connectionForServerPort:(int)port;
112 - (void)serverConnectionDidDie:(NSNotification *)notification;
113 - (void)addClient:(NSDistantObject *)client;
114 - (NSString *)alternateServerNameForName:(NSString *)name;
119 @implementation MMBackend
121 + (MMBackend *)sharedInstance
123 static MMBackend *singleton = nil;
124 return singleton ? singleton : (singleton = [MMBackend new]);
130 if (!self) return nil;
132 fontContainerRef = loadFonts();
134 outputQueue = [[NSMutableArray alloc] init];
135 inputQueue = [[NSMutableArray alloc] init];
136 drawData = [[NSMutableData alloc] initWithCapacity:1024];
137 connectionNameDict = [[NSMutableDictionary alloc] init];
138 clientProxyDict = [[NSMutableDictionary alloc] init];
139 serverReplyDict = [[NSMutableDictionary alloc] init];
141 NSBundle *mainBundle = [NSBundle mainBundle];
142 NSString *path = [mainBundle pathForResource:@"Colors" ofType:@"plist"];
144 colorDict = [[NSDictionary dictionaryWithContentsOfFile:path] retain];
146 path = [mainBundle pathForResource:@"SystemColors" ofType:@"plist"];
148 sysColorDict = [[NSDictionary dictionaryWithContentsOfFile:path]
151 path = [mainBundle pathForResource:@"Actions" ofType:@"plist"];
153 actionDict = [[NSDictionary dictionaryWithContentsOfFile:path] retain];
155 if (!(colorDict && sysColorDict && actionDict))
156 NSLog(@"ERROR: Failed to load dictionaries.%@",
157 MMSymlinkWarningString);
164 //NSLog(@"%@ %s", [self className], _cmd);
165 [[NSNotificationCenter defaultCenter] removeObserver:self];
167 [oldWideFont release]; oldWideFont = nil;
168 [blinkTimer release]; blinkTimer = nil;
169 [alternateServerName release]; alternateServerName = nil;
170 [serverReplyDict release]; serverReplyDict = nil;
171 [clientProxyDict release]; clientProxyDict = nil;
172 [connectionNameDict release]; connectionNameDict = nil;
173 [inputQueue release]; inputQueue = nil;
174 [outputQueue release]; outputQueue = nil;
175 [drawData release]; drawData = nil;
176 [frontendProxy release]; frontendProxy = nil;
177 [connection release]; connection = nil;
178 [actionDict release]; actionDict = nil;
179 [sysColorDict release]; sysColorDict = nil;
180 [colorDict release]; colorDict = nil;
185 - (void)setBackgroundColor:(int)color
187 backgroundColor = MM_COLOR_WITH_TRANSP(color,p_transp);
190 - (void)setForegroundColor:(int)color
192 foregroundColor = MM_COLOR(color);
195 - (void)setSpecialColor:(int)color
197 specialColor = MM_COLOR(color);
200 - (void)setDefaultColorsBackground:(int)bg foreground:(int)fg
202 defaultBackgroundColor = MM_COLOR_WITH_TRANSP(bg,p_transp);
203 defaultForegroundColor = MM_COLOR(fg);
205 NSMutableData *data = [NSMutableData data];
207 [data appendBytes:&defaultBackgroundColor length:sizeof(unsigned)];
208 [data appendBytes:&defaultForegroundColor length:sizeof(unsigned)];
210 [self queueMessage:SetDefaultColorsMsgID data:data];
213 - (NSConnection *)connection
216 // NOTE! If the name of the connection changes here it must also be
217 // updated in MMAppController.m.
218 NSString *name = [NSString stringWithFormat:@"%@-connection",
219 [[NSBundle mainBundle] bundleIdentifier]];
221 connection = [NSConnection connectionWithRegisteredName:name host:nil];
225 // NOTE: 'connection' may be nil here.
229 - (NSDictionary *)actionDict
234 - (void)queueMessage:(int)msgid properties:(NSDictionary *)props
236 [self queueMessage:msgid data:[props dictionaryAsData]];
241 if (![self connection]) {
242 NSBundle *mainBundle = [NSBundle mainBundle];
247 // Launch MacVim using Launch Services (NSWorkspace would be nicer, but
248 // the API to pass Apple Event parameters is broken on 10.4).
249 NSString *path = [mainBundle bundlePath];
250 status = FSPathMakeRef((const UInt8 *)[path UTF8String], &ref, NULL);
251 if (noErr == status) {
252 // Pass parameter to the 'Open' Apple Event that tells MacVim not
253 // to open an untitled window.
254 NSAppleEventDescriptor *desc =
255 [NSAppleEventDescriptor recordDescriptor];
256 [desc setParamDescriptor:
257 [NSAppleEventDescriptor descriptorWithBoolean:NO]
258 forKeyword:keyMMUntitledWindow];
260 LSLaunchFSRefSpec spec = { &ref, 0, NULL, [desc aeDesc],
261 kLSLaunchDefaults, NULL };
262 status = LSOpenFromRefSpec(&spec, NULL);
265 if (noErr != status) {
266 NSLog(@"ERROR: Failed to launch MacVim (path=%@).%@",
267 path, MMSymlinkWarningString);
271 // Launch MacVim using NSTask. For some reason the above code using
272 // Launch Services sometimes fails on LSOpenFromRefSpec() (when it
273 // fails, the dock icon starts bouncing and never stops). It seems
274 // like rebuilding the Launch Services database takes care of this
275 // problem, but the NSTask way seems more stable so stick with it.
277 // NOTE! Using NSTask to launch the GUI has the negative side-effect
278 // that the GUI won't be activated (or raised) so there is a hack in
279 // MMAppController which raises the app when a new window is opened.
280 NSMutableArray *args = [NSMutableArray arrayWithObjects:
281 [NSString stringWithFormat:@"-%@", MMNoWindowKey], @"yes", nil];
282 NSString *exeName = [[mainBundle infoDictionary]
283 objectForKey:@"CFBundleExecutable"];
284 NSString *path = [mainBundle pathForAuxiliaryExecutable:exeName];
286 NSLog(@"ERROR: Could not find MacVim executable in bundle.%@",
287 MMSymlinkWarningString);
291 [NSTask launchedTaskWithLaunchPath:path arguments:args];
294 // HACK! Poll the mach bootstrap server until it returns a valid
295 // connection to detect that MacVim has finished launching. Also set a
296 // time-out date so that we don't get stuck doing this forever.
297 NSDate *timeOutDate = [NSDate dateWithTimeIntervalSinceNow:10];
298 while (![self connection] &&
299 NSOrderedDescending == [timeOutDate compare:[NSDate date]])
300 [[NSRunLoop currentRunLoop]
301 runMode:NSDefaultRunLoopMode
302 beforeDate:[NSDate dateWithTimeIntervalSinceNow:1]];
304 // NOTE: [self connection] will set 'connection' as a side-effect.
306 NSLog(@"WARNING: Timed-out waiting for GUI to launch.");
313 [[NSNotificationCenter defaultCenter] addObserver:self
314 selector:@selector(connectionDidDie:)
315 name:NSConnectionDidDieNotification object:connection];
317 id proxy = [connection rootProxy];
318 [proxy setProtocolForProxy:@protocol(MMAppProtocol)];
320 int pid = [[NSProcessInfo processInfo] processIdentifier];
322 frontendProxy = [proxy connectBackend:self pid:pid];
324 [frontendProxy retain];
325 [frontendProxy setProtocolForProxy:@protocol(MMAppProtocol)];
329 @catch (NSException *e) {
330 NSLog(@"Exception caught when trying to connect backend: \"%@\"", e);
336 - (BOOL)openVimWindow
338 [self queueMessage:OpenVimWindowMsgID data:nil];
344 int type = ClearAllDrawType;
346 // Any draw commands in queue are effectively obsolete since this clearAll
347 // will negate any effect they have, therefore we may as well clear the
349 [drawData setLength:0];
351 [drawData appendBytes:&type length:sizeof(int)];
354 - (void)clearBlockFromRow:(int)row1 column:(int)col1
355 toRow:(int)row2 column:(int)col2
357 int type = ClearBlockDrawType;
359 [drawData appendBytes:&type length:sizeof(int)];
361 [drawData appendBytes:&defaultBackgroundColor length:sizeof(unsigned)];
362 [drawData appendBytes:&row1 length:sizeof(int)];
363 [drawData appendBytes:&col1 length:sizeof(int)];
364 [drawData appendBytes:&row2 length:sizeof(int)];
365 [drawData appendBytes:&col2 length:sizeof(int)];
368 - (void)deleteLinesFromRow:(int)row count:(int)count
369 scrollBottom:(int)bottom left:(int)left right:(int)right
371 int type = DeleteLinesDrawType;
373 [drawData appendBytes:&type length:sizeof(int)];
375 [drawData appendBytes:&defaultBackgroundColor length:sizeof(unsigned)];
376 [drawData appendBytes:&row length:sizeof(int)];
377 [drawData appendBytes:&count length:sizeof(int)];
378 [drawData appendBytes:&bottom length:sizeof(int)];
379 [drawData appendBytes:&left length:sizeof(int)];
380 [drawData appendBytes:&right length:sizeof(int)];
383 - (void)drawString:(char*)s length:(int)len row:(int)row column:(int)col
384 cells:(int)cells flags:(int)flags
386 if (len <= 0 || cells <= 0) return;
388 int type = DrawStringDrawType;
390 [drawData appendBytes:&type length:sizeof(int)];
392 [drawData appendBytes:&backgroundColor length:sizeof(unsigned)];
393 [drawData appendBytes:&foregroundColor length:sizeof(unsigned)];
394 [drawData appendBytes:&specialColor length:sizeof(unsigned)];
395 [drawData appendBytes:&row length:sizeof(int)];
396 [drawData appendBytes:&col length:sizeof(int)];
397 [drawData appendBytes:&cells length:sizeof(int)];
398 [drawData appendBytes:&flags length:sizeof(int)];
399 [drawData appendBytes:&len length:sizeof(int)];
400 [drawData appendBytes:s length:len];
403 - (void)insertLinesFromRow:(int)row count:(int)count
404 scrollBottom:(int)bottom left:(int)left right:(int)right
406 int type = InsertLinesDrawType;
408 [drawData appendBytes:&type length:sizeof(int)];
410 [drawData appendBytes:&defaultBackgroundColor length:sizeof(unsigned)];
411 [drawData appendBytes:&row length:sizeof(int)];
412 [drawData appendBytes:&count length:sizeof(int)];
413 [drawData appendBytes:&bottom length:sizeof(int)];
414 [drawData appendBytes:&left length:sizeof(int)];
415 [drawData appendBytes:&right length:sizeof(int)];
418 - (void)drawCursorAtRow:(int)row column:(int)col shape:(int)shape
419 fraction:(int)percent color:(int)color
421 int type = DrawCursorDrawType;
422 unsigned uc = MM_COLOR(color);
424 [drawData appendBytes:&type length:sizeof(int)];
426 [drawData appendBytes:&uc length:sizeof(unsigned)];
427 [drawData appendBytes:&row length:sizeof(int)];
428 [drawData appendBytes:&col length:sizeof(int)];
429 [drawData appendBytes:&shape length:sizeof(int)];
430 [drawData appendBytes:&percent length:sizeof(int)];
433 - (void)drawInvertedRectAtRow:(int)row column:(int)col numRows:(int)nr
434 numColumns:(int)nc invert:(int)invert
436 int type = DrawInvertedRectDrawType;
437 [drawData appendBytes:&type length:sizeof(int)];
439 [drawData appendBytes:&row length:sizeof(int)];
440 [drawData appendBytes:&col length:sizeof(int)];
441 [drawData appendBytes:&nr length:sizeof(int)];
442 [drawData appendBytes:&nc length:sizeof(int)];
443 [drawData appendBytes:&invert length:sizeof(int)];
448 // Tend to the run loop, returning immediately if there are no events
450 [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
451 beforeDate:[NSDate distantPast]];
454 // Keyboard and mouse input is handled directly, other input is queued and
455 // processed here. This call may enter a blocking loop.
456 if ([inputQueue count] > 0)
457 [self processInputQueue];
461 - (void)flushQueue:(BOOL)force
463 // NOTE! This method gets called a lot; if we were to flush every time it
464 // got called MacVim would feel unresponsive. So there is a time out which
465 // ensures that the queue isn't flushed too often.
466 if (!force && lastFlushDate
467 && -[lastFlushDate timeIntervalSinceNow] < MMFlushTimeoutInterval
468 && [drawData length] < MMFlushQueueLenHint)
471 if ([drawData length] > 0) {
472 // HACK! Detect changes to 'guifontwide'.
473 if (gui.wide_font != (GuiFont)oldWideFont) {
474 [oldWideFont release];
475 oldWideFont = [(NSFont*)gui.wide_font retain];
476 [self setWideFont:oldWideFont];
479 int type = SetCursorPosDrawType;
480 [drawData appendBytes:&type length:sizeof(type)];
481 [drawData appendBytes:&gui.row length:sizeof(gui.row)];
482 [drawData appendBytes:&gui.col length:sizeof(gui.col)];
484 [self queueMessage:BatchDrawMsgID data:[drawData copy]];
485 [drawData setLength:0];
488 if ([outputQueue count] > 0 || force) {
489 // When 'force' is set we always update the Vim state to ensure that
490 // MacVim has a copy of the latest state (since 'force' is typically
491 // set just before Vim takes a nap whilst waiting for input).
492 [self queueVimStateMessage];
495 [frontendProxy processCommandQueue:outputQueue];
497 @catch (NSException *e) {
498 NSLog(@"Exception caught when processing command queue: \"%@\"", e);
501 [outputQueue removeAllObjects];
503 [lastFlushDate release];
504 lastFlushDate = [[NSDate date] retain];
508 - (BOOL)waitForInput:(int)milliseconds
510 //NSLog(@"|ENTER| %s%d", _cmd, milliseconds);
512 // Only start the run loop if the input queue is empty, otherwise process
513 // the input first so that the input on queue isn't delayed.
514 if ([inputQueue count]) {
517 NSDate *date = milliseconds > 0 ?
518 [NSDate dateWithTimeIntervalSinceNow:.001*milliseconds] :
519 [NSDate distantFuture];
521 [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
525 // I know of no way to figure out if the run loop exited because input was
526 // found or because of a time out, so I need to manually indicate when
527 // input was received in processInput:data: and then reset it every time
529 BOOL yn = inputReceived;
532 // Keyboard and mouse input is handled directly, other input is queued and
533 // processed here. This call may enter a blocking loop.
534 if ([inputQueue count] > 0)
535 [self processInputQueue];
537 //NSLog(@"|LEAVE| %s input=%d", _cmd, yn);
543 #ifdef MAC_CLIENTSERVER
544 // The default connection is used for the client/server code.
545 [[NSConnection defaultConnection] setRootObject:nil];
546 [[NSConnection defaultConnection] invalidate];
549 // By invalidating the NSConnection the MMWindowController immediately
550 // finds out that the connection is down and as a result
551 // [MMWindowController connectionDidDie:] is invoked.
552 //NSLog(@"%@ %s", [self className], _cmd);
553 [[NSNotificationCenter defaultCenter] removeObserver:self];
554 [connection invalidate];
556 if (fontContainerRef) {
557 ATSFontDeactivate(fontContainerRef, NULL, kATSOptionFlagsDefault);
558 fontContainerRef = 0;
563 - (void)selectTab:(int)index
565 //NSLog(@"%s%d", _cmd, index);
568 NSData *data = [NSData dataWithBytes:&index length:sizeof(int)];
569 [self queueMessage:SelectTabMsgID data:data];
574 //NSLog(@"%s", _cmd);
576 NSMutableData *data = [NSMutableData data];
578 int idx = tabpage_index(curtab) - 1;
579 [data appendBytes:&idx length:sizeof(int)];
582 for (tp = first_tabpage; tp != NULL; tp = tp->tp_next) {
583 // This function puts the label of the tab in the global 'NameBuff'.
584 get_tabline_label(tp, FALSE);
585 char_u *s = NameBuff;
587 if (len <= 0) continue;
590 s = CONVERT_TO_UTF8(s);
593 // Count the number of windows in the tabpage.
594 //win_T *wp = tp->tp_firstwin;
596 //for (wincount = 0; wp != NULL; wp = wp->w_next, ++wincount);
598 //[data appendBytes:&wincount length:sizeof(int)];
599 [data appendBytes:&len length:sizeof(int)];
600 [data appendBytes:s length:len];
603 CONVERT_TO_UTF8_FREE(s);
607 [self queueMessage:UpdateTabBarMsgID data:data];
610 - (BOOL)tabBarVisible
612 return tabBarVisible;
615 - (void)showTabBar:(BOOL)enable
617 tabBarVisible = enable;
619 int msgid = enable ? ShowTabBarMsgID : HideTabBarMsgID;
620 [self queueMessage:msgid data:nil];
623 - (void)setRows:(int)rows columns:(int)cols
625 //NSLog(@"[VimTask] setRows:%d columns:%d", rows, cols);
627 int dim[] = { rows, cols };
628 NSData *data = [NSData dataWithBytes:&dim length:2*sizeof(int)];
630 [self queueMessage:SetTextDimensionsMsgID data:data];
633 - (void)setWindowTitle:(char *)title
635 NSMutableData *data = [NSMutableData data];
636 int len = strlen(title);
637 if (len <= 0) return;
639 [data appendBytes:&len length:sizeof(int)];
640 [data appendBytes:title length:len];
642 [self queueMessage:SetWindowTitleMsgID data:data];
645 - (void)setDocumentFilename:(char *)filename
647 NSMutableData *data = [NSMutableData data];
648 int len = filename ? strlen(filename) : 0;
650 [data appendBytes:&len length:sizeof(int)];
652 [data appendBytes:filename length:len];
654 [self queueMessage:SetDocumentFilenameMsgID data:data];
657 - (char *)browseForFileWithAttributes:(NSDictionary *)attr
662 [frontendProxy showSavePanelWithAttributes:attr];
664 [self waitForDialogReturn];
666 if (dialogReturn && [dialogReturn isKindOfClass:[NSString class]]) {
667 char_u *ret = (char_u*)[dialogReturn UTF8String];
669 ret = CONVERT_FROM_UTF8(ret);
671 s = vim_strsave(ret);
673 CONVERT_FROM_UTF8_FREE(ret);
677 [dialogReturn release]; dialogReturn = nil;
679 @catch (NSException *e) {
680 NSLog(@"Exception caught when showing save panel: \"%@\"", e);
686 - (oneway void)setDialogReturn:(in bycopy id)obj
688 // NOTE: This is called by
689 // - [MMVimController panelDidEnd:::], and
690 // - [MMVimController alertDidEnd:::],
691 // to indicate that a save/open panel or alert has finished.
693 // We want to distinguish between "no dialog return yet" and "dialog
694 // returned nothing". The former can be tested with dialogReturn == nil,
695 // the latter with dialogReturn == [NSNull null].
696 if (!obj) obj = [NSNull null];
698 if (obj != dialogReturn) {
699 [dialogReturn release];
700 dialogReturn = [obj retain];
704 - (int)showDialogWithAttributes:(NSDictionary *)attr textField:(char *)txtfield
709 [frontendProxy presentDialogWithAttributes:attr];
711 [self waitForDialogReturn];
713 if (dialogReturn && [dialogReturn isKindOfClass:[NSArray class]]
714 && [dialogReturn count]) {
715 retval = [[dialogReturn objectAtIndex:0] intValue];
716 if (txtfield && [dialogReturn count] > 1) {
717 NSString *retString = [dialogReturn objectAtIndex:1];
718 char_u *ret = (char_u*)[retString UTF8String];
720 ret = CONVERT_FROM_UTF8(ret);
722 vim_strncpy((char_u*)txtfield, ret, IOSIZE - 1);
724 CONVERT_FROM_UTF8_FREE(ret);
729 [dialogReturn release]; dialogReturn = nil;
731 @catch (NSException *e) {
732 NSLog(@"Exception caught while showing alert dialog: \"%@\"", e);
738 - (void)showToolbar:(int)enable flags:(int)flags
740 NSMutableData *data = [NSMutableData data];
742 [data appendBytes:&enable length:sizeof(int)];
743 [data appendBytes:&flags length:sizeof(int)];
745 [self queueMessage:ShowToolbarMsgID data:data];
748 - (void)createScrollbarWithIdentifier:(long)ident type:(int)type
750 NSMutableData *data = [NSMutableData data];
752 [data appendBytes:&ident length:sizeof(long)];
753 [data appendBytes:&type length:sizeof(int)];
755 [self queueMessage:CreateScrollbarMsgID data:data];
758 - (void)destroyScrollbarWithIdentifier:(long)ident
760 NSMutableData *data = [NSMutableData data];
761 [data appendBytes:&ident length:sizeof(long)];
763 [self queueMessage:DestroyScrollbarMsgID data:data];
766 - (void)showScrollbarWithIdentifier:(long)ident state:(int)visible
768 NSMutableData *data = [NSMutableData data];
770 [data appendBytes:&ident length:sizeof(long)];
771 [data appendBytes:&visible length:sizeof(int)];
773 [self queueMessage:ShowScrollbarMsgID data:data];
776 - (void)setScrollbarPosition:(int)pos length:(int)len identifier:(long)ident
778 NSMutableData *data = [NSMutableData data];
780 [data appendBytes:&ident length:sizeof(long)];
781 [data appendBytes:&pos length:sizeof(int)];
782 [data appendBytes:&len length:sizeof(int)];
784 [self queueMessage:SetScrollbarPositionMsgID data:data];
787 - (void)setScrollbarThumbValue:(long)val size:(long)size max:(long)max
788 identifier:(long)ident
790 float fval = max-size+1 > 0 ? (float)val/(max-size+1) : 0;
791 float prop = (float)size/(max+1);
792 if (fval < 0) fval = 0;
793 else if (fval > 1.0f) fval = 1.0f;
794 if (prop < 0) prop = 0;
795 else if (prop > 1.0f) prop = 1.0f;
797 NSMutableData *data = [NSMutableData data];
799 [data appendBytes:&ident length:sizeof(long)];
800 [data appendBytes:&fval length:sizeof(float)];
801 [data appendBytes:&prop length:sizeof(float)];
803 [self queueMessage:SetScrollbarThumbMsgID data:data];
806 - (void)setFont:(NSFont *)font
808 NSString *fontName = [font displayName];
809 float size = [font pointSize];
810 int len = [fontName lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
812 NSMutableData *data = [NSMutableData data];
814 [data appendBytes:&size length:sizeof(float)];
815 [data appendBytes:&len length:sizeof(int)];
816 [data appendBytes:[fontName UTF8String] length:len];
818 [self queueMessage:SetFontMsgID data:data];
822 - (void)setWideFont:(NSFont *)font
824 NSString *fontName = [font displayName];
825 float size = [font pointSize];
826 int len = [fontName lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
827 NSMutableData *data = [NSMutableData data];
829 [data appendBytes:&size length:sizeof(float)];
830 [data appendBytes:&len length:sizeof(int)];
832 [data appendBytes:[fontName UTF8String] length:len];
834 [self queueMessage:SetWideFontMsgID data:data];
837 - (void)executeActionWithName:(NSString *)name
839 int len = [name lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
842 NSMutableData *data = [NSMutableData data];
844 [data appendBytes:&len length:sizeof(int)];
845 [data appendBytes:[name UTF8String] length:len];
847 [self queueMessage:ExecuteActionMsgID data:data];
851 - (void)setMouseShape:(int)shape
853 NSMutableData *data = [NSMutableData data];
854 [data appendBytes:&shape length:sizeof(int)];
855 [self queueMessage:SetMouseShapeMsgID data:data];
858 - (void)setBlinkWait:(int)wait on:(int)on off:(int)off
860 // Vim specifies times in milliseconds, whereas Cocoa wants them in
862 blinkWaitInterval = .001f*wait;
863 blinkOnInterval = .001f*on;
864 blinkOffInterval = .001f*off;
870 [blinkTimer invalidate];
871 [blinkTimer release];
875 if (blinkWaitInterval > 0 && blinkOnInterval > 0 && blinkOffInterval > 0
877 blinkState = MMBlinkStateOn;
879 [[NSTimer scheduledTimerWithTimeInterval:blinkWaitInterval
881 selector:@selector(blinkTimerFired:)
882 userInfo:nil repeats:NO] retain];
883 gui_update_cursor(TRUE, FALSE);
884 [self flushQueue:YES];
890 if (MMBlinkStateOff == blinkState) {
891 gui_update_cursor(TRUE, FALSE);
892 [self flushQueue:YES];
895 blinkState = MMBlinkStateNone;
898 - (void)adjustLinespace:(int)linespace
900 NSMutableData *data = [NSMutableData data];
901 [data appendBytes:&linespace length:sizeof(int)];
902 [self queueMessage:AdjustLinespaceMsgID data:data];
907 [self queueMessage:ActivateMsgID data:nil];
910 - (void)setPreEditRow:(int)row column:(int)col
912 NSMutableData *data = [NSMutableData data];
913 [data appendBytes:&row length:sizeof(int)];
914 [data appendBytes:&col length:sizeof(int)];
915 [self queueMessage:SetPreEditPositionMsgID data:data];
918 - (int)lookupColorWithKey:(NSString *)key
920 if (!(key && [key length] > 0))
923 NSString *stripKey = [[[[key lowercaseString]
924 stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]
925 componentsSeparatedByString:@" "]
926 componentsJoinedByString:@""];
928 if (stripKey && [stripKey length] > 0) {
929 // First of all try to lookup key in the color dictionary; note that
930 // all keys in this dictionary are lowercase with no whitespace.
931 id obj = [colorDict objectForKey:stripKey];
932 if (obj) return [obj intValue];
934 // The key was not in the dictionary; is it perhaps of the form
936 if ([stripKey length] > 1 && [stripKey characterAtIndex:0] == '#') {
937 NSScanner *scanner = [NSScanner scannerWithString:stripKey];
938 [scanner setScanLocation:1];
940 if ([scanner scanHexInt:&hex]) {
945 // As a last resort, check if it is one of the system defined colors.
946 // The keys in this dictionary are also lowercase with no whitespace.
947 obj = [sysColorDict objectForKey:stripKey];
949 NSColor *col = [NSColor performSelector:NSSelectorFromString(obj)];
952 col = [col colorUsingColorSpaceName:NSCalibratedRGBColorSpace];
953 [col getRed:&r green:&g blue:&b alpha:&a];
954 return (((int)(r*255+.5f) & 0xff) << 16)
955 + (((int)(g*255+.5f) & 0xff) << 8)
956 + ((int)(b*255+.5f) & 0xff);
961 //NSLog(@"WARNING: No color with key %@ found.", stripKey);
965 - (BOOL)hasSpecialKeyWithValue:(NSString *)value
967 NSEnumerator *e = [[MMBackend specialKeys] objectEnumerator];
970 while ((obj = [e nextObject])) {
971 if ([value isEqual:obj])
978 - (void)enterFullscreen:(int)fuoptions background:(int)bg
980 NSMutableData *data = [NSMutableData data];
981 [data appendBytes:&fuoptions length:sizeof(int)];
983 [data appendBytes:&bg length:sizeof(int)];
984 [self queueMessage:EnterFullscreenMsgID data:data];
987 - (void)leaveFullscreen
989 [self queueMessage:LeaveFullscreenMsgID data:nil];
992 - (void)setAntialias:(BOOL)antialias
994 int msgid = antialias ? EnableAntialiasMsgID : DisableAntialiasMsgID;
996 [self queueMessage:msgid data:nil];
999 - (void)updateModifiedFlag
1001 // Notify MacVim if _any_ buffer has changed from unmodified to modified or
1003 int msgid = [self checkForModifiedBuffers]
1004 ? BuffersModifiedMsgID : BuffersNotModifiedMsgID;
1006 [self queueMessage:msgid data:nil];
1009 - (oneway void)processInput:(int)msgid data:(in bycopy NSData *)data
1011 // NOTE: This method might get called whenever the run loop is tended to.
1012 // Normal keyboard and mouse input is added to input buffers, so there is
1013 // no risk in handling these events directly (they return immediately, and
1014 // do not call any other Vim functions). However, other events such
1015 // as 'VimShouldCloseMsgID' may enter blocking loops that wait for key
1016 // events which would cause this method to be called recursively. This
1017 // in turn leads to various difficulties that we do not want to have to
1018 // deal with. To avoid recursive calls here we add all events except
1019 // keyboard and mouse events to an input queue which is processed whenever
1020 // gui_mch_update() is called (see processInputQueue).
1022 //NSLog(@"%s%s", _cmd, MessageStrings[msgid]);
1024 // Don't flush too soon after receiving input or update speed will suffer.
1025 [lastFlushDate release];
1026 lastFlushDate = [[NSDate date] retain];
1028 // Handle keyboard and mouse input now. All other events are queued.
1029 if (InsertTextMsgID == msgid) {
1030 [self handleInsertText:data];
1031 } else if (KeyDownMsgID == msgid || CmdKeyMsgID == msgid) {
1033 const void *bytes = [data bytes];
1034 int mods = *((int*)bytes); bytes += sizeof(int);
1035 int len = *((int*)bytes); bytes += sizeof(int);
1036 NSString *key = [[NSString alloc] initWithBytes:bytes length:len
1037 encoding:NSUTF8StringEncoding];
1038 mods = eventModifierFlagsToVimModMask(mods);
1040 [self handleKeyDown:key modifiers:mods];
1043 } else if (ScrollWheelMsgID == msgid) {
1045 const void *bytes = [data bytes];
1047 int row = *((int*)bytes); bytes += sizeof(int);
1048 int col = *((int*)bytes); bytes += sizeof(int);
1049 int flags = *((int*)bytes); bytes += sizeof(int);
1050 float dy = *((float*)bytes); bytes += sizeof(float);
1052 int button = MOUSE_5;
1053 if (dy > 0) button = MOUSE_4;
1055 flags = eventModifierFlagsToVimMouseModMask(flags);
1057 int numLines = (int)round(dy);
1058 if (numLines < 0) numLines = -numLines;
1059 if (numLines == 0) numLines = 1;
1061 #ifdef FEAT_GUI_SCROLL_WHEEL_FORCE
1062 gui.scroll_wheel_force = numLines;
1065 gui_send_mouse_event(button, col, row, NO, flags);
1066 } else if (MouseDownMsgID == msgid) {
1068 const void *bytes = [data bytes];
1070 int row = *((int*)bytes); bytes += sizeof(int);
1071 int col = *((int*)bytes); bytes += sizeof(int);
1072 int button = *((int*)bytes); bytes += sizeof(int);
1073 int flags = *((int*)bytes); bytes += sizeof(int);
1074 int count = *((int*)bytes); bytes += sizeof(int);
1076 button = eventButtonNumberToVimMouseButton(button);
1078 flags = eventModifierFlagsToVimMouseModMask(flags);
1079 gui_send_mouse_event(button, col, row, count>1, flags);
1081 } else if (MouseUpMsgID == msgid) {
1083 const void *bytes = [data bytes];
1085 int row = *((int*)bytes); bytes += sizeof(int);
1086 int col = *((int*)bytes); bytes += sizeof(int);
1087 int flags = *((int*)bytes); bytes += sizeof(int);
1089 flags = eventModifierFlagsToVimMouseModMask(flags);
1091 gui_send_mouse_event(MOUSE_RELEASE, col, row, NO, flags);
1092 } else if (MouseDraggedMsgID == msgid) {
1094 const void *bytes = [data bytes];
1096 int row = *((int*)bytes); bytes += sizeof(int);
1097 int col = *((int*)bytes); bytes += sizeof(int);
1098 int flags = *((int*)bytes); bytes += sizeof(int);
1100 flags = eventModifierFlagsToVimMouseModMask(flags);
1102 gui_send_mouse_event(MOUSE_DRAG, col, row, NO, flags);
1103 } else if (MouseMovedMsgID == msgid) {
1104 const void *bytes = [data bytes];
1105 int row = *((int*)bytes); bytes += sizeof(int);
1106 int col = *((int*)bytes); bytes += sizeof(int);
1108 gui_mouse_moved(col, row);
1109 } else if (AddInputMsgID == msgid) {
1110 NSString *string = [[NSString alloc] initWithData:data
1111 encoding:NSUTF8StringEncoding];
1113 [self addInput:string];
1116 } else if (TerminateNowMsgID == msgid) {
1117 isTerminating = YES;
1119 // Not keyboard or mouse event, queue it and handle later.
1120 //NSLog(@"Add event %s to input event queue", MessageStrings[msgid]);
1121 [inputQueue addObject:[NSNumber numberWithInt:msgid]];
1122 [inputQueue addObject:(data ? (id)data : (id)[NSNull null])];
1125 // See waitForInput: for an explanation of this flag.
1126 inputReceived = YES;
1129 - (oneway void)processInputAndData:(in bycopy NSArray *)messages
1131 // TODO: Get rid of this method?
1132 //NSLog(@"%s%@", _cmd, messages);
1134 unsigned i, count = [messages count];
1135 for (i = 0; i < count; i += 2) {
1136 int msgid = [[messages objectAtIndex:i] intValue];
1137 id data = [messages objectAtIndex:i+1];
1138 if ([data isEqual:[NSNull null]])
1141 [self processInput:msgid data:data];
1145 - (id)evaluateExpressionCocoa:(in bycopy NSString *)expr
1146 errorString:(out bycopy NSString **)errstr
1148 return evalExprCocoa(expr, errstr);
1152 - (NSString *)evaluateExpression:(in bycopy NSString *)expr
1154 NSString *eval = nil;
1155 char_u *s = (char_u*)[expr UTF8String];
1158 s = CONVERT_FROM_UTF8(s);
1161 char_u *res = eval_client_expr_to_string(s);
1164 CONVERT_FROM_UTF8_FREE(s);
1170 s = CONVERT_TO_UTF8(s);
1172 eval = [NSString stringWithUTF8String:(char*)s];
1174 CONVERT_TO_UTF8_FREE(s);
1182 - (BOOL)starRegisterToPasteboard:(byref NSPasteboard *)pboard
1184 // TODO: This method should share code with clip_mch_request_selection().
1186 if (VIsual_active && (State & NORMAL) && clip_star.available) {
1187 // If there is no pasteboard, return YES to indicate that there is text
1192 clip_copy_selection();
1194 // Get the text to put on the pasteboard.
1195 long_u llen = 0; char_u *str = 0;
1196 int type = clip_convert_selection(&str, &llen, &clip_star);
1200 // TODO: Avoid overflow.
1201 int len = (int)llen;
1203 if (output_conv.vc_type != CONV_NONE) {
1204 char_u *conv_str = string_convert(&output_conv, str, &len);
1212 NSString *string = [[NSString alloc]
1213 initWithBytes:str length:len encoding:NSUTF8StringEncoding];
1215 NSArray *types = [NSArray arrayWithObject:NSStringPboardType];
1216 [pboard declareTypes:types owner:nil];
1217 BOOL ok = [pboard setString:string forType:NSStringPboardType];
1228 - (oneway void)addReply:(in bycopy NSString *)reply
1229 server:(in byref id <MMVimServerProtocol>)server
1231 //NSLog(@"addReply:%@ server:%@", reply, (id)server);
1233 // Replies might come at any time and in any order so we keep them in an
1234 // array inside a dictionary with the send port used as key.
1236 NSConnection *conn = [(NSDistantObject*)server connectionForProxy];
1237 // HACK! Assume connection uses mach ports.
1238 int port = [(NSMachPort*)[conn sendPort] machPort];
1239 NSNumber *key = [NSNumber numberWithInt:port];
1241 NSMutableArray *replies = [serverReplyDict objectForKey:key];
1243 replies = [NSMutableArray array];
1244 [serverReplyDict setObject:replies forKey:key];
1247 [replies addObject:reply];
1250 - (void)addInput:(in bycopy NSString *)input
1251 client:(in byref id <MMVimClientProtocol>)client
1253 //NSLog(@"addInput:%@ client:%@", input, (id)client);
1255 [self addInput:input];
1256 [self addClient:(id)client];
1258 inputReceived = YES;
1261 - (NSString *)evaluateExpression:(in bycopy NSString *)expr
1262 client:(in byref id <MMVimClientProtocol>)client
1264 [self addClient:(id)client];
1265 return [self evaluateExpression:expr];
1268 - (void)registerServerWithName:(NSString *)name
1270 NSString *svrName = name;
1271 NSConnection *svrConn = [NSConnection defaultConnection];
1274 for (i = 0; i < MMServerMax; ++i) {
1275 NSString *connName = [self connectionNameFromServerName:svrName];
1277 if ([svrConn registerName:connName]) {
1278 //NSLog(@"Registered server with name: %@", svrName);
1280 // TODO: Set request/reply time-outs to something else?
1282 // Don't wait for requests (time-out means that the message is
1284 [svrConn setRequestTimeout:0];
1285 //[svrConn setReplyTimeout:MMReplyTimeout];
1286 [svrConn setRootObject:self];
1288 char_u *s = (char_u*)[svrName UTF8String];
1290 s = CONVERT_FROM_UTF8(s);
1292 // NOTE: 'serverName' is a global variable
1293 serverName = vim_strsave(s);
1295 CONVERT_FROM_UTF8_FREE(s);
1298 set_vim_var_string(VV_SEND_SERVER, serverName, -1);
1301 need_maketitle = TRUE;
1303 [self queueMessage:SetServerNameMsgID data:
1304 [svrName dataUsingEncoding:NSUTF8StringEncoding]];
1308 svrName = [NSString stringWithFormat:@"%@%d", name, i+1];
1312 - (BOOL)sendToServer:(NSString *)name string:(NSString *)string
1313 reply:(char_u **)reply port:(int *)port expression:(BOOL)expr
1316 // NOTE: If 'name' equals 'serverName' then the request is local (client
1317 // and server are the same). This case is not handled separately, so a
1318 // connection will be set up anyway (this simplifies the code).
1320 NSConnection *conn = [self connectionForServerName:name];
1323 char_u *s = (char_u*)[name UTF8String];
1325 s = CONVERT_FROM_UTF8(s);
1327 EMSG2(_(e_noserver), s);
1329 CONVERT_FROM_UTF8_FREE(s);
1336 // HACK! Assume connection uses mach ports.
1337 *port = [(NSMachPort*)[conn sendPort] machPort];
1340 id proxy = [conn rootProxy];
1341 [proxy setProtocolForProxy:@protocol(MMVimServerProtocol)];
1345 NSString *eval = [proxy evaluateExpression:string client:self];
1348 char_u *r = (char_u*)[eval UTF8String];
1350 r = CONVERT_FROM_UTF8(r);
1352 *reply = vim_strsave(r);
1354 CONVERT_FROM_UTF8_FREE(r);
1357 *reply = vim_strsave((char_u*)_(e_invexprmsg));
1364 [proxy addInput:string client:self];
1367 @catch (NSException *e) {
1368 NSLog(@"WARNING: Caught exception in %s: \"%@\"", _cmd, e);
1375 - (NSArray *)serverList
1377 NSArray *list = nil;
1379 if ([self connection]) {
1380 id proxy = [connection rootProxy];
1381 [proxy setProtocolForProxy:@protocol(MMAppProtocol)];
1384 list = [proxy serverList];
1386 @catch (NSException *e) {
1387 NSLog(@"Exception caught when listing servers: \"%@\"", e);
1390 EMSG(_("E???: No connection to MacVim, server listing not possible."));
1396 - (NSString *)peekForReplyOnPort:(int)port
1398 //NSLog(@"%s%d", _cmd, port);
1400 NSNumber *key = [NSNumber numberWithInt:port];
1401 NSMutableArray *replies = [serverReplyDict objectForKey:key];
1402 if (replies && [replies count]) {
1403 //NSLog(@" %d replies, topmost is: %@", [replies count],
1404 // [replies objectAtIndex:0]);
1405 return [replies objectAtIndex:0];
1408 //NSLog(@" No replies");
1412 - (NSString *)waitForReplyOnPort:(int)port
1414 //NSLog(@"%s%d", _cmd, port);
1416 NSConnection *conn = [self connectionForServerPort:port];
1420 NSNumber *key = [NSNumber numberWithInt:port];
1421 NSMutableArray *replies = nil;
1422 NSString *reply = nil;
1424 // Wait for reply as long as the connection to the server is valid (unless
1425 // user interrupts wait with Ctrl-C).
1426 while (!got_int && [conn isValid] &&
1427 !(replies = [serverReplyDict objectForKey:key])) {
1428 [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
1429 beforeDate:[NSDate distantFuture]];
1433 if ([replies count] > 0) {
1434 reply = [[replies objectAtIndex:0] retain];
1435 //NSLog(@" Got reply: %@", reply);
1436 [replies removeObjectAtIndex:0];
1437 [reply autorelease];
1440 if ([replies count] == 0)
1441 [serverReplyDict removeObjectForKey:key];
1447 - (BOOL)sendReply:(NSString *)reply toPort:(int)port
1449 id client = [clientProxyDict objectForKey:[NSNumber numberWithInt:port]];
1452 //NSLog(@"sendReply:%@ toPort:%d", reply, port);
1453 [client addReply:reply server:self];
1456 @catch (NSException *e) {
1457 NSLog(@"WARNING: Exception caught in %s: \"%@\"", _cmd, e);
1460 EMSG2(_("E???: server2client failed; no client with id 0x%x"), port);
1470 @implementation MMBackend (Private)
1472 - (void)waitForDialogReturn
1474 // Keep processing the run loop until a dialog returns. To avoid getting
1475 // stuck in an endless loop (could happen if the setDialogReturn: message
1476 // was lost) we also do some paranoia checks.
1478 // Note that in Cocoa the user can still resize windows and select menu
1479 // items while a sheet is being displayed, so we can't just wait for the
1480 // first message to arrive and assume that is the setDialogReturn: call.
1482 while (nil == dialogReturn && !got_int && [connection isValid]
1484 [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
1485 beforeDate:[NSDate distantFuture]];
1487 // Search for any resize messages on the input queue. All other messages
1488 // on the input queue are dropped. The reason why we single out resize
1489 // messages is because the user may have resized the window while a sheet
1491 int i, count = [inputQueue count];
1493 id textDimData = nil;
1495 for (i = count-2; i >= 0; i -= 2) {
1496 int msgid = [[inputQueue objectAtIndex:i] intValue];
1497 if (SetTextDimensionsMsgID == msgid) {
1498 textDimData = [[inputQueue objectAtIndex:i+1] retain];
1504 [inputQueue removeAllObjects];
1507 [inputQueue addObject:
1508 [NSNumber numberWithInt:SetTextDimensionsMsgID]];
1509 [inputQueue addObject:textDimData];
1510 [textDimData release];
1515 - (void)queueVimStateMessage
1517 // NOTE: This is the place to add Vim state that needs to be accessed from
1518 // MacVim. Do not add state that could potentially require lots of memory
1519 // since this message gets sent each time the output queue is forcibly
1520 // flushed (e.g. storing the currently selected text would be a bad idea).
1521 // We take this approach of "pushing" the state to MacVim to avoid having
1522 // to make synchronous calls from MacVim to Vim in order to get state.
1524 NSDictionary *vimState = [NSDictionary dictionaryWithObjectsAndKeys:
1525 [[NSFileManager defaultManager] currentDirectoryPath], @"pwd",
1526 [NSNumber numberWithInt:p_mh], @"p_mh",
1529 [self queueMessage:SetVimStateMsgID data:[vimState dictionaryAsData]];
1532 - (void)processInputQueue
1534 if ([inputQueue count] == 0) return;
1536 // NOTE: One of the input events may cause this method to be called
1537 // recursively, so copy the input queue to a local variable and clear it
1538 // before starting to process input events (otherwise we could get stuck in
1539 // an endless loop).
1540 NSArray *q = [inputQueue copy];
1541 unsigned i, count = [q count];
1543 [inputQueue removeAllObjects];
1545 for (i = 0; i < count-1; i += 2) {
1546 int msgid = [[q objectAtIndex:i] intValue];
1547 id data = [q objectAtIndex:i+1];
1548 if ([data isEqual:[NSNull null]])
1551 //NSLog(@"(%d) %s:%s", i, _cmd, MessageStrings[msgid]);
1552 [self handleInputEvent:msgid data:data];
1556 //NSLog(@"Clear input event queue");
1559 - (void)handleInputEvent:(int)msgid data:(NSData *)data
1561 // NOTE: Be careful with what you do in this method. Ideally, a message
1562 // should be handled by adding something to the input buffer and returning
1563 // immediately. If you call a Vim function then it should not enter a loop
1564 // waiting for key presses or in any other way block the process. The
1565 // reason for this being that only one message can be processed at a time,
1566 // so if another message is received while processing, then the new message
1567 // is dropped. See also the comment in processInput:data:.
1569 //NSLog(@"%s%s", _cmd, MessageStrings[msgid]);
1571 if (SelectTabMsgID == msgid) {
1573 const void *bytes = [data bytes];
1574 int idx = *((int*)bytes) + 1;
1575 //NSLog(@"Selecting tab %d", idx);
1576 send_tabline_event(idx);
1577 } else if (CloseTabMsgID == msgid) {
1579 const void *bytes = [data bytes];
1580 int idx = *((int*)bytes) + 1;
1581 //NSLog(@"Closing tab %d", idx);
1582 send_tabline_menu_event(idx, TABLINE_MENU_CLOSE);
1583 } else if (AddNewTabMsgID == msgid) {
1584 //NSLog(@"Adding new tab");
1585 send_tabline_menu_event(0, TABLINE_MENU_NEW);
1586 } else if (DraggedTabMsgID == msgid) {
1588 const void *bytes = [data bytes];
1589 // NOTE! The destination index is 0 based, so do not add 1 to make it 1
1591 int idx = *((int*)bytes);
1594 } else if (SetTextDimensionsMsgID == msgid || LiveResizeMsgID == msgid) {
1596 const void *bytes = [data bytes];
1597 int rows = *((int*)bytes); bytes += sizeof(int);
1598 int cols = *((int*)bytes); bytes += sizeof(int);
1600 // NOTE! Vim doesn't call gui_mch_set_shellsize() after
1601 // gui_resize_shell(), so we have to manually set the rows and columns
1602 // here. (MacVim doesn't change the rows and columns to avoid
1603 // inconsistent states between Vim and MacVim.)
1604 [self queueMessage:msgid data:data];
1606 //NSLog(@"[VimTask] Resizing shell to %dx%d.", cols, rows);
1607 gui_resize_shell(cols, rows);
1608 } else if (ExecuteMenuMsgID == msgid) {
1609 NSDictionary *attrs = [NSDictionary dictionaryWithData:data];
1611 NSArray *desc = [attrs objectForKey:@"descriptor"];
1612 vimmenu_T *menu = menu_for_descriptor(desc);
1616 } else if (ToggleToolbarMsgID == msgid) {
1617 [self handleToggleToolbar];
1618 } else if (ScrollbarEventMsgID == msgid) {
1619 [self handleScrollbarEvent:data];
1620 } else if (SetFontMsgID == msgid) {
1621 [self handleSetFont:data];
1622 } else if (VimShouldCloseMsgID == msgid) {
1624 } else if (DropFilesMsgID == msgid) {
1625 [self handleDropFiles:data];
1626 } else if (DropStringMsgID == msgid) {
1627 [self handleDropString:data];
1628 } else if (GotFocusMsgID == msgid) {
1630 [self focusChange:YES];
1631 } else if (LostFocusMsgID == msgid) {
1633 [self focusChange:NO];
1634 } else if (SetMouseShapeMsgID == msgid) {
1635 const void *bytes = [data bytes];
1636 int shape = *((int*)bytes); bytes += sizeof(int);
1637 update_mouseshape(shape);
1638 } else if (ODBEditMsgID == msgid) {
1639 [self handleOdbEdit:data];
1640 } else if (XcodeModMsgID == msgid) {
1641 [self handleXcodeMod:data];
1643 NSLog(@"WARNING: Unknown message received (msgid=%d)", msgid);
1647 + (NSDictionary *)specialKeys
1649 static NSDictionary *specialKeys = nil;
1652 NSBundle *mainBundle = [NSBundle mainBundle];
1653 NSString *path = [mainBundle pathForResource:@"SpecialKeys"
1655 specialKeys = [[NSDictionary alloc] initWithContentsOfFile:path];
1661 - (void)handleInsertText:(NSData *)data
1665 NSString *key = [[NSString alloc] initWithData:data
1666 encoding:NSUTF8StringEncoding];
1667 char_u *str = (char_u*)[key UTF8String];
1668 int i, len = [key lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
1671 char_u *conv_str = NULL;
1672 if (input_conv.vc_type != CONV_NONE) {
1673 conv_str = string_convert(&input_conv, str, &len);
1679 if (len == 1 && ((str[0] == Ctrl_C && ctrl_c_interrupts)
1680 || (str[0] == intr_char && intr_char != Ctrl_C))) {
1685 for (i = 0; i < len; ++i) {
1686 add_to_input_buf(str+i, 1);
1687 if (CSI == str[i]) {
1688 // NOTE: If the converted string contains the byte CSI, then it
1689 // must be followed by the bytes KS_EXTRA, KE_CSI or things
1691 static char_u extra[2] = { KS_EXTRA, KE_CSI };
1692 add_to_input_buf(extra, 2);
1703 - (void)handleKeyDown:(NSString *)key modifiers:(int)mods
1707 char_u *chars = (char_u*)[key UTF8String];
1709 char_u *conv_str = NULL;
1711 int length = [key lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
1713 // Special keys (arrow keys, function keys, etc.) are stored in a plist so
1714 // that new keys can easily be added.
1715 NSString *specialString = [[MMBackend specialKeys]
1717 if (specialString && [specialString length] > 1) {
1718 //NSLog(@"special key: %@", specialString);
1719 int ikey = TO_SPECIAL([specialString characterAtIndex:0],
1720 [specialString characterAtIndex:1]);
1722 ikey = simplify_key(ikey, &mods);
1727 special[1] = K_SECOND(ikey);
1728 special[2] = K_THIRD(ikey);
1732 } else if (1 == length && TAB == chars[0]) {
1733 // Tab is a trouble child:
1734 // - <Tab> is added to the input buffer as is
1735 // - <S-Tab> is translated to, {CSI,'k','B'} (i.e. 'Back-tab')
1736 // - <M-Tab> should be 0x80|TAB but this is not valid utf-8 so it needs
1737 // to be converted to utf-8
1738 // - <S-M-Tab> is translated to <S-Tab> with ALT modifier
1739 // - <C-Tab> is reserved by Mac OS X
1740 // - <D-Tab> is reserved by Mac OS X
1745 if (mods & MOD_MASK_SHIFT) {
1746 mods &= ~MOD_MASK_SHIFT;
1748 special[1] = K_SECOND(K_S_TAB);
1749 special[2] = K_THIRD(K_S_TAB);
1751 } else if (mods & MOD_MASK_ALT) {
1752 int mtab = 0x80 | TAB;
1756 special[0] = (mtab >> 6) + 0xc0;
1757 special[1] = mtab & 0xbf;
1765 mods &= ~MOD_MASK_ALT;
1767 } else if (length > 0) {
1768 unichar c = [key characterAtIndex:0];
1770 //NSLog(@"non-special: %@ (hex=%x, mods=%d)", key,
1771 // [key characterAtIndex:0], mods);
1773 if (length == 1 && ((c == Ctrl_C && ctrl_c_interrupts)
1774 || (c == intr_char && intr_char != Ctrl_C))) {
1779 // HACK! In most circumstances the Ctrl and Shift modifiers should be
1780 // cleared since they are already added to the key by the AppKit.
1781 // Unfortunately, the only way to deal with when to clear the modifiers
1782 // or not seems to be to have hard-wired rules like this.
1783 if ( !((' ' == c) || (0xa0 == c) || (mods & MOD_MASK_CMD)
1784 || 0x9 == c || 0xd == c || ESC == c) ) {
1785 mods &= ~MOD_MASK_SHIFT;
1786 mods &= ~MOD_MASK_CTRL;
1787 //NSLog(@"clear shift ctrl");
1790 // HACK! All Option+key presses go via 'insert text' messages, except
1791 // for <M-Space>. If the Alt flag is not cleared for <M-Space> it does
1792 // not work to map to it.
1793 if (0xa0 == c && !(mods & MOD_MASK_CMD)) {
1794 //NSLog(@"clear alt");
1795 mods &= ~MOD_MASK_ALT;
1799 if (input_conv.vc_type != CONV_NONE) {
1800 conv_str = string_convert(&input_conv, chars, &length);
1807 if (chars && length > 0) {
1809 //NSLog(@"adding mods: %d", mods);
1811 modChars[1] = KS_MODIFIER;
1813 add_to_input_buf(modChars, 3);
1816 //NSLog(@"add to input buf: 0x%x", chars[0]);
1817 // TODO: Check for CSI bytes?
1818 add_to_input_buf(chars, length);
1827 - (void)queueMessage:(int)msgid data:(NSData *)data
1829 //if (msgid != EnableMenuItemMsgID)
1830 // NSLog(@"queueMessage:%s", MessageStrings[msgid]);
1832 [outputQueue addObject:[NSData dataWithBytes:&msgid length:sizeof(int)]];
1834 [outputQueue addObject:data];
1836 [outputQueue addObject:[NSData data]];
1839 - (void)connectionDidDie:(NSNotification *)notification
1841 // If the main connection to MacVim is lost this means that MacVim was
1842 // either quit (by the user chosing Quit on the MacVim menu), or it has
1843 // crashed. In the former case the flag 'isTerminating' is set and we then
1844 // quit cleanly; in the latter case we make sure the swap files are left
1847 //NSLog(@"%s isTerminating=%d", _cmd, isTerminating);
1851 getout_preserve_modified(1);
1854 - (void)blinkTimerFired:(NSTimer *)timer
1856 NSTimeInterval timeInterval = 0;
1858 [blinkTimer release];
1861 if (MMBlinkStateOn == blinkState) {
1862 gui_undraw_cursor();
1863 blinkState = MMBlinkStateOff;
1864 timeInterval = blinkOffInterval;
1865 } else if (MMBlinkStateOff == blinkState) {
1866 gui_update_cursor(TRUE, FALSE);
1867 blinkState = MMBlinkStateOn;
1868 timeInterval = blinkOnInterval;
1871 if (timeInterval > 0) {
1873 [[NSTimer scheduledTimerWithTimeInterval:timeInterval target:self
1874 selector:@selector(blinkTimerFired:)
1875 userInfo:nil repeats:NO] retain];
1876 [self flushQueue:YES];
1880 - (void)focusChange:(BOOL)on
1882 gui_focus_change(on);
1885 - (void)handleToggleToolbar
1887 // If 'go' contains 'T', then remove it, else add it.
1889 char_u go[sizeof(GO_ALL)+2];
1894 p = vim_strchr(go, GO_TOOLBAR);
1898 char_u *end = go + len;
1904 go[len] = GO_TOOLBAR;
1908 set_option_value((char_u*)"guioptions", 0, go, 0);
1910 // Force screen redraw (does it have to be this complicated?).
1911 redraw_all_later(CLEAR);
1912 update_screen(NOT_VALID);
1915 gui_update_cursor(FALSE, FALSE);
1919 - (void)handleScrollbarEvent:(NSData *)data
1923 const void *bytes = [data bytes];
1924 long ident = *((long*)bytes); bytes += sizeof(long);
1925 int hitPart = *((int*)bytes); bytes += sizeof(int);
1926 float fval = *((float*)bytes); bytes += sizeof(float);
1927 scrollbar_T *sb = gui_find_scrollbar(ident);
1930 scrollbar_T *sb_info = sb->wp ? &sb->wp->w_scrollbars[0] : sb;
1931 long value = sb_info->value;
1932 long size = sb_info->size;
1933 long max = sb_info->max;
1934 BOOL isStillDragging = NO;
1935 BOOL updateKnob = YES;
1938 case NSScrollerDecrementPage:
1939 value -= (size > 2 ? size - 2 : 1);
1941 case NSScrollerIncrementPage:
1942 value += (size > 2 ? size - 2 : 1);
1944 case NSScrollerDecrementLine:
1947 case NSScrollerIncrementLine:
1950 case NSScrollerKnob:
1951 isStillDragging = YES;
1953 case NSScrollerKnobSlot:
1954 value = (long)(fval * (max - size + 1));
1961 //NSLog(@"value %d -> %d", sb_info->value, value);
1962 gui_drag_scrollbar(sb, value, isStillDragging);
1965 // Dragging the knob or option+clicking automatically updates
1966 // the knob position (on the actual NSScroller), so we only
1967 // need to set the knob position in the other cases.
1969 // Update both the left&right vertical scrollbars.
1970 long identLeft = sb->wp->w_scrollbars[SBAR_LEFT].ident;
1971 long identRight = sb->wp->w_scrollbars[SBAR_RIGHT].ident;
1972 [self setScrollbarThumbValue:value size:size max:max
1973 identifier:identLeft];
1974 [self setScrollbarThumbValue:value size:size max:max
1975 identifier:identRight];
1977 // Update the horizontal scrollbar.
1978 [self setScrollbarThumbValue:value size:size max:max
1985 - (void)handleSetFont:(NSData *)data
1989 const void *bytes = [data bytes];
1990 float pointSize = *((float*)bytes); bytes += sizeof(float);
1991 //unsigned len = *((unsigned*)bytes); bytes += sizeof(unsigned);
1992 bytes += sizeof(unsigned); // len not used
1994 NSMutableString *name = [NSMutableString stringWithUTF8String:bytes];
1995 [name appendString:[NSString stringWithFormat:@":h%.2f", pointSize]];
1996 char_u *s = (char_u*)[name UTF8String];
1999 s = CONVERT_FROM_UTF8(s);
2002 set_option_value((char_u*)"guifont", 0, s, 0);
2005 CONVERT_FROM_UTF8_FREE(s);
2008 // Force screen redraw (does it have to be this complicated?).
2009 redraw_all_later(CLEAR);
2010 update_screen(NOT_VALID);
2013 gui_update_cursor(FALSE, FALSE);
2017 - (void)handleDropFiles:(NSData *)data
2019 // TODO: Get rid of this method; instead use Vim script directly. At the
2020 // moment I know how to do this to open files in tabs, but I'm not sure how
2021 // to add the filenames to the command line when in command line mode.
2026 const void *bytes = [data bytes];
2027 const void *end = [data bytes] + [data length];
2028 BOOL forceOpen = *((BOOL*)bytes); bytes += sizeof(BOOL);
2029 int n = *((int*)bytes); bytes += sizeof(int);
2031 if (!forceOpen && (State & CMDLINE)) {
2032 // HACK! If Vim is in command line mode then the files names
2033 // should be added to the command line, instead of opening the
2034 // files in tabs (unless forceOpen is set). This is taken care of by
2035 // gui_handle_drop().
2036 char_u **fnames = (char_u **)alloc(n * sizeof(char_u *));
2039 while (bytes < end && i < n) {
2040 int len = *((int*)bytes); bytes += sizeof(int);
2041 char_u *s = (char_u*)bytes;
2043 s = CONVERT_FROM_UTF8(s);
2045 fnames[i++] = vim_strsave(s);
2047 CONVERT_FROM_UTF8_FREE(s);
2052 // NOTE! This function will free 'fnames'.
2053 // HACK! It is assumed that the 'x' and 'y' arguments are
2054 // unused when in command line mode.
2055 gui_handle_drop(0, 0, 0, fnames, i < n ? i : n);
2058 // HACK! I'm not sure how to get Vim to open a list of files in
2059 // tabs, so instead I create a ':tab drop' command with all the
2060 // files to open and execute it.
2061 NSMutableString *cmd = [NSMutableString stringWithString:@":tab drop"];
2064 for (i = 0; i < n && bytes < end; ++i) {
2065 int len = *((int*)bytes); bytes += sizeof(int);
2066 NSString *file = [NSString stringWithUTF8String:bytes];
2067 file = [file stringByEscapingSpecialFilenameCharacters];
2070 [cmd appendString:@" "];
2071 [cmd appendString:file];
2074 // By going to the last tabpage we ensure that the new tabs will
2075 // appear last (if this call is left out, the taborder becomes
2079 char_u *s = (char_u*)[cmd UTF8String];
2081 s = CONVERT_FROM_UTF8(s);
2085 CONVERT_FROM_UTF8_FREE(s);
2088 // Force screen redraw (does it have to be this complicated?).
2089 // (This code was taken from the end of gui_handle_drop().)
2090 update_screen(NOT_VALID);
2093 gui_update_cursor(FALSE, FALSE);
2100 - (void)handleDropString:(NSData *)data
2105 char_u dropkey[3] = { CSI, KS_EXTRA, (char_u)KE_DROP };
2106 const void *bytes = [data bytes];
2107 int len = *((int*)bytes); bytes += sizeof(int);
2108 NSMutableString *string = [NSMutableString stringWithUTF8String:bytes];
2110 // Replace unrecognized end-of-line sequences with \x0a (line feed).
2111 NSRange range = { 0, [string length] };
2112 unsigned n = [string replaceOccurrencesOfString:@"\x0d\x0a"
2113 withString:@"\x0a" options:0
2116 n = [string replaceOccurrencesOfString:@"\x0d" withString:@"\x0a"
2117 options:0 range:range];
2120 len = [string lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
2121 char_u *s = (char_u*)[string UTF8String];
2123 if (input_conv.vc_type != CONV_NONE)
2124 s = string_convert(&input_conv, s, &len);
2126 dnd_yank_drag_data(s, len);
2128 if (input_conv.vc_type != CONV_NONE)
2131 add_to_input_buf(dropkey, sizeof(dropkey));
2135 - (void)handleOdbEdit:(NSData *)data
2137 #ifdef FEAT_ODB_EDITOR
2138 const void *bytes = [data bytes];
2140 OSType serverID = *((OSType*)bytes); bytes += sizeof(OSType);
2142 char_u *path = NULL;
2143 int pathLen = *((int*)bytes); bytes += sizeof(int);
2145 path = (char_u*)bytes;
2148 path = CONVERT_FROM_UTF8(path);
2152 NSAppleEventDescriptor *token = nil;
2153 DescType tokenType = *((DescType*)bytes); bytes += sizeof(DescType);
2154 int descLen = *((int*)bytes); bytes += sizeof(int);
2156 token = [NSAppleEventDescriptor descriptorWithDescriptorType:tokenType
2162 unsigned i, numFiles = *((unsigned*)bytes); bytes += sizeof(unsigned);
2163 for (i = 0; i < numFiles; ++i) {
2164 int len = *((int*)bytes); bytes += sizeof(int);
2165 char_u *filename = (char_u*)bytes;
2167 filename = CONVERT_FROM_UTF8(filename);
2169 buf_T *buf = buflist_findname(filename);
2171 if (buf->b_odb_token) {
2172 [(NSAppleEventDescriptor*)(buf->b_odb_token) release];
2173 buf->b_odb_token = NULL;
2176 if (buf->b_odb_fname) {
2177 vim_free(buf->b_odb_fname);
2178 buf->b_odb_fname = NULL;
2181 buf->b_odb_server_id = serverID;
2184 buf->b_odb_token = [token retain];
2186 buf->b_odb_fname = vim_strsave(path);
2188 NSLog(@"WARNING: Could not find buffer '%s' for ODB editing.",
2193 CONVERT_FROM_UTF8_FREE(filename);
2198 CONVERT_FROM_UTF8_FREE(path);
2200 #endif // FEAT_ODB_EDITOR
2203 - (void)handleXcodeMod:(NSData *)data
2206 const void *bytes = [data bytes];
2207 DescType type = *((DescType*)bytes); bytes += sizeof(DescType);
2208 unsigned len = *((unsigned*)bytes); bytes += sizeof(unsigned);
2212 NSAppleEventDescriptor *replyEvent = [NSAppleEventDescriptor
2213 descriptorWithDescriptorType:type
2219 - (BOOL)checkForModifiedBuffers
2222 for (buf = firstbuf; buf != NULL; buf = buf->b_next) {
2223 if (bufIsChanged(buf)) {
2231 - (void)addInput:(NSString *)input
2233 char_u *s = (char_u*)[input UTF8String];
2236 s = CONVERT_FROM_UTF8(s);
2239 server_to_input_buf(s);
2242 CONVERT_FROM_UTF8_FREE(s);
2246 @end // MMBackend (Private)
2251 @implementation MMBackend (ClientServer)
2253 - (NSString *)connectionNameFromServerName:(NSString *)name
2255 NSString *bundleIdentifier = [[NSBundle mainBundle] bundleIdentifier];
2257 return [[NSString stringWithFormat:@"%@.%@", bundleIdentifier, name]
2261 - (NSConnection *)connectionForServerName:(NSString *)name
2263 // TODO: Try 'name%d' if 'name' fails.
2264 NSString *connName = [self connectionNameFromServerName:name];
2265 NSConnection *svrConn = [connectionNameDict objectForKey:connName];
2268 svrConn = [NSConnection connectionWithRegisteredName:connName
2270 // Try alternate server...
2271 if (!svrConn && alternateServerName) {
2272 //NSLog(@" trying to connect to alternate server: %@",
2273 // alternateServerName);
2274 connName = [self connectionNameFromServerName:alternateServerName];
2275 svrConn = [NSConnection connectionWithRegisteredName:connName
2279 // Try looking for alternate servers...
2281 //NSLog(@" looking for alternate servers...");
2282 NSString *alt = [self alternateServerNameForName:name];
2283 if (alt != alternateServerName) {
2284 //NSLog(@" found alternate server: %@", string);
2285 [alternateServerName release];
2286 alternateServerName = [alt copy];
2290 // Try alternate server again...
2291 if (!svrConn && alternateServerName) {
2292 //NSLog(@" trying to connect to alternate server: %@",
2293 // alternateServerName);
2294 connName = [self connectionNameFromServerName:alternateServerName];
2295 svrConn = [NSConnection connectionWithRegisteredName:connName
2300 [connectionNameDict setObject:svrConn forKey:connName];
2302 //NSLog(@"Adding %@ as connection observer for %@", self, svrConn);
2303 [[NSNotificationCenter defaultCenter] addObserver:self
2304 selector:@selector(serverConnectionDidDie:)
2305 name:NSConnectionDidDieNotification object:svrConn];
2312 - (NSConnection *)connectionForServerPort:(int)port
2315 NSEnumerator *e = [connectionNameDict objectEnumerator];
2317 while ((conn = [e nextObject])) {
2318 // HACK! Assume connection uses mach ports.
2319 if (port == [(NSMachPort*)[conn sendPort] machPort])
2326 - (void)serverConnectionDidDie:(NSNotification *)notification
2328 //NSLog(@"%s%@", _cmd, notification);
2330 NSConnection *svrConn = [notification object];
2332 //NSLog(@"Removing %@ as connection observer from %@", self, svrConn);
2333 [[NSNotificationCenter defaultCenter]
2335 name:NSConnectionDidDieNotification
2338 [connectionNameDict removeObjectsForKeys:
2339 [connectionNameDict allKeysForObject:svrConn]];
2341 // HACK! Assume connection uses mach ports.
2342 int port = [(NSMachPort*)[svrConn sendPort] machPort];
2343 NSNumber *key = [NSNumber numberWithInt:port];
2345 [clientProxyDict removeObjectForKey:key];
2346 [serverReplyDict removeObjectForKey:key];
2349 - (void)addClient:(NSDistantObject *)client
2351 NSConnection *conn = [client connectionForProxy];
2352 // HACK! Assume connection uses mach ports.
2353 int port = [(NSMachPort*)[conn sendPort] machPort];
2354 NSNumber *key = [NSNumber numberWithInt:port];
2356 if (![clientProxyDict objectForKey:key]) {
2357 [client setProtocolForProxy:@protocol(MMVimClientProtocol)];
2358 [clientProxyDict setObject:client forKey:key];
2361 // NOTE: 'clientWindow' is a global variable which is used by <client>
2362 clientWindow = port;
2365 - (NSString *)alternateServerNameForName:(NSString *)name
2367 if (!(name && [name length] > 0))
2370 // Only look for alternates if 'name' doesn't end in a digit.
2371 unichar lastChar = [name characterAtIndex:[name length]-1];
2372 if (lastChar >= '0' && lastChar <= '9')
2375 // Look for alternates among all current servers.
2376 NSArray *list = [self serverList];
2377 if (!(list && [list count] > 0))
2380 // Filter out servers starting with 'name' and ending with a number. The
2381 // (?i) pattern ensures that the match is case insensitive.
2382 NSString *pat = [NSString stringWithFormat:@"(?i)%@[0-9]+\\z", name];
2383 NSPredicate *pred = [NSPredicate predicateWithFormat:
2384 @"SELF MATCHES %@", pat];
2385 list = [list filteredArrayUsingPredicate:pred];
2386 if ([list count] > 0) {
2387 list = [list sortedArrayUsingSelector:@selector(serverNameCompare:)];
2388 return [list objectAtIndex:0];
2394 @end // MMBackend (ClientServer)
2399 @implementation NSString (MMServerNameCompare)
2400 - (NSComparisonResult)serverNameCompare:(NSString *)string
2402 return [self compare:string
2403 options:NSCaseInsensitiveSearch|NSNumericSearch];
2410 static int eventModifierFlagsToVimModMask(int modifierFlags)
2414 if (modifierFlags & NSShiftKeyMask)
2415 modMask |= MOD_MASK_SHIFT;
2416 if (modifierFlags & NSControlKeyMask)
2417 modMask |= MOD_MASK_CTRL;
2418 if (modifierFlags & NSAlternateKeyMask)
2419 modMask |= MOD_MASK_ALT;
2420 if (modifierFlags & NSCommandKeyMask)
2421 modMask |= MOD_MASK_CMD;
2426 static int eventModifierFlagsToVimMouseModMask(int modifierFlags)
2430 if (modifierFlags & NSShiftKeyMask)
2431 modMask |= MOUSE_SHIFT;
2432 if (modifierFlags & NSControlKeyMask)
2433 modMask |= MOUSE_CTRL;
2434 if (modifierFlags & NSAlternateKeyMask)
2435 modMask |= MOUSE_ALT;
2440 static int eventButtonNumberToVimMouseButton(int buttonNumber)
2442 static int mouseButton[] = { MOUSE_LEFT, MOUSE_RIGHT, MOUSE_MIDDLE };
2444 return (buttonNumber >= 0 && buttonNumber < 3)
2445 ? mouseButton[buttonNumber] : -1;
2448 // This function is modeled after the VimToPython function found in if_python.c
2449 // NB This does a deep copy by value, it does not lookup references like the
2450 // VimToPython function does. This is because I didn't want to deal with the
2451 // retain cycles that this would create, and we can cover 99% of the use cases
2452 // by ignoring it. If we ever switch to using GC in MacVim then this
2453 // functionality can be implemented easily.
2454 static id vimToCocoa(typval_T * tv, int depth)
2460 // Avoid infinite recursion
2465 if (tv->v_type == VAR_STRING) {
2466 char_u * val = tv->vval.v_string;
2467 // val can be NULL if the string is empty
2469 result = [NSString string];
2472 val = CONVERT_TO_UTF8(val);
2474 result = [NSString stringWithUTF8String:(char*)val];
2476 CONVERT_TO_UTF8_FREE(val);
2479 } else if (tv->v_type == VAR_NUMBER) {
2480 // looks like sizeof(varnumber_T) is always <= sizeof(long)
2481 result = [NSNumber numberWithLong:(long)tv->vval.v_number];
2482 } else if (tv->v_type == VAR_LIST) {
2483 list_T * list = tv->vval.v_list;
2486 NSMutableArray * arr = result = [NSMutableArray array];
2489 for (curr = list->lv_first; curr != NULL; curr = curr->li_next) {
2490 newObj = vimToCocoa(&curr->li_tv, depth + 1);
2491 [arr addObject:newObj];
2494 } else if (tv->v_type == VAR_DICT) {
2495 NSMutableDictionary * dict = result = [NSMutableDictionary dictionary];
2497 if (tv->vval.v_dict != NULL) {
2498 hashtab_T * ht = &tv->vval.v_dict->dv_hashtab;
2499 int todo = ht->ht_used;
2503 for (hi = ht->ht_array; todo > 0; ++hi) {
2504 if (!HASHITEM_EMPTY(hi)) {
2507 di = dict_lookup(hi);
2508 newObj = vimToCocoa(&di->di_tv, depth + 1);
2510 char_u * keyval = hi->hi_key;
2512 keyval = CONVERT_TO_UTF8(keyval);
2514 NSString * key = [NSString stringWithUTF8String:(char*)keyval];
2516 CONVERT_TO_UTF8_FREE(keyval);
2518 [dict setObject:newObj forKey:key];
2522 } else { // only func refs should fall into this category?
2530 // This function is modeled after eval_client_expr_to_string found in main.c
2531 // Returns nil if there was an error evaluating the expression, and writes a
2532 // message to errorStr.
2533 // TODO Get the error that occurred while evaluating the expression in vim
2535 static id evalExprCocoa(NSString * expr, NSString ** errstr)
2538 char_u *s = (char_u*)[expr UTF8String];
2541 s = CONVERT_FROM_UTF8(s);
2544 int save_dbl = debug_break_level;
2545 int save_ro = redir_off;
2547 debug_break_level = -1;
2551 typval_T * tvres = eval_expr(s, NULL);
2553 debug_break_level = save_dbl;
2554 redir_off = save_ro;
2561 CONVERT_FROM_UTF8_FREE(s);
2566 gui_update_cursor(FALSE, FALSE);
2569 if (tvres == NULL) {
2571 *errstr = @"Expression evaluation failed.";
2574 id res = vimToCocoa(tvres, 1);
2579 *errstr = @"Conversion to cocoa values failed.";