1 /* vi:set ts=8 sts=4 sw=4 ft=objc:
3 * VIM - Vi IMproved by Bram Moolenaar
4 * MacVim GUI port by Bjorn Winckler
6 * Do ":help uganda" in Vim to read copying and usage conditions.
7 * Do ":help credits" in Vim to see a list of people who contributed.
8 * See README.txt for an overview of the Vim source code.
13 * MMBackend communicates with the frontend (MacVim). It maintains a queue of
14 * output which is flushed to the frontend under controlled circumstances (so
15 * as to maintain a steady framerate). Input from the frontend is also handled
18 * The frontend communicates with the backend via the MMBackendProtocol. In
19 * particular, input is sent to the backend via processInput:data: and Vim
20 * state can be queried from the frontend with evaluateExpression:.
22 * It is very important to realize that all state is held by the backend, the
23 * frontend must either ask for state [MMBackend evaluateExpression:] or wait
24 * for the backend to update [MMVimController processCommandQueue:].
26 * The client/server functionality of Vim is handled by the backend. It sets
27 * up a named NSConnection to which other Vim processes can connect.
34 // NOTE: Colors in MMBackend are stored as unsigned ints on the form 0xaarrggbb
35 // whereas colors in Vim are int without the alpha component. Also note that
36 // 'transp' is assumed to be a value between 0 and 100.
37 #define MM_COLOR(col) ((unsigned)( ((col)&0xffffff) | 0xff000000 ))
38 #define MM_COLOR_WITH_TRANSP(col,transp) \
39 ((unsigned)( ((col)&0xffffff) \
40 | ((((unsigned)((((100-(transp))*255)/100)+.5f))&0xff)<<24) ))
43 // This constant controls how often the command queue may be flushed. If it is
44 // too small the app might feel unresponsive; if it is too large there might be
45 // long periods without the screen updating (e.g. when sourcing a large session
46 // file). (The unit is seconds.)
47 static float MMFlushTimeoutInterval = 0.1f;
48 static int MMFlushQueueLenHint = 80*40;
50 static unsigned MMServerMax = 1000;
52 // TODO: Move to separate file.
53 static int eventModifierFlagsToVimModMask(int modifierFlags);
54 static int vimModMaskToEventModifierFlags(int mods);
55 static int eventModifierFlagsToVimMouseModMask(int modifierFlags);
56 static int eventButtonNumberToVimMouseButton(int buttonNumber);
57 static int specialKeyToNSKey(int key);
65 static NSString *MMSymlinkWarningString =
66 @"\n\n\tMost likely this is because you have symlinked directly to\n"
67 "\tthe Vim binary, which Cocoa does not allow. Please use an\n"
68 "\talias or the mvim shell script instead. If you have not used\n"
69 "\ta symlink, then your MacVim.app bundle is incomplete.\n\n";
73 @interface NSString (MMServerNameCompare)
74 - (NSComparisonResult)serverNameCompare:(NSString *)string;
79 @interface MMBackend (Private)
80 - (void)processInputQueue;
81 - (void)handleInputEvent:(int)msgid data:(NSData *)data;
82 + (NSDictionary *)specialKeys;
83 - (void)handleInsertText:(NSData *)data;
84 - (void)handleKeyDown:(NSString *)key modifiers:(int)mods;
85 - (void)queueMessage:(int)msgid data:(NSData *)data;
86 - (void)connectionDidDie:(NSNotification *)notification;
87 - (void)blinkTimerFired:(NSTimer *)timer;
88 - (void)focusChange:(BOOL)on;
89 - (void)handleToggleToolbar;
90 - (void)handleScrollbarEvent:(NSData *)data;
91 - (void)handleSetFont:(NSData *)data;
92 - (void)handleDropFiles:(NSData *)data;
93 - (void)handleDropString:(NSData *)data;
94 - (void)handleOdbEdit:(NSData *)data;
95 - (void)handleXcodeMod:(NSData *)data;
96 - (BOOL)checkForModifiedBuffers;
97 - (void)addInput:(NSString *)input;
102 @interface MMBackend (ClientServer)
103 - (NSString *)connectionNameFromServerName:(NSString *)name;
104 - (NSConnection *)connectionForServerName:(NSString *)name;
105 - (NSConnection *)connectionForServerPort:(int)port;
106 - (void)serverConnectionDidDie:(NSNotification *)notification;
107 - (void)addClient:(NSDistantObject *)client;
108 - (NSString *)alternateServerNameForName:(NSString *)name;
113 @implementation MMBackend
115 + (MMBackend *)sharedInstance
117 static MMBackend *singleton = nil;
118 return singleton ? singleton : (singleton = [MMBackend new]);
124 if (!self) return nil;
126 fontContainerRef = loadFonts();
128 outputQueue = [[NSMutableArray alloc] init];
129 inputQueue = [[NSMutableArray alloc] init];
130 drawData = [[NSMutableData alloc] initWithCapacity:1024];
131 connectionNameDict = [[NSMutableDictionary alloc] init];
132 clientProxyDict = [[NSMutableDictionary alloc] init];
133 serverReplyDict = [[NSMutableDictionary alloc] init];
135 NSBundle *mainBundle = [NSBundle mainBundle];
136 NSString *path = [mainBundle pathForResource:@"Colors" ofType:@"plist"];
138 colorDict = [[NSDictionary dictionaryWithContentsOfFile:path] retain];
140 path = [mainBundle pathForResource:@"SystemColors" ofType:@"plist"];
142 sysColorDict = [[NSDictionary dictionaryWithContentsOfFile:path]
145 if (!(colorDict && sysColorDict))
146 NSLog(@"ERROR: Failed to load color dictionaries.%@",
147 MMSymlinkWarningString);
154 //NSLog(@"%@ %s", [self className], _cmd);
155 [[NSNotificationCenter defaultCenter] removeObserver:self];
157 [oldWideFont release]; oldWideFont = nil;
158 [blinkTimer release]; blinkTimer = nil;
159 [alternateServerName release]; alternateServerName = nil;
160 [serverReplyDict release]; serverReplyDict = nil;
161 [clientProxyDict release]; clientProxyDict = nil;
162 [connectionNameDict release]; connectionNameDict = nil;
163 [inputQueue release]; inputQueue = nil;
164 [outputQueue release]; outputQueue = nil;
165 [drawData release]; drawData = nil;
166 [frontendProxy release]; frontendProxy = nil;
167 [connection release]; connection = nil;
168 [sysColorDict release]; sysColorDict = nil;
169 [colorDict release]; colorDict = nil;
174 - (void)setBackgroundColor:(int)color
176 backgroundColor = MM_COLOR_WITH_TRANSP(color,p_transp);
179 - (void)setForegroundColor:(int)color
181 foregroundColor = MM_COLOR(color);
184 - (void)setSpecialColor:(int)color
186 specialColor = MM_COLOR(color);
189 - (void)setDefaultColorsBackground:(int)bg foreground:(int)fg
191 defaultBackgroundColor = MM_COLOR_WITH_TRANSP(bg,p_transp);
192 defaultForegroundColor = MM_COLOR(fg);
194 NSMutableData *data = [NSMutableData data];
196 [data appendBytes:&defaultBackgroundColor length:sizeof(unsigned)];
197 [data appendBytes:&defaultForegroundColor length:sizeof(unsigned)];
199 [self queueMessage:SetDefaultColorsMsgID data:data];
202 - (NSConnection *)connection
205 // NOTE! If the name of the connection changes here it must also be
206 // updated in MMAppController.m.
207 NSString *name = [NSString stringWithFormat:@"%@-connection",
208 [[NSBundle mainBundle] bundleIdentifier]];
210 connection = [NSConnection connectionWithRegisteredName:name host:nil];
214 // NOTE: 'connection' may be nil here.
220 if (![self connection]) {
221 NSBundle *mainBundle = [NSBundle mainBundle];
226 // Launch MacVim using Launch Services (NSWorkspace would be nicer, but
227 // the API to pass Apple Event parameters is broken on 10.4).
228 NSString *path = [mainBundle bundlePath];
229 status = FSPathMakeRef((const UInt8 *)[path UTF8String], &ref, NULL);
230 if (noErr == status) {
231 // Pass parameter to the 'Open' Apple Event that tells MacVim not
232 // to open an untitled window.
233 NSAppleEventDescriptor *desc =
234 [NSAppleEventDescriptor recordDescriptor];
235 [desc setParamDescriptor:
236 [NSAppleEventDescriptor descriptorWithBoolean:NO]
237 forKeyword:keyMMUntitledWindow];
239 LSLaunchFSRefSpec spec = { &ref, 0, NULL, [desc aeDesc],
240 kLSLaunchDefaults, NULL };
241 status = LSOpenFromRefSpec(&spec, NULL);
244 if (noErr != status) {
245 NSLog(@"ERROR: Failed to launch MacVim (path=%@).%@",
246 path, MMSymlinkWarningString);
250 // Launch MacVim using NSTask. For some reason the above code using
251 // Launch Services sometimes fails on LSOpenFromRefSpec() (when it
252 // fails, the dock icon starts bouncing and never stops). It seems
253 // like rebuilding the Launch Services database takes care of this
254 // problem, but the NSTask way seems more stable so stick with it.
256 // NOTE! Using NSTask to launch the GUI has the negative side-effect
257 // that the GUI won't be activated (or raised) so there is a hack in
258 // MMAppController which raises the app when a new window is opened.
259 NSMutableArray *args = [NSMutableArray arrayWithObjects:
260 [NSString stringWithFormat:@"-%@", MMNoWindowKey], @"yes", nil];
261 NSString *exeName = [[mainBundle infoDictionary]
262 objectForKey:@"CFBundleExecutable"];
263 NSString *path = [mainBundle pathForAuxiliaryExecutable:exeName];
265 NSLog(@"ERROR: Could not find MacVim executable in bundle.%@",
266 MMSymlinkWarningString);
270 [NSTask launchedTaskWithLaunchPath:path arguments:args];
273 // HACK! Poll the mach bootstrap server until it returns a valid
274 // connection to detect that MacVim has finished launching. Also set a
275 // time-out date so that we don't get stuck doing this forever.
276 NSDate *timeOutDate = [NSDate dateWithTimeIntervalSinceNow:10];
277 while (![self connection] &&
278 NSOrderedDescending == [timeOutDate compare:[NSDate date]])
279 [[NSRunLoop currentRunLoop]
280 runMode:NSDefaultRunLoopMode
281 beforeDate:[NSDate dateWithTimeIntervalSinceNow:1]];
283 // NOTE: [self connection] will set 'connection' as a side-effect.
285 NSLog(@"WARNING: Timed-out waiting for GUI to launch.");
292 [[NSNotificationCenter defaultCenter] addObserver:self
293 selector:@selector(connectionDidDie:)
294 name:NSConnectionDidDieNotification object:connection];
296 id proxy = [connection rootProxy];
297 [proxy setProtocolForProxy:@protocol(MMAppProtocol)];
299 int pid = [[NSProcessInfo processInfo] processIdentifier];
301 frontendProxy = [proxy connectBackend:self pid:pid];
303 [frontendProxy retain];
304 [frontendProxy setProtocolForProxy:@protocol(MMAppProtocol)];
308 @catch (NSException *e) {
309 NSLog(@"Exception caught when trying to connect backend: \"%@\"", e);
315 - (BOOL)openVimWindow
317 [self queueMessage:OpenVimWindowMsgID data:nil];
323 int type = ClearAllDrawType;
325 // Any draw commands in queue are effectively obsolete since this clearAll
326 // will negate any effect they have, therefore we may as well clear the
328 [drawData setLength:0];
330 [drawData appendBytes:&type length:sizeof(int)];
333 - (void)clearBlockFromRow:(int)row1 column:(int)col1
334 toRow:(int)row2 column:(int)col2
336 int type = ClearBlockDrawType;
338 [drawData appendBytes:&type length:sizeof(int)];
340 [drawData appendBytes:&defaultBackgroundColor length:sizeof(unsigned)];
341 [drawData appendBytes:&row1 length:sizeof(int)];
342 [drawData appendBytes:&col1 length:sizeof(int)];
343 [drawData appendBytes:&row2 length:sizeof(int)];
344 [drawData appendBytes:&col2 length:sizeof(int)];
347 - (void)deleteLinesFromRow:(int)row count:(int)count
348 scrollBottom:(int)bottom left:(int)left right:(int)right
350 int type = DeleteLinesDrawType;
352 [drawData appendBytes:&type length:sizeof(int)];
354 [drawData appendBytes:&defaultBackgroundColor length:sizeof(unsigned)];
355 [drawData appendBytes:&row length:sizeof(int)];
356 [drawData appendBytes:&count length:sizeof(int)];
357 [drawData appendBytes:&bottom length:sizeof(int)];
358 [drawData appendBytes:&left length:sizeof(int)];
359 [drawData appendBytes:&right length:sizeof(int)];
362 - (void)drawString:(char*)s length:(int)len row:(int)row column:(int)col
363 cells:(int)cells flags:(int)flags
365 if (len <= 0 || cells <= 0) return;
367 int type = DrawStringDrawType;
369 [drawData appendBytes:&type length:sizeof(int)];
371 [drawData appendBytes:&backgroundColor length:sizeof(unsigned)];
372 [drawData appendBytes:&foregroundColor length:sizeof(unsigned)];
373 [drawData appendBytes:&specialColor length:sizeof(unsigned)];
374 [drawData appendBytes:&row length:sizeof(int)];
375 [drawData appendBytes:&col length:sizeof(int)];
376 [drawData appendBytes:&cells length:sizeof(int)];
377 [drawData appendBytes:&flags length:sizeof(int)];
378 [drawData appendBytes:&len length:sizeof(int)];
379 [drawData appendBytes:s length:len];
382 - (void)insertLinesFromRow:(int)row count:(int)count
383 scrollBottom:(int)bottom left:(int)left right:(int)right
385 int type = InsertLinesDrawType;
387 [drawData appendBytes:&type length:sizeof(int)];
389 [drawData appendBytes:&defaultBackgroundColor length:sizeof(unsigned)];
390 [drawData appendBytes:&row length:sizeof(int)];
391 [drawData appendBytes:&count length:sizeof(int)];
392 [drawData appendBytes:&bottom length:sizeof(int)];
393 [drawData appendBytes:&left length:sizeof(int)];
394 [drawData appendBytes:&right length:sizeof(int)];
397 - (void)drawCursorAtRow:(int)row column:(int)col shape:(int)shape
398 fraction:(int)percent color:(int)color
400 int type = DrawCursorDrawType;
401 unsigned uc = MM_COLOR(color);
403 [drawData appendBytes:&type length:sizeof(int)];
405 [drawData appendBytes:&uc length:sizeof(unsigned)];
406 [drawData appendBytes:&row length:sizeof(int)];
407 [drawData appendBytes:&col length:sizeof(int)];
408 [drawData appendBytes:&shape length:sizeof(int)];
409 [drawData appendBytes:&percent length:sizeof(int)];
414 // Tend to the run loop, returning immediately if there are no events
416 [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
417 beforeDate:[NSDate distantPast]];
420 // Keyboard and mouse input is handled directly, other input is queued and
421 // processed here. This call may enter a blocking loop.
422 if ([inputQueue count] > 0)
423 [self processInputQueue];
427 - (void)flushQueue:(BOOL)force
429 // NOTE! This method gets called a lot; if we were to flush every time it
430 // got called MacVim would feel unresponsive. So there is a time out which
431 // ensures that the queue isn't flushed too often.
432 if (!force && lastFlushDate && -[lastFlushDate timeIntervalSinceNow]
433 < MMFlushTimeoutInterval
434 && [drawData length] < MMFlushQueueLenHint)
437 if ([drawData length] > 0) {
438 // HACK! Detect changes to 'guifontwide'.
439 if (gui.wide_font != (GuiFont)oldWideFont) {
440 [oldWideFont release];
441 oldWideFont = [(NSFont*)gui.wide_font retain];
442 [self setWideFont:oldWideFont];
445 int type = SetCursorPosDrawType;
446 [drawData appendBytes:&type length:sizeof(type)];
447 [drawData appendBytes:&gui.row length:sizeof(gui.row)];
448 [drawData appendBytes:&gui.col length:sizeof(gui.col)];
450 [self queueMessage:BatchDrawMsgID data:[drawData copy]];
451 [drawData setLength:0];
454 if ([outputQueue count] > 0) {
456 [frontendProxy processCommandQueue:outputQueue];
458 @catch (NSException *e) {
459 NSLog(@"Exception caught when processing command queue: \"%@\"", e);
462 [outputQueue removeAllObjects];
464 [lastFlushDate release];
465 lastFlushDate = [[NSDate date] retain];
469 - (BOOL)waitForInput:(int)milliseconds
471 //NSLog(@"|ENTER| %s%d", _cmd, milliseconds);
473 // Only start the run loop if the input queue is empty, otherwise process
474 // the input first so that the input on queue isn't delayed.
475 if ([inputQueue count]) {
478 NSDate *date = milliseconds > 0 ?
479 [NSDate dateWithTimeIntervalSinceNow:.001*milliseconds] :
480 [NSDate distantFuture];
482 [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
486 // I know of no way to figure out if the run loop exited because input was
487 // found or because of a time out, so I need to manually indicate when
488 // input was received in processInput:data: and then reset it every time
490 BOOL yn = inputReceived;
493 // Keyboard and mouse input is handled directly, other input is queued and
494 // processed here. This call may enter a blocking loop.
495 if ([inputQueue count] > 0)
496 [self processInputQueue];
498 //NSLog(@"|LEAVE| %s input=%d", _cmd, yn);
504 #ifdef MAC_CLIENTSERVER
505 // The default connection is used for the client/server code.
506 [[NSConnection defaultConnection] setRootObject:nil];
507 [[NSConnection defaultConnection] invalidate];
510 // By invalidating the NSConnection the MMWindowController immediately
511 // finds out that the connection is down and as a result
512 // [MMWindowController connectionDidDie:] is invoked.
513 //NSLog(@"%@ %s", [self className], _cmd);
514 [[NSNotificationCenter defaultCenter] removeObserver:self];
515 [connection invalidate];
517 if (fontContainerRef) {
518 ATSFontDeactivate(fontContainerRef, NULL, kATSOptionFlagsDefault);
519 fontContainerRef = 0;
524 - (void)selectTab:(int)index
526 //NSLog(@"%s%d", _cmd, index);
529 NSData *data = [NSData dataWithBytes:&index length:sizeof(int)];
530 [self queueMessage:SelectTabMsgID data:data];
535 //NSLog(@"%s", _cmd);
537 NSMutableData *data = [NSMutableData data];
539 int idx = tabpage_index(curtab) - 1;
540 [data appendBytes:&idx length:sizeof(int)];
543 for (tp = first_tabpage; tp != NULL; tp = tp->tp_next) {
544 // This function puts the label of the tab in the global 'NameBuff'.
545 get_tabline_label(tp, FALSE);
546 char_u *s = NameBuff;
548 if (len <= 0) continue;
551 s = CONVERT_TO_UTF8(s);
554 // Count the number of windows in the tabpage.
555 //win_T *wp = tp->tp_firstwin;
557 //for (wincount = 0; wp != NULL; wp = wp->w_next, ++wincount);
559 //[data appendBytes:&wincount length:sizeof(int)];
560 [data appendBytes:&len length:sizeof(int)];
561 [data appendBytes:s length:len];
564 CONVERT_TO_UTF8_FREE(s);
568 [self queueMessage:UpdateTabBarMsgID data:data];
571 - (BOOL)tabBarVisible
573 return tabBarVisible;
576 - (void)showTabBar:(BOOL)enable
578 tabBarVisible = enable;
580 int msgid = enable ? ShowTabBarMsgID : HideTabBarMsgID;
581 [self queueMessage:msgid data:nil];
584 - (void)setRows:(int)rows columns:(int)cols
586 //NSLog(@"[VimTask] setRows:%d columns:%d", rows, cols);
588 int dim[] = { rows, cols };
589 NSData *data = [NSData dataWithBytes:&dim length:2*sizeof(int)];
591 [self queueMessage:SetTextDimensionsMsgID data:data];
594 - (void)setWindowTitle:(char *)title
596 NSMutableData *data = [NSMutableData data];
597 int len = strlen(title);
598 if (len <= 0) return;
600 [data appendBytes:&len length:sizeof(int)];
601 [data appendBytes:title length:len];
603 [self queueMessage:SetWindowTitleMsgID data:data];
606 - (char *)browseForFileInDirectory:(char *)dir title:(char *)title
609 //NSLog(@"browseForFileInDirectory:%s title:%s saving:%d", dir, title,
613 NSString *ds = dir ? [NSString stringWithUTF8String:dir] : nil;
614 NSString *ts = title ? [NSString stringWithUTF8String:title] : nil;
616 [frontendProxy showSavePanelForDirectory:ds title:ts saving:saving];
618 // Wait until a reply is sent from MMVimController.
619 [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
620 beforeDate:[NSDate distantFuture]];
622 if (dialogReturn && [dialogReturn isKindOfClass:[NSString class]]) {
623 char_u *ret = (char_u*)[dialogReturn UTF8String];
625 ret = CONVERT_FROM_UTF8(ret);
627 s = vim_strsave(ret);
629 CONVERT_FROM_UTF8_FREE(ret);
633 [dialogReturn release]; dialogReturn = nil;
635 @catch (NSException *e) {
636 NSLog(@"Exception caught when showing save panel: \"%@\"", e);
642 - (oneway void)setDialogReturn:(in bycopy id)obj
644 // NOTE: This is called by
645 // - [MMVimController panelDidEnd:::], and
646 // - [MMVimController alertDidEnd:::],
647 // to indicate that a save/open panel or alert has finished.
649 if (obj != dialogReturn) {
650 [dialogReturn release];
651 dialogReturn = [obj retain];
655 - (int)presentDialogWithType:(int)type title:(char *)title message:(char *)msg
656 buttons:(char *)btns textField:(char *)txtfield
659 NSString *message = nil, *text = nil, *textFieldString = nil;
660 NSArray *buttons = nil;
661 int style = NSInformationalAlertStyle;
663 if (VIM_WARNING == type) style = NSWarningAlertStyle;
664 else if (VIM_ERROR == type) style = NSCriticalAlertStyle;
667 NSString *btnString = [NSString stringWithUTF8String:btns];
668 buttons = [btnString componentsSeparatedByString:@"\n"];
671 message = [NSString stringWithUTF8String:title];
673 text = [NSString stringWithUTF8String:msg];
675 // HACK! If there is a '\n\n' or '\n' sequence in the message, then
676 // make the part up to there into the title. We only do this
677 // because Vim has lots of dialogs without a title and they look
679 // TODO: Fix the actual dialog texts.
680 NSRange eolRange = [text rangeOfString:@"\n\n"];
681 if (NSNotFound == eolRange.location)
682 eolRange = [text rangeOfString:@"\n"];
683 if (NSNotFound != eolRange.location) {
684 message = [text substringToIndex:eolRange.location];
685 text = [text substringFromIndex:NSMaxRange(eolRange)];
690 textFieldString = [NSString stringWithUTF8String:txtfield];
693 [frontendProxy presentDialogWithStyle:style message:message
694 informativeText:text buttonTitles:buttons
695 textFieldString:textFieldString];
697 // Wait until a reply is sent from MMVimController.
698 [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
699 beforeDate:[NSDate distantFuture]];
701 if (dialogReturn && [dialogReturn isKindOfClass:[NSArray class]]
702 && [dialogReturn count]) {
703 retval = [[dialogReturn objectAtIndex:0] intValue];
704 if (txtfield && [dialogReturn count] > 1) {
705 NSString *retString = [dialogReturn objectAtIndex:1];
706 char_u *ret = (char_u*)[retString UTF8String];
708 ret = CONVERT_FROM_UTF8(ret);
710 vim_strncpy((char_u*)txtfield, ret, IOSIZE - 1);
712 CONVERT_FROM_UTF8_FREE(ret);
717 [dialogReturn release]; dialogReturn = nil;
719 @catch (NSException *e) {
720 NSLog(@"Exception caught while showing alert dialog: \"%@\"", e);
726 - (void)addMenuWithTag:(int)tag parent:(int)parentTag name:(char *)name
729 //NSLog(@"addMenuWithTag:%d parent:%d name:%s atIndex:%d", tag, parentTag,
732 int namelen = name ? strlen(name) : 0;
733 NSMutableData *data = [NSMutableData data];
735 [data appendBytes:&tag length:sizeof(int)];
736 [data appendBytes:&parentTag length:sizeof(int)];
737 [data appendBytes:&namelen length:sizeof(int)];
738 if (namelen > 0) [data appendBytes:name length:namelen];
739 [data appendBytes:&index length:sizeof(int)];
741 [self queueMessage:AddMenuMsgID data:data];
744 - (void)addMenuItemWithTag:(int)tag parent:(int)parentTag name:(char *)name
745 tip:(char *)tip icon:(char *)icon
746 keyEquivalent:(int)key modifiers:(int)mods
747 action:(NSString *)action atIndex:(int)index
749 //NSLog(@"addMenuItemWithTag:%d parent:%d name:%s tip:%s atIndex:%d", tag,
750 // parentTag, name, tip, index);
752 int namelen = name ? strlen(name) : 0;
753 int tiplen = tip ? strlen(tip) : 0;
754 int iconlen = icon ? strlen(icon) : 0;
755 int eventFlags = vimModMaskToEventModifierFlags(mods);
756 int actionlen = [action lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
757 NSMutableData *data = [NSMutableData data];
759 key = specialKeyToNSKey(key);
761 [data appendBytes:&tag length:sizeof(int)];
762 [data appendBytes:&parentTag length:sizeof(int)];
763 [data appendBytes:&namelen length:sizeof(int)];
764 if (namelen > 0) [data appendBytes:name length:namelen];
765 [data appendBytes:&tiplen length:sizeof(int)];
766 if (tiplen > 0) [data appendBytes:tip length:tiplen];
767 [data appendBytes:&iconlen length:sizeof(int)];
768 if (iconlen > 0) [data appendBytes:icon length:iconlen];
769 [data appendBytes:&actionlen length:sizeof(int)];
770 if (actionlen > 0) [data appendBytes:[action UTF8String] length:actionlen];
771 [data appendBytes:&index length:sizeof(int)];
772 [data appendBytes:&key length:sizeof(int)];
773 [data appendBytes:&eventFlags length:sizeof(int)];
775 [self queueMessage:AddMenuItemMsgID data:data];
778 - (void)removeMenuItemWithTag:(int)tag
780 NSMutableData *data = [NSMutableData data];
781 [data appendBytes:&tag length:sizeof(int)];
783 [self queueMessage:RemoveMenuItemMsgID data:data];
786 - (void)enableMenuItemWithTag:(int)tag state:(int)enabled
788 NSMutableData *data = [NSMutableData data];
790 [data appendBytes:&tag length:sizeof(int)];
791 [data appendBytes:&enabled length:sizeof(int)];
793 [self queueMessage:EnableMenuItemMsgID data:data];
796 - (void)showPopupMenuWithName:(char *)name atMouseLocation:(BOOL)mouse
798 int len = strlen(name);
799 int row = -1, col = -1;
801 if (len <= 0) return;
803 if (!mouse && curwin) {
804 row = curwin->w_wrow;
805 col = curwin->w_wcol;
808 NSMutableData *data = [NSMutableData data];
810 [data appendBytes:&row length:sizeof(int)];
811 [data appendBytes:&col length:sizeof(int)];
812 [data appendBytes:&len length:sizeof(int)];
813 [data appendBytes:name length:len];
815 [self queueMessage:ShowPopupMenuMsgID data:data];
818 - (void)showToolbar:(int)enable flags:(int)flags
820 NSMutableData *data = [NSMutableData data];
822 [data appendBytes:&enable length:sizeof(int)];
823 [data appendBytes:&flags length:sizeof(int)];
825 [self queueMessage:ShowToolbarMsgID data:data];
828 - (void)createScrollbarWithIdentifier:(long)ident type:(int)type
830 NSMutableData *data = [NSMutableData data];
832 [data appendBytes:&ident length:sizeof(long)];
833 [data appendBytes:&type length:sizeof(int)];
835 [self queueMessage:CreateScrollbarMsgID data:data];
838 - (void)destroyScrollbarWithIdentifier:(long)ident
840 NSMutableData *data = [NSMutableData data];
841 [data appendBytes:&ident length:sizeof(long)];
843 [self queueMessage:DestroyScrollbarMsgID data:data];
846 - (void)showScrollbarWithIdentifier:(long)ident state:(int)visible
848 NSMutableData *data = [NSMutableData data];
850 [data appendBytes:&ident length:sizeof(long)];
851 [data appendBytes:&visible length:sizeof(int)];
853 [self queueMessage:ShowScrollbarMsgID data:data];
856 - (void)setScrollbarPosition:(int)pos length:(int)len identifier:(long)ident
858 NSMutableData *data = [NSMutableData data];
860 [data appendBytes:&ident length:sizeof(long)];
861 [data appendBytes:&pos length:sizeof(int)];
862 [data appendBytes:&len length:sizeof(int)];
864 [self queueMessage:SetScrollbarPositionMsgID data:data];
867 - (void)setScrollbarThumbValue:(long)val size:(long)size max:(long)max
868 identifier:(long)ident
870 float fval = max-size+1 > 0 ? (float)val/(max-size+1) : 0;
871 float prop = (float)size/(max+1);
872 if (fval < 0) fval = 0;
873 else if (fval > 1.0f) fval = 1.0f;
874 if (prop < 0) prop = 0;
875 else if (prop > 1.0f) prop = 1.0f;
877 NSMutableData *data = [NSMutableData data];
879 [data appendBytes:&ident length:sizeof(long)];
880 [data appendBytes:&fval length:sizeof(float)];
881 [data appendBytes:&prop length:sizeof(float)];
883 [self queueMessage:SetScrollbarThumbMsgID data:data];
886 - (void)setFont:(NSFont *)font
888 NSString *fontName = [font displayName];
889 float size = [font pointSize];
890 int len = [fontName lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
892 NSMutableData *data = [NSMutableData data];
894 [data appendBytes:&size length:sizeof(float)];
895 [data appendBytes:&len length:sizeof(int)];
896 [data appendBytes:[fontName UTF8String] length:len];
898 [self queueMessage:SetFontMsgID data:data];
902 - (void)setWideFont:(NSFont *)font
904 NSString *fontName = [font displayName];
905 float size = [font pointSize];
906 int len = [fontName lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
907 NSMutableData *data = [NSMutableData data];
909 [data appendBytes:&size length:sizeof(float)];
910 [data appendBytes:&len length:sizeof(int)];
912 [data appendBytes:[fontName UTF8String] length:len];
914 [self queueMessage:SetWideFontMsgID data:data];
917 - (void)executeActionWithName:(NSString *)name
919 int len = [name lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
922 NSMutableData *data = [NSMutableData data];
924 [data appendBytes:&len length:sizeof(int)];
925 [data appendBytes:[name UTF8String] length:len];
927 [self queueMessage:ExecuteActionMsgID data:data];
931 - (void)setMouseShape:(int)shape
933 NSMutableData *data = [NSMutableData data];
934 [data appendBytes:&shape length:sizeof(int)];
935 [self queueMessage:SetMouseShapeMsgID data:data];
938 - (void)setBlinkWait:(int)wait on:(int)on off:(int)off
940 // Vim specifies times in milliseconds, whereas Cocoa wants them in
942 blinkWaitInterval = .001f*wait;
943 blinkOnInterval = .001f*on;
944 blinkOffInterval = .001f*off;
950 [blinkTimer invalidate];
951 [blinkTimer release];
955 if (blinkWaitInterval > 0 && blinkOnInterval > 0 && blinkOffInterval > 0
957 blinkState = MMBlinkStateOn;
959 [[NSTimer scheduledTimerWithTimeInterval:blinkWaitInterval
961 selector:@selector(blinkTimerFired:)
962 userInfo:nil repeats:NO] retain];
963 gui_update_cursor(TRUE, FALSE);
964 [self flushQueue:YES];
970 if (MMBlinkStateOff == blinkState) {
971 gui_update_cursor(TRUE, FALSE);
972 [self flushQueue:YES];
975 blinkState = MMBlinkStateNone;
978 - (void)adjustLinespace:(int)linespace
980 NSMutableData *data = [NSMutableData data];
981 [data appendBytes:&linespace length:sizeof(int)];
982 [self queueMessage:AdjustLinespaceMsgID data:data];
987 [self queueMessage:ActivateMsgID data:nil];
990 - (void)setPreEditRow:(int)row column:(int)col
992 NSMutableData *data = [NSMutableData data];
993 [data appendBytes:&row length:sizeof(int)];
994 [data appendBytes:&col length:sizeof(int)];
995 [self queueMessage:SetPreEditPositionMsgID data:data];
998 - (int)lookupColorWithKey:(NSString *)key
1000 if (!(key && [key length] > 0))
1003 NSString *stripKey = [[[[key lowercaseString]
1004 stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]
1005 componentsSeparatedByString:@" "]
1006 componentsJoinedByString:@""];
1008 if (stripKey && [stripKey length] > 0) {
1009 // First of all try to lookup key in the color dictionary; note that
1010 // all keys in this dictionary are lowercase with no whitespace.
1011 id obj = [colorDict objectForKey:stripKey];
1012 if (obj) return [obj intValue];
1014 // The key was not in the dictionary; is it perhaps of the form
1016 if ([stripKey length] > 1 && [stripKey characterAtIndex:0] == '#') {
1017 NSScanner *scanner = [NSScanner scannerWithString:stripKey];
1018 [scanner setScanLocation:1];
1020 if ([scanner scanHexInt:&hex]) {
1025 // As a last resort, check if it is one of the system defined colors.
1026 // The keys in this dictionary are also lowercase with no whitespace.
1027 obj = [sysColorDict objectForKey:stripKey];
1029 NSColor *col = [NSColor performSelector:NSSelectorFromString(obj)];
1032 col = [col colorUsingColorSpaceName:NSCalibratedRGBColorSpace];
1033 [col getRed:&r green:&g blue:&b alpha:&a];
1034 return (((int)(r*255+.5f) & 0xff) << 16)
1035 + (((int)(g*255+.5f) & 0xff) << 8)
1036 + ((int)(b*255+.5f) & 0xff);
1041 //NSLog(@"WARNING: No color with key %@ found.", stripKey);
1045 - (BOOL)hasSpecialKeyWithValue:(NSString *)value
1047 NSEnumerator *e = [[MMBackend specialKeys] objectEnumerator];
1050 while ((obj = [e nextObject])) {
1051 if ([value isEqual:obj])
1058 - (void)enterFullscreen
1060 [self queueMessage:EnterFullscreenMsgID data:nil];
1063 - (void)leaveFullscreen
1065 [self queueMessage:LeaveFullscreenMsgID data:nil];
1068 - (void)updateModifiedFlag
1070 // Notify MacVim if _any_ buffer has changed from unmodified to modified or
1072 int msgid = [self checkForModifiedBuffers]
1073 ? BuffersModifiedMsgID : BuffersNotModifiedMsgID;
1075 [self queueMessage:msgid data:nil];
1078 - (oneway void)processInput:(int)msgid data:(in bycopy NSData *)data
1080 // NOTE: This method might get called whenever the run loop is tended to.
1081 // Normal keyboard and mouse input is added to input buffers, so there is
1082 // no risk in handling these events directly (they return immediately, and
1083 // do not call any other Vim functions). However, other events such
1084 // as 'VimShouldCloseMsgID' may enter blocking loops that wait for key
1085 // events which would cause this method to be called recursively. This
1086 // in turn leads to various difficulties that we do not want to have to
1087 // deal with. To avoid recursive calls here we add all events except
1088 // keyboard and mouse events to an input queue which is processed whenever
1089 // gui_mch_update() is called (see processInputQueue).
1091 //NSLog(@"%s%s", _cmd, MessageStrings[msgid]);
1093 // Don't flush too soon after receiving input or update speed will suffer.
1094 [lastFlushDate release];
1095 lastFlushDate = [[NSDate date] retain];
1097 // Handle keyboard and mouse input now. All other events are queued.
1098 if (InsertTextMsgID == msgid) {
1099 [self handleInsertText:data];
1100 } else if (KeyDownMsgID == msgid || CmdKeyMsgID == msgid) {
1102 const void *bytes = [data bytes];
1103 int mods = *((int*)bytes); bytes += sizeof(int);
1104 int len = *((int*)bytes); bytes += sizeof(int);
1105 NSString *key = [[NSString alloc] initWithBytes:bytes length:len
1106 encoding:NSUTF8StringEncoding];
1107 mods = eventModifierFlagsToVimModMask(mods);
1109 [self handleKeyDown:key modifiers:mods];
1112 } else if (ScrollWheelMsgID == msgid) {
1114 const void *bytes = [data bytes];
1116 int row = *((int*)bytes); bytes += sizeof(int);
1117 int col = *((int*)bytes); bytes += sizeof(int);
1118 int flags = *((int*)bytes); bytes += sizeof(int);
1119 float dy = *((float*)bytes); bytes += sizeof(float);
1121 int button = MOUSE_5;
1122 if (dy > 0) button = MOUSE_4;
1124 flags = eventModifierFlagsToVimMouseModMask(flags);
1126 int numLines = (int)round(dy);
1127 if (numLines < 0) numLines = -numLines;
1128 if (numLines == 0) numLines = 1;
1130 #ifdef FEAT_GUI_SCROLL_WHEEL_FORCE
1131 gui.scroll_wheel_force = numLines;
1134 gui_send_mouse_event(button, col, row, NO, flags);
1135 } else if (MouseDownMsgID == msgid) {
1137 const void *bytes = [data bytes];
1139 int row = *((int*)bytes); bytes += sizeof(int);
1140 int col = *((int*)bytes); bytes += sizeof(int);
1141 int button = *((int*)bytes); bytes += sizeof(int);
1142 int flags = *((int*)bytes); bytes += sizeof(int);
1143 int count = *((int*)bytes); bytes += sizeof(int);
1145 button = eventButtonNumberToVimMouseButton(button);
1147 flags = eventModifierFlagsToVimMouseModMask(flags);
1148 gui_send_mouse_event(button, col, row, count>1, flags);
1150 } else if (MouseUpMsgID == msgid) {
1152 const void *bytes = [data bytes];
1154 int row = *((int*)bytes); bytes += sizeof(int);
1155 int col = *((int*)bytes); bytes += sizeof(int);
1156 int flags = *((int*)bytes); bytes += sizeof(int);
1158 flags = eventModifierFlagsToVimMouseModMask(flags);
1160 gui_send_mouse_event(MOUSE_RELEASE, col, row, NO, flags);
1161 } else if (MouseDraggedMsgID == msgid) {
1163 const void *bytes = [data bytes];
1165 int row = *((int*)bytes); bytes += sizeof(int);
1166 int col = *((int*)bytes); bytes += sizeof(int);
1167 int flags = *((int*)bytes); bytes += sizeof(int);
1169 flags = eventModifierFlagsToVimMouseModMask(flags);
1171 gui_send_mouse_event(MOUSE_DRAG, col, row, NO, flags);
1172 } else if (MouseMovedMsgID == msgid) {
1173 const void *bytes = [data bytes];
1174 int row = *((int*)bytes); bytes += sizeof(int);
1175 int col = *((int*)bytes); bytes += sizeof(int);
1177 gui_mouse_moved(col, row);
1178 } else if (AddInputMsgID == msgid) {
1179 NSString *string = [[NSString alloc] initWithData:data
1180 encoding:NSUTF8StringEncoding];
1182 [self addInput:string];
1185 } else if (TerminateNowMsgID == msgid) {
1186 isTerminating = YES;
1188 // Not keyboard or mouse event, queue it and handle later.
1189 //NSLog(@"Add event %s to input event queue", MessageStrings[msgid]);
1190 [inputQueue addObject:[NSNumber numberWithInt:msgid]];
1191 [inputQueue addObject:(data ? (id)data : (id)[NSNull null])];
1194 // See waitForInput: for an explanation of this flag.
1195 inputReceived = YES;
1198 - (oneway void)processInputAndData:(in bycopy NSArray *)messages
1200 // TODO: Get rid of this method?
1201 //NSLog(@"%s%@", _cmd, messages);
1203 unsigned i, count = [messages count];
1204 for (i = 0; i < count; i += 2) {
1205 int msgid = [[messages objectAtIndex:i] intValue];
1206 id data = [messages objectAtIndex:i+1];
1207 if ([data isEqual:[NSNull null]])
1210 [self processInput:msgid data:data];
1214 - (NSString *)evaluateExpression:(in bycopy NSString *)expr
1216 NSString *eval = nil;
1217 char_u *s = (char_u*)[expr UTF8String];
1220 s = CONVERT_FROM_UTF8(s);
1223 char_u *res = eval_client_expr_to_string(s);
1226 CONVERT_FROM_UTF8_FREE(s);
1232 s = CONVERT_TO_UTF8(s);
1234 eval = [NSString stringWithUTF8String:(char*)s];
1236 CONVERT_TO_UTF8_FREE(s);
1244 - (BOOL)starRegisterToPasteboard:(byref NSPasteboard *)pboard
1246 if (VIsual_active && (State & NORMAL) && clip_star.available) {
1247 // If there is no pasteboard, return YES to indicate that there is text
1252 clip_copy_selection();
1254 // Get the text to put on the pasteboard.
1255 long_u llen = 0; char_u *str = 0;
1256 int type = clip_convert_selection(&str, &llen, &clip_star);
1260 // TODO: Avoid overflow.
1261 int len = (int)llen;
1263 if (output_conv.vc_type != CONV_NONE) {
1264 char_u *conv_str = string_convert(&output_conv, str, &len);
1272 NSString *string = [[NSString alloc]
1273 initWithBytes:str length:len encoding:NSUTF8StringEncoding];
1275 NSArray *types = [NSArray arrayWithObject:NSStringPboardType];
1276 [pboard declareTypes:types owner:nil];
1277 BOOL ok = [pboard setString:string forType:NSStringPboardType];
1288 - (oneway void)addReply:(in bycopy NSString *)reply
1289 server:(in byref id <MMVimServerProtocol>)server
1291 //NSLog(@"addReply:%@ server:%@", reply, (id)server);
1293 // Replies might come at any time and in any order so we keep them in an
1294 // array inside a dictionary with the send port used as key.
1296 NSConnection *conn = [(NSDistantObject*)server connectionForProxy];
1297 // HACK! Assume connection uses mach ports.
1298 int port = [(NSMachPort*)[conn sendPort] machPort];
1299 NSNumber *key = [NSNumber numberWithInt:port];
1301 NSMutableArray *replies = [serverReplyDict objectForKey:key];
1303 replies = [NSMutableArray array];
1304 [serverReplyDict setObject:replies forKey:key];
1307 [replies addObject:reply];
1310 - (void)addInput:(in bycopy NSString *)input
1311 client:(in byref id <MMVimClientProtocol>)client
1313 //NSLog(@"addInput:%@ client:%@", input, (id)client);
1315 [self addInput:input];
1316 [self addClient:(id)client];
1318 inputReceived = YES;
1321 - (NSString *)evaluateExpression:(in bycopy NSString *)expr
1322 client:(in byref id <MMVimClientProtocol>)client
1324 [self addClient:(id)client];
1325 return [self evaluateExpression:expr];
1328 - (void)registerServerWithName:(NSString *)name
1330 NSString *svrName = name;
1331 NSConnection *svrConn = [NSConnection defaultConnection];
1334 for (i = 0; i < MMServerMax; ++i) {
1335 NSString *connName = [self connectionNameFromServerName:svrName];
1337 if ([svrConn registerName:connName]) {
1338 //NSLog(@"Registered server with name: %@", svrName);
1340 // TODO: Set request/reply time-outs to something else?
1342 // Don't wait for requests (time-out means that the message is
1344 [svrConn setRequestTimeout:0];
1345 //[svrConn setReplyTimeout:MMReplyTimeout];
1346 [svrConn setRootObject:self];
1348 char_u *s = (char_u*)[svrName UTF8String];
1350 s = CONVERT_FROM_UTF8(s);
1352 // NOTE: 'serverName' is a global variable
1353 serverName = vim_strsave(s);
1355 CONVERT_FROM_UTF8_FREE(s);
1358 set_vim_var_string(VV_SEND_SERVER, serverName, -1);
1361 need_maketitle = TRUE;
1363 [self queueMessage:SetServerNameMsgID data:
1364 [svrName dataUsingEncoding:NSUTF8StringEncoding]];
1368 svrName = [NSString stringWithFormat:@"%@%d", name, i+1];
1372 - (BOOL)sendToServer:(NSString *)name string:(NSString *)string
1373 reply:(char_u **)reply port:(int *)port expression:(BOOL)expr
1376 // NOTE: If 'name' equals 'serverName' then the request is local (client
1377 // and server are the same). This case is not handled separately, so a
1378 // connection will be set up anyway (this simplifies the code).
1380 NSConnection *conn = [self connectionForServerName:name];
1383 char_u *s = (char_u*)[name UTF8String];
1385 s = CONVERT_FROM_UTF8(s);
1387 EMSG2(_(e_noserver), s);
1389 CONVERT_FROM_UTF8_FREE(s);
1396 // HACK! Assume connection uses mach ports.
1397 *port = [(NSMachPort*)[conn sendPort] machPort];
1400 id proxy = [conn rootProxy];
1401 [proxy setProtocolForProxy:@protocol(MMVimServerProtocol)];
1405 NSString *eval = [proxy evaluateExpression:string client:self];
1408 char_u *r = (char_u*)[eval UTF8String];
1410 r = CONVERT_FROM_UTF8(r);
1412 *reply = vim_strsave(r);
1414 CONVERT_FROM_UTF8_FREE(r);
1417 *reply = vim_strsave((char_u*)_(e_invexprmsg));
1424 [proxy addInput:string client:self];
1427 @catch (NSException *e) {
1428 NSLog(@"WARNING: Caught exception in %s: \"%@\"", _cmd, e);
1435 - (NSArray *)serverList
1437 NSArray *list = nil;
1439 if ([self connection]) {
1440 id proxy = [connection rootProxy];
1441 [proxy setProtocolForProxy:@protocol(MMAppProtocol)];
1444 list = [proxy serverList];
1446 @catch (NSException *e) {
1447 NSLog(@"Exception caught when listing servers: \"%@\"", e);
1450 EMSG(_("E???: No connection to MacVim, server listing not possible."));
1456 - (NSString *)peekForReplyOnPort:(int)port
1458 //NSLog(@"%s%d", _cmd, port);
1460 NSNumber *key = [NSNumber numberWithInt:port];
1461 NSMutableArray *replies = [serverReplyDict objectForKey:key];
1462 if (replies && [replies count]) {
1463 //NSLog(@" %d replies, topmost is: %@", [replies count],
1464 // [replies objectAtIndex:0]);
1465 return [replies objectAtIndex:0];
1468 //NSLog(@" No replies");
1472 - (NSString *)waitForReplyOnPort:(int)port
1474 //NSLog(@"%s%d", _cmd, port);
1476 NSConnection *conn = [self connectionForServerPort:port];
1480 NSNumber *key = [NSNumber numberWithInt:port];
1481 NSMutableArray *replies = nil;
1482 NSString *reply = nil;
1484 // Wait for reply as long as the connection to the server is valid (unless
1485 // user interrupts wait with Ctrl-C).
1486 while (!got_int && [conn isValid] &&
1487 !(replies = [serverReplyDict objectForKey:key])) {
1488 [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
1489 beforeDate:[NSDate distantFuture]];
1493 if ([replies count] > 0) {
1494 reply = [[replies objectAtIndex:0] retain];
1495 //NSLog(@" Got reply: %@", reply);
1496 [replies removeObjectAtIndex:0];
1497 [reply autorelease];
1500 if ([replies count] == 0)
1501 [serverReplyDict removeObjectForKey:key];
1507 - (BOOL)sendReply:(NSString *)reply toPort:(int)port
1509 id client = [clientProxyDict objectForKey:[NSNumber numberWithInt:port]];
1512 //NSLog(@"sendReply:%@ toPort:%d", reply, port);
1513 [client addReply:reply server:self];
1516 @catch (NSException *e) {
1517 NSLog(@"WARNING: Exception caught in %s: \"%@\"", _cmd, e);
1520 EMSG2(_("E???: server2client failed; no client with id 0x%x"), port);
1530 @implementation MMBackend (Private)
1532 - (void)processInputQueue
1534 // NOTE: One of the input events may cause this method to be called
1535 // recursively, so copy the input queue to a local variable and clear it
1536 // before starting to process input events (otherwise we could get stuck in
1537 // an endless loop).
1538 NSArray *q = [inputQueue copy];
1539 unsigned i, count = [q count];
1541 [inputQueue removeAllObjects];
1543 for (i = 0; i < count-1; i += 2) {
1544 int msgid = [[q objectAtIndex:i] intValue];
1545 id data = [q objectAtIndex:i+1];
1546 if ([data isEqual:[NSNull null]])
1549 //NSLog(@"(%d) %s:%s", i, _cmd, MessageStrings[msgid]);
1550 [self handleInputEvent:msgid data:data];
1554 //NSLog(@"Clear input event queue");
1557 - (void)handleInputEvent:(int)msgid data:(NSData *)data
1559 // NOTE: Be careful with what you do in this method. Ideally, a message
1560 // should be handled by adding something to the input buffer and returning
1561 // immediately. If you call a Vim function then it should not enter a loop
1562 // waiting for key presses or in any other way block the process. The
1563 // reason for this being that only one message can be processed at a time,
1564 // so if another message is received while processing, then the new message
1565 // is dropped. See also the comment in processInput:data:.
1567 //NSLog(@"%s%s", _cmd, MessageStrings[msgid]);
1569 if (SelectTabMsgID == msgid) {
1571 const void *bytes = [data bytes];
1572 int idx = *((int*)bytes) + 1;
1573 //NSLog(@"Selecting tab %d", idx);
1574 send_tabline_event(idx);
1575 } else if (CloseTabMsgID == msgid) {
1577 const void *bytes = [data bytes];
1578 int idx = *((int*)bytes) + 1;
1579 //NSLog(@"Closing tab %d", idx);
1580 send_tabline_menu_event(idx, TABLINE_MENU_CLOSE);
1581 } else if (AddNewTabMsgID == msgid) {
1582 //NSLog(@"Adding new tab");
1583 send_tabline_menu_event(0, TABLINE_MENU_NEW);
1584 } else if (DraggedTabMsgID == msgid) {
1586 const void *bytes = [data bytes];
1587 // NOTE! The destination index is 0 based, so do not add 1 to make it 1
1589 int idx = *((int*)bytes);
1592 } else if (SetTextDimensionsMsgID == msgid || LiveResizeMsgID == msgid) {
1594 const void *bytes = [data bytes];
1595 int rows = *((int*)bytes); bytes += sizeof(int);
1596 int cols = *((int*)bytes); bytes += sizeof(int);
1598 // NOTE! Vim doesn't call gui_mch_set_shellsize() after
1599 // gui_resize_shell(), so we have to manually set the rows and columns
1600 // here. (MacVim doesn't change the rows and columns to avoid
1601 // inconsistent states between Vim and MacVim.)
1602 [self queueMessage:msgid data:data];
1604 //NSLog(@"[VimTask] Resizing shell to %dx%d.", cols, rows);
1605 gui_resize_shell(cols, rows);
1606 } else if (ExecuteMenuMsgID == msgid) {
1608 const void *bytes = [data bytes];
1609 int tag = *((int*)bytes); bytes += sizeof(int);
1611 vimmenu_T *menu = (vimmenu_T*)tag;
1612 // TODO! Make sure 'menu' is a valid menu pointer!
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 [outputQueue addObject:[NSData dataWithBytes:&msgid length:sizeof(int)]];
1831 [outputQueue addObject:data];
1833 [outputQueue addObject:[NSData data]];
1836 - (void)connectionDidDie:(NSNotification *)notification
1838 // If the main connection to MacVim is lost this means that MacVim was
1839 // either quit (by the user chosing Quit on the MacVim menu), or it has
1840 // crashed. In the former case the flag 'isTerminating' is set and we then
1841 // quit cleanly; in the latter case we make sure the swap files are left
1844 //NSLog(@"%s isTerminating=%d", _cmd, isTerminating);
1848 getout_preserve_modified(1);
1851 - (void)blinkTimerFired:(NSTimer *)timer
1853 NSTimeInterval timeInterval = 0;
1855 [blinkTimer release];
1858 if (MMBlinkStateOn == blinkState) {
1859 gui_undraw_cursor();
1860 blinkState = MMBlinkStateOff;
1861 timeInterval = blinkOffInterval;
1862 } else if (MMBlinkStateOff == blinkState) {
1863 gui_update_cursor(TRUE, FALSE);
1864 blinkState = MMBlinkStateOn;
1865 timeInterval = blinkOnInterval;
1868 if (timeInterval > 0) {
1870 [[NSTimer scheduledTimerWithTimeInterval:timeInterval target:self
1871 selector:@selector(blinkTimerFired:)
1872 userInfo:nil repeats:NO] retain];
1873 [self flushQueue:YES];
1877 - (void)focusChange:(BOOL)on
1879 gui_focus_change(on);
1882 - (void)handleToggleToolbar
1884 // If 'go' contains 'T', then remove it, else add it.
1886 char_u go[sizeof(GO_ALL)+2];
1891 p = vim_strchr(go, GO_TOOLBAR);
1895 char_u *end = go + len;
1901 go[len] = GO_TOOLBAR;
1905 set_option_value((char_u*)"guioptions", 0, go, 0);
1907 // Force screen redraw (does it have to be this complicated?).
1908 redraw_all_later(CLEAR);
1909 update_screen(NOT_VALID);
1912 gui_update_cursor(FALSE, FALSE);
1916 - (void)handleScrollbarEvent:(NSData *)data
1920 const void *bytes = [data bytes];
1921 long ident = *((long*)bytes); bytes += sizeof(long);
1922 int hitPart = *((int*)bytes); bytes += sizeof(int);
1923 float fval = *((float*)bytes); bytes += sizeof(float);
1924 scrollbar_T *sb = gui_find_scrollbar(ident);
1927 scrollbar_T *sb_info = sb->wp ? &sb->wp->w_scrollbars[0] : sb;
1928 long value = sb_info->value;
1929 long size = sb_info->size;
1930 long max = sb_info->max;
1931 BOOL isStillDragging = NO;
1932 BOOL updateKnob = YES;
1935 case NSScrollerDecrementPage:
1936 value -= (size > 2 ? size - 2 : 1);
1938 case NSScrollerIncrementPage:
1939 value += (size > 2 ? size - 2 : 1);
1941 case NSScrollerDecrementLine:
1944 case NSScrollerIncrementLine:
1947 case NSScrollerKnob:
1948 isStillDragging = YES;
1950 case NSScrollerKnobSlot:
1951 value = (long)(fval * (max - size + 1));
1958 //NSLog(@"value %d -> %d", sb_info->value, value);
1959 gui_drag_scrollbar(sb, value, isStillDragging);
1962 // Dragging the knob or option+clicking automatically updates
1963 // the knob position (on the actual NSScroller), so we only
1964 // need to set the knob position in the other cases.
1966 // Update both the left&right vertical scrollbars.
1967 long identLeft = sb->wp->w_scrollbars[SBAR_LEFT].ident;
1968 long identRight = sb->wp->w_scrollbars[SBAR_RIGHT].ident;
1969 [self setScrollbarThumbValue:value size:size max:max
1970 identifier:identLeft];
1971 [self setScrollbarThumbValue:value size:size max:max
1972 identifier:identRight];
1974 // Update the horizontal scrollbar.
1975 [self setScrollbarThumbValue:value size:size max:max
1982 - (void)handleSetFont:(NSData *)data
1986 const void *bytes = [data bytes];
1987 float pointSize = *((float*)bytes); bytes += sizeof(float);
1988 //unsigned len = *((unsigned*)bytes); bytes += sizeof(unsigned);
1989 bytes += sizeof(unsigned); // len not used
1991 NSMutableString *name = [NSMutableString stringWithUTF8String:bytes];
1992 [name appendString:[NSString stringWithFormat:@":h%.2f", pointSize]];
1993 char_u *s = (char_u*)[name UTF8String];
1996 s = CONVERT_FROM_UTF8(s);
1999 set_option_value((char_u*)"guifont", 0, s, 0);
2002 CONVERT_FROM_UTF8_FREE(s);
2005 // Force screen redraw (does it have to be this complicated?).
2006 redraw_all_later(CLEAR);
2007 update_screen(NOT_VALID);
2010 gui_update_cursor(FALSE, FALSE);
2014 - (void)handleDropFiles:(NSData *)data
2016 // TODO: Get rid of this method; instead use Vim script directly. At the
2017 // moment I know how to do this to open files in tabs, but I'm not sure how
2018 // to add the filenames to the command line when in command line mode.
2023 const void *bytes = [data bytes];
2024 const void *end = [data bytes] + [data length];
2025 BOOL forceOpen = *((BOOL*)bytes); bytes += sizeof(BOOL);
2026 int n = *((int*)bytes); bytes += sizeof(int);
2028 if (!forceOpen && (State & CMDLINE)) {
2029 // HACK! If Vim is in command line mode then the files names
2030 // should be added to the command line, instead of opening the
2031 // files in tabs (unless forceOpen is set). This is taken care of by
2032 // gui_handle_drop().
2033 char_u **fnames = (char_u **)alloc(n * sizeof(char_u *));
2036 while (bytes < end && i < n) {
2037 int len = *((int*)bytes); bytes += sizeof(int);
2038 char_u *s = (char_u*)bytes;
2040 s = CONVERT_FROM_UTF8(s);
2042 fnames[i++] = vim_strsave(s);
2044 CONVERT_FROM_UTF8_FREE(s);
2049 // NOTE! This function will free 'fnames'.
2050 // HACK! It is assumed that the 'x' and 'y' arguments are
2051 // unused when in command line mode.
2052 gui_handle_drop(0, 0, 0, fnames, i < n ? i : n);
2055 // HACK! I'm not sure how to get Vim to open a list of files in
2056 // tabs, so instead I create a ':tab drop' command with all the
2057 // files to open and execute it.
2058 NSMutableString *cmd = [NSMutableString stringWithString:@":tab drop"];
2061 for (i = 0; i < n && bytes < end; ++i) {
2062 int len = *((int*)bytes); bytes += sizeof(int);
2063 NSString *file = [NSString stringWithUTF8String:bytes];
2064 file = [file stringByEscapingSpecialFilenameCharacters];
2067 [cmd appendString:@" "];
2068 [cmd appendString:file];
2071 // By going to the last tabpage we ensure that the new tabs will
2072 // appear last (if this call is left out, the taborder becomes
2076 char_u *s = (char_u*)[cmd UTF8String];
2078 s = CONVERT_FROM_UTF8(s);
2082 CONVERT_FROM_UTF8_FREE(s);
2085 // Force screen redraw (does it have to be this complicated?).
2086 // (This code was taken from the end of gui_handle_drop().)
2087 update_screen(NOT_VALID);
2090 gui_update_cursor(FALSE, FALSE);
2097 - (void)handleDropString:(NSData *)data
2102 char_u dropkey[3] = { CSI, KS_EXTRA, (char_u)KE_DROP };
2103 const void *bytes = [data bytes];
2104 int len = *((int*)bytes); bytes += sizeof(int);
2105 NSMutableString *string = [NSMutableString stringWithUTF8String:bytes];
2107 // Replace unrecognized end-of-line sequences with \x0a (line feed).
2108 NSRange range = { 0, [string length] };
2109 unsigned n = [string replaceOccurrencesOfString:@"\x0d\x0a"
2110 withString:@"\x0a" options:0
2113 n = [string replaceOccurrencesOfString:@"\x0d" withString:@"\x0a"
2114 options:0 range:range];
2117 len = [string lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
2118 char_u *s = (char_u*)[string UTF8String];
2120 if (input_conv.vc_type != CONV_NONE)
2121 s = string_convert(&input_conv, s, &len);
2123 dnd_yank_drag_data(s, len);
2125 if (input_conv.vc_type != CONV_NONE)
2128 add_to_input_buf(dropkey, sizeof(dropkey));
2132 - (void)handleOdbEdit:(NSData *)data
2134 #ifdef FEAT_ODB_EDITOR
2135 const void *bytes = [data bytes];
2137 OSType serverID = *((OSType*)bytes); bytes += sizeof(OSType);
2139 char_u *path = NULL;
2140 int pathLen = *((int*)bytes); bytes += sizeof(int);
2142 path = (char_u*)bytes;
2145 path = CONVERT_FROM_UTF8(path);
2149 NSAppleEventDescriptor *token = nil;
2150 DescType tokenType = *((DescType*)bytes); bytes += sizeof(DescType);
2151 int descLen = *((int*)bytes); bytes += sizeof(int);
2153 token = [NSAppleEventDescriptor descriptorWithDescriptorType:tokenType
2159 unsigned i, numFiles = *((unsigned*)bytes); bytes += sizeof(unsigned);
2160 for (i = 0; i < numFiles; ++i) {
2161 int len = *((int*)bytes); bytes += sizeof(int);
2162 char_u *filename = (char_u*)bytes;
2164 filename = CONVERT_FROM_UTF8(filename);
2166 buf_T *buf = buflist_findname(filename);
2168 if (buf->b_odb_token) {
2169 [(NSAppleEventDescriptor*)(buf->b_odb_token) release];
2170 buf->b_odb_token = NULL;
2173 if (buf->b_odb_fname) {
2174 vim_free(buf->b_odb_fname);
2175 buf->b_odb_fname = NULL;
2178 buf->b_odb_server_id = serverID;
2181 buf->b_odb_token = [token retain];
2183 buf->b_odb_fname = vim_strsave(path);
2185 NSLog(@"WARNING: Could not find buffer '%s' for ODB editing.",
2190 CONVERT_FROM_UTF8_FREE(filename);
2195 CONVERT_FROM_UTF8_FREE(path);
2197 #endif // FEAT_ODB_EDITOR
2200 - (void)handleXcodeMod:(NSData *)data
2203 const void *bytes = [data bytes];
2204 DescType type = *((DescType*)bytes); bytes += sizeof(DescType);
2205 unsigned len = *((unsigned*)bytes); bytes += sizeof(unsigned);
2209 NSAppleEventDescriptor *replyEvent = [NSAppleEventDescriptor
2210 descriptorWithDescriptorType:type
2216 - (BOOL)checkForModifiedBuffers
2219 for (buf = firstbuf; buf != NULL; buf = buf->b_next) {
2220 if (bufIsChanged(buf)) {
2228 - (void)addInput:(NSString *)input
2230 char_u *s = (char_u*)[input UTF8String];
2233 s = CONVERT_FROM_UTF8(s);
2236 server_to_input_buf(s);
2239 CONVERT_FROM_UTF8_FREE(s);
2243 @end // MMBackend (Private)
2248 @implementation MMBackend (ClientServer)
2250 - (NSString *)connectionNameFromServerName:(NSString *)name
2252 NSString *bundleIdentifier = [[NSBundle mainBundle] bundleIdentifier];
2254 return [[NSString stringWithFormat:@"%@.%@", bundleIdentifier, name]
2258 - (NSConnection *)connectionForServerName:(NSString *)name
2260 // TODO: Try 'name%d' if 'name' fails.
2261 NSString *connName = [self connectionNameFromServerName:name];
2262 NSConnection *svrConn = [connectionNameDict objectForKey:connName];
2265 svrConn = [NSConnection connectionWithRegisteredName:connName
2267 // Try alternate server...
2268 if (!svrConn && alternateServerName) {
2269 //NSLog(@" trying to connect to alternate server: %@",
2270 // alternateServerName);
2271 connName = [self connectionNameFromServerName:alternateServerName];
2272 svrConn = [NSConnection connectionWithRegisteredName:connName
2276 // Try looking for alternate servers...
2278 //NSLog(@" looking for alternate servers...");
2279 NSString *alt = [self alternateServerNameForName:name];
2280 if (alt != alternateServerName) {
2281 //NSLog(@" found alternate server: %@", string);
2282 [alternateServerName release];
2283 alternateServerName = [alt copy];
2287 // Try alternate server again...
2288 if (!svrConn && alternateServerName) {
2289 //NSLog(@" trying to connect to alternate server: %@",
2290 // alternateServerName);
2291 connName = [self connectionNameFromServerName:alternateServerName];
2292 svrConn = [NSConnection connectionWithRegisteredName:connName
2297 [connectionNameDict setObject:svrConn forKey:connName];
2299 //NSLog(@"Adding %@ as connection observer for %@", self, svrConn);
2300 [[NSNotificationCenter defaultCenter] addObserver:self
2301 selector:@selector(serverConnectionDidDie:)
2302 name:NSConnectionDidDieNotification object:svrConn];
2309 - (NSConnection *)connectionForServerPort:(int)port
2312 NSEnumerator *e = [connectionNameDict objectEnumerator];
2314 while ((conn = [e nextObject])) {
2315 // HACK! Assume connection uses mach ports.
2316 if (port == [(NSMachPort*)[conn sendPort] machPort])
2323 - (void)serverConnectionDidDie:(NSNotification *)notification
2325 //NSLog(@"%s%@", _cmd, notification);
2327 NSConnection *svrConn = [notification object];
2329 //NSLog(@"Removing %@ as connection observer from %@", self, svrConn);
2330 [[NSNotificationCenter defaultCenter]
2332 name:NSConnectionDidDieNotification
2335 [connectionNameDict removeObjectsForKeys:
2336 [connectionNameDict allKeysForObject:svrConn]];
2338 // HACK! Assume connection uses mach ports.
2339 int port = [(NSMachPort*)[svrConn sendPort] machPort];
2340 NSNumber *key = [NSNumber numberWithInt:port];
2342 [clientProxyDict removeObjectForKey:key];
2343 [serverReplyDict removeObjectForKey:key];
2346 - (void)addClient:(NSDistantObject *)client
2348 NSConnection *conn = [client connectionForProxy];
2349 // HACK! Assume connection uses mach ports.
2350 int port = [(NSMachPort*)[conn sendPort] machPort];
2351 NSNumber *key = [NSNumber numberWithInt:port];
2353 if (![clientProxyDict objectForKey:key]) {
2354 [client setProtocolForProxy:@protocol(MMVimClientProtocol)];
2355 [clientProxyDict setObject:client forKey:key];
2358 // NOTE: 'clientWindow' is a global variable which is used by <client>
2359 clientWindow = port;
2362 - (NSString *)alternateServerNameForName:(NSString *)name
2364 if (!(name && [name length] > 0))
2367 // Only look for alternates if 'name' doesn't end in a digit.
2368 unichar lastChar = [name characterAtIndex:[name length]-1];
2369 if (lastChar >= '0' && lastChar <= '9')
2372 // Look for alternates among all current servers.
2373 NSArray *list = [self serverList];
2374 if (!(list && [list count] > 0))
2377 // Filter out servers starting with 'name' and ending with a number. The
2378 // (?i) pattern ensures that the match is case insensitive.
2379 NSString *pat = [NSString stringWithFormat:@"(?i)%@[0-9]+\\z", name];
2380 NSPredicate *pred = [NSPredicate predicateWithFormat:
2381 @"SELF MATCHES %@", pat];
2382 list = [list filteredArrayUsingPredicate:pred];
2383 if ([list count] > 0) {
2384 list = [list sortedArrayUsingSelector:@selector(serverNameCompare:)];
2385 return [list objectAtIndex:0];
2391 @end // MMBackend (ClientServer)
2396 @implementation NSString (MMServerNameCompare)
2397 - (NSComparisonResult)serverNameCompare:(NSString *)string
2399 return [self compare:string
2400 options:NSCaseInsensitiveSearch|NSNumericSearch];
2407 static int eventModifierFlagsToVimModMask(int modifierFlags)
2411 if (modifierFlags & NSShiftKeyMask)
2412 modMask |= MOD_MASK_SHIFT;
2413 if (modifierFlags & NSControlKeyMask)
2414 modMask |= MOD_MASK_CTRL;
2415 if (modifierFlags & NSAlternateKeyMask)
2416 modMask |= MOD_MASK_ALT;
2417 if (modifierFlags & NSCommandKeyMask)
2418 modMask |= MOD_MASK_CMD;
2423 static int vimModMaskToEventModifierFlags(int mods)
2427 if (mods & MOD_MASK_SHIFT)
2428 flags |= NSShiftKeyMask;
2429 if (mods & MOD_MASK_CTRL)
2430 flags |= NSControlKeyMask;
2431 if (mods & MOD_MASK_ALT)
2432 flags |= NSAlternateKeyMask;
2433 if (mods & MOD_MASK_CMD)
2434 flags |= NSCommandKeyMask;
2439 static int eventModifierFlagsToVimMouseModMask(int modifierFlags)
2443 if (modifierFlags & NSShiftKeyMask)
2444 modMask |= MOUSE_SHIFT;
2445 if (modifierFlags & NSControlKeyMask)
2446 modMask |= MOUSE_CTRL;
2447 if (modifierFlags & NSAlternateKeyMask)
2448 modMask |= MOUSE_ALT;
2453 static int eventButtonNumberToVimMouseButton(int buttonNumber)
2455 static int mouseButton[] = { MOUSE_LEFT, MOUSE_RIGHT, MOUSE_MIDDLE };
2457 return (buttonNumber >= 0 && buttonNumber < 3)
2458 ? mouseButton[buttonNumber] : -1;
2461 static int specialKeyToNSKey(int key)
2463 if (!IS_SPECIAL(key))
2470 { K_UP, NSUpArrowFunctionKey },
2471 { K_DOWN, NSDownArrowFunctionKey },
2472 { K_LEFT, NSLeftArrowFunctionKey },
2473 { K_RIGHT, NSRightArrowFunctionKey },
2474 { K_F1, NSF1FunctionKey },
2475 { K_F2, NSF2FunctionKey },
2476 { K_F3, NSF3FunctionKey },
2477 { K_F4, NSF4FunctionKey },
2478 { K_F5, NSF5FunctionKey },
2479 { K_F6, NSF6FunctionKey },
2480 { K_F7, NSF7FunctionKey },
2481 { K_F8, NSF8FunctionKey },
2482 { K_F9, NSF9FunctionKey },
2483 { K_F10, NSF10FunctionKey },
2484 { K_F11, NSF11FunctionKey },
2485 { K_F12, NSF12FunctionKey },
2486 { K_F13, NSF13FunctionKey },
2487 { K_F14, NSF14FunctionKey },
2488 { K_F15, NSF15FunctionKey },
2489 { K_F16, NSF16FunctionKey },
2490 { K_F17, NSF17FunctionKey },
2491 { K_F18, NSF18FunctionKey },
2492 { K_F19, NSF19FunctionKey },
2493 { K_F20, NSF20FunctionKey },
2494 { K_F21, NSF21FunctionKey },
2495 { K_F22, NSF22FunctionKey },
2496 { K_F23, NSF23FunctionKey },
2497 { K_F24, NSF24FunctionKey },
2498 { K_F25, NSF25FunctionKey },
2499 { K_F26, NSF26FunctionKey },
2500 { K_F27, NSF27FunctionKey },
2501 { K_F28, NSF28FunctionKey },
2502 { K_F29, NSF29FunctionKey },
2503 { K_F30, NSF30FunctionKey },
2504 { K_F31, NSF31FunctionKey },
2505 { K_F32, NSF32FunctionKey },
2506 { K_F33, NSF33FunctionKey },
2507 { K_F34, NSF34FunctionKey },
2508 { K_F35, NSF35FunctionKey },
2509 { K_DEL, NSBackspaceCharacter },
2510 { K_BS, NSDeleteCharacter },
2511 { K_HOME, NSHomeFunctionKey },
2512 { K_END, NSEndFunctionKey },
2513 { K_PAGEUP, NSPageUpFunctionKey },
2514 { K_PAGEDOWN, NSPageDownFunctionKey }
2518 for (i = 0; i < sizeof(sp2ns)/sizeof(sp2ns[0]); ++i) {
2519 if (sp2ns[i].special == key)
2520 return sp2ns[i].nskey;