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.");
290 id proxy = [connection rootProxy];
291 [proxy setProtocolForProxy:@protocol(MMAppProtocol)];
293 [[NSNotificationCenter defaultCenter] addObserver:self
294 selector:@selector(connectionDidDie:)
295 name:NSConnectionDidDieNotification object:connection];
297 int pid = [[NSProcessInfo processInfo] processIdentifier];
300 frontendProxy = [proxy connectBackend:self pid:pid];
302 @catch (NSException *e) {
303 NSLog(@"Exception caught when trying to connect backend: \"%@\"", e);
307 [frontendProxy retain];
308 [frontendProxy setProtocolForProxy:@protocol(MMAppProtocol)];
311 return connection && frontendProxy;
314 - (BOOL)openVimWindow
316 [self queueMessage:OpenVimWindowMsgID data:nil];
322 int type = ClearAllDrawType;
324 // Any draw commands in queue are effectively obsolete since this clearAll
325 // will negate any effect they have, therefore we may as well clear the
327 [drawData setLength:0];
329 [drawData appendBytes:&type length:sizeof(int)];
332 - (void)clearBlockFromRow:(int)row1 column:(int)col1
333 toRow:(int)row2 column:(int)col2
335 int type = ClearBlockDrawType;
337 [drawData appendBytes:&type length:sizeof(int)];
339 [drawData appendBytes:&defaultBackgroundColor length:sizeof(unsigned)];
340 [drawData appendBytes:&row1 length:sizeof(int)];
341 [drawData appendBytes:&col1 length:sizeof(int)];
342 [drawData appendBytes:&row2 length:sizeof(int)];
343 [drawData appendBytes:&col2 length:sizeof(int)];
346 - (void)deleteLinesFromRow:(int)row count:(int)count
347 scrollBottom:(int)bottom left:(int)left right:(int)right
349 int type = DeleteLinesDrawType;
351 [drawData appendBytes:&type length:sizeof(int)];
353 [drawData appendBytes:&defaultBackgroundColor length:sizeof(unsigned)];
354 [drawData appendBytes:&row length:sizeof(int)];
355 [drawData appendBytes:&count length:sizeof(int)];
356 [drawData appendBytes:&bottom length:sizeof(int)];
357 [drawData appendBytes:&left length:sizeof(int)];
358 [drawData appendBytes:&right length:sizeof(int)];
361 - (void)drawString:(char*)s length:(int)len row:(int)row column:(int)col
362 cells:(int)cells flags:(int)flags
364 if (len <= 0 || cells <= 0) return;
366 int type = DrawStringDrawType;
368 [drawData appendBytes:&type length:sizeof(int)];
370 [drawData appendBytes:&backgroundColor length:sizeof(unsigned)];
371 [drawData appendBytes:&foregroundColor length:sizeof(unsigned)];
372 [drawData appendBytes:&specialColor length:sizeof(unsigned)];
373 [drawData appendBytes:&row length:sizeof(int)];
374 [drawData appendBytes:&col length:sizeof(int)];
375 [drawData appendBytes:&cells length:sizeof(int)];
376 [drawData appendBytes:&flags length:sizeof(int)];
377 [drawData appendBytes:&len length:sizeof(int)];
378 [drawData appendBytes:s length:len];
381 - (void)insertLinesFromRow:(int)row count:(int)count
382 scrollBottom:(int)bottom left:(int)left right:(int)right
384 int type = InsertLinesDrawType;
386 [drawData appendBytes:&type length:sizeof(int)];
388 [drawData appendBytes:&defaultBackgroundColor length:sizeof(unsigned)];
389 [drawData appendBytes:&row length:sizeof(int)];
390 [drawData appendBytes:&count length:sizeof(int)];
391 [drawData appendBytes:&bottom length:sizeof(int)];
392 [drawData appendBytes:&left length:sizeof(int)];
393 [drawData appendBytes:&right length:sizeof(int)];
396 - (void)drawCursorAtRow:(int)row column:(int)col shape:(int)shape
397 fraction:(int)percent color:(int)color
399 int type = DrawCursorDrawType;
400 unsigned uc = MM_COLOR(color);
402 [drawData appendBytes:&type length:sizeof(int)];
404 [drawData appendBytes:&uc length:sizeof(unsigned)];
405 [drawData appendBytes:&row length:sizeof(int)];
406 [drawData appendBytes:&col length:sizeof(int)];
407 [drawData appendBytes:&shape length:sizeof(int)];
408 [drawData appendBytes:&percent length:sizeof(int)];
413 // Tend to the run loop, returning immediately if there are no events
415 [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
416 beforeDate:[NSDate distantPast]];
419 // Keyboard and mouse input is handled directly, other input is queued and
420 // processed here. This call may enter a blocking loop.
421 if ([inputQueue count] > 0)
422 [self processInputQueue];
426 - (void)flushQueue:(BOOL)force
428 // NOTE! This method gets called a lot; if we were to flush every time it
429 // got called MacVim would feel unresponsive. So there is a time out which
430 // ensures that the queue isn't flushed too often.
431 if (!force && lastFlushDate && -[lastFlushDate timeIntervalSinceNow]
432 < MMFlushTimeoutInterval
433 && [drawData length] < MMFlushQueueLenHint)
436 if ([drawData length] > 0) {
437 // HACK! Detect changes to 'guifontwide'.
438 if (gui.wide_font != (GuiFont)oldWideFont) {
439 [oldWideFont release];
440 oldWideFont = [(NSFont*)gui.wide_font retain];
441 [self setWideFont:oldWideFont];
444 int type = SetCursorPosDrawType;
445 [drawData appendBytes:&type length:sizeof(type)];
446 [drawData appendBytes:&gui.row length:sizeof(gui.row)];
447 [drawData appendBytes:&gui.col length:sizeof(gui.col)];
449 [self queueMessage:BatchDrawMsgID data:[drawData copy]];
450 [drawData setLength:0];
453 if ([outputQueue count] > 0) {
455 [frontendProxy processCommandQueue:outputQueue];
457 @catch (NSException *e) {
458 NSLog(@"Exception caught when processing command queue: \"%@\"", e);
461 [outputQueue removeAllObjects];
463 [lastFlushDate release];
464 lastFlushDate = [[NSDate date] retain];
468 - (BOOL)waitForInput:(int)milliseconds
470 //NSLog(@"|ENTER| %s%d", _cmd, milliseconds);
472 // Only start the run loop if the input queue is empty, otherwise process
473 // the input first so that the input on queue isn't delayed.
474 if ([inputQueue count]) {
477 NSDate *date = milliseconds > 0 ?
478 [NSDate dateWithTimeIntervalSinceNow:.001*milliseconds] :
479 [NSDate distantFuture];
481 [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
485 // I know of no way to figure out if the run loop exited because input was
486 // found or because of a time out, so I need to manually indicate when
487 // input was received in processInput:data: and then reset it every time
489 BOOL yn = inputReceived;
492 // Keyboard and mouse input is handled directly, other input is queued and
493 // processed here. This call may enter a blocking loop.
494 if ([inputQueue count] > 0)
495 [self processInputQueue];
497 //NSLog(@"|LEAVE| %s input=%d", _cmd, yn);
503 #ifdef MAC_CLIENTSERVER
504 // The default connection is used for the client/server code.
505 [[NSConnection defaultConnection] setRootObject:nil];
506 [[NSConnection defaultConnection] invalidate];
509 // By invalidating the NSConnection the MMWindowController immediately
510 // finds out that the connection is down and as a result
511 // [MMWindowController connectionDidDie:] is invoked.
512 //NSLog(@"%@ %s", [self className], _cmd);
513 [[NSNotificationCenter defaultCenter] removeObserver:self];
514 [connection invalidate];
516 if (fontContainerRef) {
517 ATSFontDeactivate(fontContainerRef, NULL, kATSOptionFlagsDefault);
518 fontContainerRef = 0;
523 - (void)selectTab:(int)index
525 //NSLog(@"%s%d", _cmd, index);
528 NSData *data = [NSData dataWithBytes:&index length:sizeof(int)];
529 [self queueMessage:SelectTabMsgID data:data];
534 //NSLog(@"%s", _cmd);
536 NSMutableData *data = [NSMutableData data];
538 int idx = tabpage_index(curtab) - 1;
539 [data appendBytes:&idx length:sizeof(int)];
542 for (tp = first_tabpage; tp != NULL; tp = tp->tp_next) {
543 // This function puts the label of the tab in the global 'NameBuff'.
544 get_tabline_label(tp, FALSE);
545 char_u *s = NameBuff;
547 if (len <= 0) continue;
550 s = CONVERT_TO_UTF8(s);
553 // Count the number of windows in the tabpage.
554 //win_T *wp = tp->tp_firstwin;
556 //for (wincount = 0; wp != NULL; wp = wp->w_next, ++wincount);
558 //[data appendBytes:&wincount length:sizeof(int)];
559 [data appendBytes:&len length:sizeof(int)];
560 [data appendBytes:s length:len];
563 CONVERT_TO_UTF8_FREE(s);
567 [self queueMessage:UpdateTabBarMsgID data:data];
570 - (BOOL)tabBarVisible
572 return tabBarVisible;
575 - (void)showTabBar:(BOOL)enable
577 tabBarVisible = enable;
579 int msgid = enable ? ShowTabBarMsgID : HideTabBarMsgID;
580 [self queueMessage:msgid data:nil];
583 - (void)setRows:(int)rows columns:(int)cols
585 //NSLog(@"[VimTask] setRows:%d columns:%d", rows, cols);
587 int dim[] = { rows, cols };
588 NSData *data = [NSData dataWithBytes:&dim length:2*sizeof(int)];
590 [self queueMessage:SetTextDimensionsMsgID data:data];
593 - (void)setWindowTitle:(char *)title
595 NSMutableData *data = [NSMutableData data];
596 int len = strlen(title);
597 if (len <= 0) return;
599 [data appendBytes:&len length:sizeof(int)];
600 [data appendBytes:title length:len];
602 [self queueMessage:SetWindowTitleMsgID data:data];
605 - (char *)browseForFileInDirectory:(char *)dir title:(char *)title
608 //NSLog(@"browseForFileInDirectory:%s title:%s saving:%d", dir, title,
612 NSString *ds = dir ? [NSString stringWithUTF8String:dir] : nil;
613 NSString *ts = title ? [NSString stringWithUTF8String:title] : nil;
615 [frontendProxy showSavePanelForDirectory:ds title:ts saving:saving];
617 // Wait until a reply is sent from MMVimController.
618 [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
619 beforeDate:[NSDate distantFuture]];
621 if (dialogReturn && [dialogReturn isKindOfClass:[NSString class]]) {
622 char_u *ret = (char_u*)[dialogReturn UTF8String];
624 ret = CONVERT_FROM_UTF8(ret);
626 s = vim_strsave(ret);
628 CONVERT_FROM_UTF8_FREE(ret);
632 [dialogReturn release]; dialogReturn = nil;
634 @catch (NSException *e) {
635 NSLog(@"Exception caught when showing save panel: \"%@\"", e);
641 - (oneway void)setDialogReturn:(in bycopy id)obj
643 // NOTE: This is called by
644 // - [MMVimController panelDidEnd:::], and
645 // - [MMVimController alertDidEnd:::],
646 // to indicate that a save/open panel or alert has finished.
648 if (obj != dialogReturn) {
649 [dialogReturn release];
650 dialogReturn = [obj retain];
654 - (int)presentDialogWithType:(int)type title:(char *)title message:(char *)msg
655 buttons:(char *)btns textField:(char *)txtfield
658 NSString *message = nil, *text = nil, *textFieldString = nil;
659 NSArray *buttons = nil;
660 int style = NSInformationalAlertStyle;
662 if (VIM_WARNING == type) style = NSWarningAlertStyle;
663 else if (VIM_ERROR == type) style = NSCriticalAlertStyle;
666 NSString *btnString = [NSString stringWithUTF8String:btns];
667 buttons = [btnString componentsSeparatedByString:@"\n"];
670 message = [NSString stringWithUTF8String:title];
672 text = [NSString stringWithUTF8String:msg];
674 // HACK! If there is a '\n\n' or '\n' sequence in the message, then
675 // make the part up to there into the title. We only do this
676 // because Vim has lots of dialogs without a title and they look
678 // TODO: Fix the actual dialog texts.
679 NSRange eolRange = [text rangeOfString:@"\n\n"];
680 if (NSNotFound == eolRange.location)
681 eolRange = [text rangeOfString:@"\n"];
682 if (NSNotFound != eolRange.location) {
683 message = [text substringToIndex:eolRange.location];
684 text = [text substringFromIndex:NSMaxRange(eolRange)];
689 textFieldString = [NSString stringWithUTF8String:txtfield];
692 [frontendProxy presentDialogWithStyle:style message:message
693 informativeText:text buttonTitles:buttons
694 textFieldString:textFieldString];
696 // Wait until a reply is sent from MMVimController.
697 [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
698 beforeDate:[NSDate distantFuture]];
700 if (dialogReturn && [dialogReturn isKindOfClass:[NSArray class]]
701 && [dialogReturn count]) {
702 retval = [[dialogReturn objectAtIndex:0] intValue];
703 if (txtfield && [dialogReturn count] > 1) {
704 NSString *retString = [dialogReturn objectAtIndex:1];
705 char_u *ret = (char_u*)[retString UTF8String];
707 ret = CONVERT_FROM_UTF8(ret);
709 vim_strncpy((char_u*)txtfield, ret, IOSIZE - 1);
711 CONVERT_FROM_UTF8_FREE(ret);
716 [dialogReturn release]; dialogReturn = nil;
718 @catch (NSException *e) {
719 NSLog(@"Exception caught while showing alert dialog: \"%@\"", e);
725 - (void)addMenuWithTag:(int)tag parent:(int)parentTag name:(char *)name
728 //NSLog(@"addMenuWithTag:%d parent:%d name:%s atIndex:%d", tag, parentTag,
731 int namelen = name ? strlen(name) : 0;
732 NSMutableData *data = [NSMutableData data];
734 [data appendBytes:&tag length:sizeof(int)];
735 [data appendBytes:&parentTag length:sizeof(int)];
736 [data appendBytes:&namelen length:sizeof(int)];
737 if (namelen > 0) [data appendBytes:name length:namelen];
738 [data appendBytes:&index length:sizeof(int)];
740 [self queueMessage:AddMenuMsgID data:data];
743 - (void)addMenuItemWithTag:(int)tag parent:(int)parentTag name:(char *)name
744 tip:(char *)tip icon:(char *)icon
745 keyEquivalent:(int)key modifiers:(int)mods
746 action:(NSString *)action atIndex:(int)index
748 //NSLog(@"addMenuItemWithTag:%d parent:%d name:%s tip:%s atIndex:%d", tag,
749 // parentTag, name, tip, index);
751 int namelen = name ? strlen(name) : 0;
752 int tiplen = tip ? strlen(tip) : 0;
753 int iconlen = icon ? strlen(icon) : 0;
754 int eventFlags = vimModMaskToEventModifierFlags(mods);
755 int actionlen = [action lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
756 NSMutableData *data = [NSMutableData data];
758 key = specialKeyToNSKey(key);
760 [data appendBytes:&tag length:sizeof(int)];
761 [data appendBytes:&parentTag length:sizeof(int)];
762 [data appendBytes:&namelen length:sizeof(int)];
763 if (namelen > 0) [data appendBytes:name length:namelen];
764 [data appendBytes:&tiplen length:sizeof(int)];
765 if (tiplen > 0) [data appendBytes:tip length:tiplen];
766 [data appendBytes:&iconlen length:sizeof(int)];
767 if (iconlen > 0) [data appendBytes:icon length:iconlen];
768 [data appendBytes:&actionlen length:sizeof(int)];
769 if (actionlen > 0) [data appendBytes:[action UTF8String] length:actionlen];
770 [data appendBytes:&index length:sizeof(int)];
771 [data appendBytes:&key length:sizeof(int)];
772 [data appendBytes:&eventFlags length:sizeof(int)];
774 [self queueMessage:AddMenuItemMsgID data:data];
777 - (void)removeMenuItemWithTag:(int)tag
779 NSMutableData *data = [NSMutableData data];
780 [data appendBytes:&tag length:sizeof(int)];
782 [self queueMessage:RemoveMenuItemMsgID data:data];
785 - (void)enableMenuItemWithTag:(int)tag state:(int)enabled
787 NSMutableData *data = [NSMutableData data];
789 [data appendBytes:&tag length:sizeof(int)];
790 [data appendBytes:&enabled length:sizeof(int)];
792 [self queueMessage:EnableMenuItemMsgID data:data];
795 - (void)showPopupMenuWithName:(char *)name atMouseLocation:(BOOL)mouse
797 int len = strlen(name);
798 int row = -1, col = -1;
800 if (len <= 0) return;
802 if (!mouse && curwin) {
803 row = curwin->w_wrow;
804 col = curwin->w_wcol;
807 NSMutableData *data = [NSMutableData data];
809 [data appendBytes:&row length:sizeof(int)];
810 [data appendBytes:&col length:sizeof(int)];
811 [data appendBytes:&len length:sizeof(int)];
812 [data appendBytes:name length:len];
814 [self queueMessage:ShowPopupMenuMsgID data:data];
817 - (void)showToolbar:(int)enable flags:(int)flags
819 NSMutableData *data = [NSMutableData data];
821 [data appendBytes:&enable length:sizeof(int)];
822 [data appendBytes:&flags length:sizeof(int)];
824 [self queueMessage:ShowToolbarMsgID data:data];
827 - (void)createScrollbarWithIdentifier:(long)ident type:(int)type
829 NSMutableData *data = [NSMutableData data];
831 [data appendBytes:&ident length:sizeof(long)];
832 [data appendBytes:&type length:sizeof(int)];
834 [self queueMessage:CreateScrollbarMsgID data:data];
837 - (void)destroyScrollbarWithIdentifier:(long)ident
839 NSMutableData *data = [NSMutableData data];
840 [data appendBytes:&ident length:sizeof(long)];
842 [self queueMessage:DestroyScrollbarMsgID data:data];
845 - (void)showScrollbarWithIdentifier:(long)ident state:(int)visible
847 NSMutableData *data = [NSMutableData data];
849 [data appendBytes:&ident length:sizeof(long)];
850 [data appendBytes:&visible length:sizeof(int)];
852 [self queueMessage:ShowScrollbarMsgID data:data];
855 - (void)setScrollbarPosition:(int)pos length:(int)len identifier:(long)ident
857 NSMutableData *data = [NSMutableData data];
859 [data appendBytes:&ident length:sizeof(long)];
860 [data appendBytes:&pos length:sizeof(int)];
861 [data appendBytes:&len length:sizeof(int)];
863 [self queueMessage:SetScrollbarPositionMsgID data:data];
866 - (void)setScrollbarThumbValue:(long)val size:(long)size max:(long)max
867 identifier:(long)ident
869 float fval = max-size+1 > 0 ? (float)val/(max-size+1) : 0;
870 float prop = (float)size/(max+1);
871 if (fval < 0) fval = 0;
872 else if (fval > 1.0f) fval = 1.0f;
873 if (prop < 0) prop = 0;
874 else if (prop > 1.0f) prop = 1.0f;
876 NSMutableData *data = [NSMutableData data];
878 [data appendBytes:&ident length:sizeof(long)];
879 [data appendBytes:&fval length:sizeof(float)];
880 [data appendBytes:&prop length:sizeof(float)];
882 [self queueMessage:SetScrollbarThumbMsgID data:data];
885 - (void)setFont:(NSFont *)font
887 NSString *fontName = [font displayName];
888 float size = [font pointSize];
889 int len = [fontName lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
891 NSMutableData *data = [NSMutableData data];
893 [data appendBytes:&size length:sizeof(float)];
894 [data appendBytes:&len length:sizeof(int)];
895 [data appendBytes:[fontName UTF8String] length:len];
897 [self queueMessage:SetFontMsgID data:data];
901 - (void)setWideFont:(NSFont *)font
903 NSString *fontName = [font displayName];
904 float size = [font pointSize];
905 int len = [fontName lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
906 NSMutableData *data = [NSMutableData data];
908 [data appendBytes:&size length:sizeof(float)];
909 [data appendBytes:&len length:sizeof(int)];
911 [data appendBytes:[fontName UTF8String] length:len];
913 [self queueMessage:SetWideFontMsgID data:data];
916 - (void)executeActionWithName:(NSString *)name
918 int len = [name lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
921 NSMutableData *data = [NSMutableData data];
923 [data appendBytes:&len length:sizeof(int)];
924 [data appendBytes:[name UTF8String] length:len];
926 [self queueMessage:ExecuteActionMsgID data:data];
930 - (void)setMouseShape:(int)shape
932 NSMutableData *data = [NSMutableData data];
933 [data appendBytes:&shape length:sizeof(int)];
934 [self queueMessage:SetMouseShapeMsgID data:data];
937 - (void)setBlinkWait:(int)wait on:(int)on off:(int)off
939 // Vim specifies times in milliseconds, whereas Cocoa wants them in
941 blinkWaitInterval = .001f*wait;
942 blinkOnInterval = .001f*on;
943 blinkOffInterval = .001f*off;
949 [blinkTimer invalidate];
950 [blinkTimer release];
954 if (blinkWaitInterval > 0 && blinkOnInterval > 0 && blinkOffInterval > 0
956 blinkState = MMBlinkStateOn;
958 [[NSTimer scheduledTimerWithTimeInterval:blinkWaitInterval
960 selector:@selector(blinkTimerFired:)
961 userInfo:nil repeats:NO] retain];
962 gui_update_cursor(TRUE, FALSE);
963 [self flushQueue:YES];
969 if (MMBlinkStateOff == blinkState) {
970 gui_update_cursor(TRUE, FALSE);
971 [self flushQueue:YES];
974 blinkState = MMBlinkStateNone;
977 - (void)adjustLinespace:(int)linespace
979 NSMutableData *data = [NSMutableData data];
980 [data appendBytes:&linespace length:sizeof(int)];
981 [self queueMessage:AdjustLinespaceMsgID data:data];
986 [self queueMessage:ActivateMsgID data:nil];
989 - (void)setPreEditRow:(int)row column:(int)col
991 NSMutableData *data = [NSMutableData data];
992 [data appendBytes:&row length:sizeof(int)];
993 [data appendBytes:&col length:sizeof(int)];
994 [self queueMessage:SetPreEditPositionMsgID data:data];
997 - (int)lookupColorWithKey:(NSString *)key
999 if (!(key && [key length] > 0))
1002 NSString *stripKey = [[[[key lowercaseString]
1003 stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]
1004 componentsSeparatedByString:@" "]
1005 componentsJoinedByString:@""];
1007 if (stripKey && [stripKey length] > 0) {
1008 // First of all try to lookup key in the color dictionary; note that
1009 // all keys in this dictionary are lowercase with no whitespace.
1010 id obj = [colorDict objectForKey:stripKey];
1011 if (obj) return [obj intValue];
1013 // The key was not in the dictionary; is it perhaps of the form
1015 if ([stripKey length] > 1 && [stripKey characterAtIndex:0] == '#') {
1016 NSScanner *scanner = [NSScanner scannerWithString:stripKey];
1017 [scanner setScanLocation:1];
1019 if ([scanner scanHexInt:&hex]) {
1024 // As a last resort, check if it is one of the system defined colors.
1025 // The keys in this dictionary are also lowercase with no whitespace.
1026 obj = [sysColorDict objectForKey:stripKey];
1028 NSColor *col = [NSColor performSelector:NSSelectorFromString(obj)];
1031 col = [col colorUsingColorSpaceName:NSCalibratedRGBColorSpace];
1032 [col getRed:&r green:&g blue:&b alpha:&a];
1033 return (((int)(r*255+.5f) & 0xff) << 16)
1034 + (((int)(g*255+.5f) & 0xff) << 8)
1035 + ((int)(b*255+.5f) & 0xff);
1040 //NSLog(@"WARNING: No color with key %@ found.", stripKey);
1044 - (BOOL)hasSpecialKeyWithValue:(NSString *)value
1046 NSEnumerator *e = [[MMBackend specialKeys] objectEnumerator];
1049 while ((obj = [e nextObject])) {
1050 if ([value isEqual:obj])
1057 - (void)enterFullscreen
1059 [self queueMessage:EnterFullscreenMsgID data:nil];
1062 - (void)leaveFullscreen
1064 [self queueMessage:LeaveFullscreenMsgID data:nil];
1067 - (void)updateModifiedFlag
1069 // Notify MacVim if _any_ buffer has changed from unmodified to modified or
1071 int msgid = [self checkForModifiedBuffers]
1072 ? BuffersModifiedMsgID : BuffersNotModifiedMsgID;
1074 [self queueMessage:msgid data:nil];
1077 - (oneway void)processInput:(int)msgid data:(in bycopy NSData *)data
1079 // NOTE: This method might get called whenever the run loop is tended to.
1080 // Normal keyboard and mouse input is added to input buffers, so there is
1081 // no risk in handling these events directly (they return immediately, and
1082 // do not call any other Vim functions). However, other events such
1083 // as 'VimShouldCloseMsgID' may enter blocking loops that wait for key
1084 // events which would cause this method to be called recursively. This
1085 // in turn leads to various difficulties that we do not want to have to
1086 // deal with. To avoid recursive calls here we add all events except
1087 // keyboard and mouse events to an input queue which is processed whenever
1088 // gui_mch_update() is called (see processInputQueue).
1090 //NSLog(@"%s%s", _cmd, MessageStrings[msgid]);
1092 // Don't flush too soon after receiving input or update speed will suffer.
1093 [lastFlushDate release];
1094 lastFlushDate = [[NSDate date] retain];
1096 // Handle keyboard and mouse input now. All other events are queued.
1097 if (InsertTextMsgID == msgid) {
1098 [self handleInsertText:data];
1099 } else if (KeyDownMsgID == msgid || CmdKeyMsgID == msgid) {
1101 const void *bytes = [data bytes];
1102 int mods = *((int*)bytes); bytes += sizeof(int);
1103 int len = *((int*)bytes); bytes += sizeof(int);
1104 NSString *key = [[NSString alloc] initWithBytes:bytes length:len
1105 encoding:NSUTF8StringEncoding];
1106 mods = eventModifierFlagsToVimModMask(mods);
1108 [self handleKeyDown:key modifiers:mods];
1111 } else if (ScrollWheelMsgID == msgid) {
1113 const void *bytes = [data bytes];
1115 int row = *((int*)bytes); bytes += sizeof(int);
1116 int col = *((int*)bytes); bytes += sizeof(int);
1117 int flags = *((int*)bytes); bytes += sizeof(int);
1118 float dy = *((float*)bytes); bytes += sizeof(float);
1120 int button = MOUSE_5;
1121 if (dy > 0) button = MOUSE_4;
1123 flags = eventModifierFlagsToVimMouseModMask(flags);
1125 int numLines = (int)round(dy);
1126 if (numLines < 0) numLines = -numLines;
1127 if (numLines == 0) numLines = 1;
1129 #ifdef FEAT_GUI_SCROLL_WHEEL_FORCE
1130 gui.scroll_wheel_force = numLines;
1133 gui_send_mouse_event(button, col, row, NO, flags);
1134 } else if (MouseDownMsgID == msgid) {
1136 const void *bytes = [data bytes];
1138 int row = *((int*)bytes); bytes += sizeof(int);
1139 int col = *((int*)bytes); bytes += sizeof(int);
1140 int button = *((int*)bytes); bytes += sizeof(int);
1141 int flags = *((int*)bytes); bytes += sizeof(int);
1142 int count = *((int*)bytes); bytes += sizeof(int);
1144 button = eventButtonNumberToVimMouseButton(button);
1146 flags = eventModifierFlagsToVimMouseModMask(flags);
1147 gui_send_mouse_event(button, col, row, count>1, flags);
1149 } else if (MouseUpMsgID == msgid) {
1151 const void *bytes = [data bytes];
1153 int row = *((int*)bytes); bytes += sizeof(int);
1154 int col = *((int*)bytes); bytes += sizeof(int);
1155 int flags = *((int*)bytes); bytes += sizeof(int);
1157 flags = eventModifierFlagsToVimMouseModMask(flags);
1159 gui_send_mouse_event(MOUSE_RELEASE, col, row, NO, flags);
1160 } else if (MouseDraggedMsgID == msgid) {
1162 const void *bytes = [data bytes];
1164 int row = *((int*)bytes); bytes += sizeof(int);
1165 int col = *((int*)bytes); bytes += sizeof(int);
1166 int flags = *((int*)bytes); bytes += sizeof(int);
1168 flags = eventModifierFlagsToVimMouseModMask(flags);
1170 gui_send_mouse_event(MOUSE_DRAG, col, row, NO, flags);
1171 } else if (MouseMovedMsgID == msgid) {
1172 const void *bytes = [data bytes];
1173 int row = *((int*)bytes); bytes += sizeof(int);
1174 int col = *((int*)bytes); bytes += sizeof(int);
1176 gui_mouse_moved(col, row);
1177 } else if (AddInputMsgID == msgid) {
1178 NSString *string = [[NSString alloc] initWithData:data
1179 encoding:NSUTF8StringEncoding];
1181 [self addInput:string];
1184 } else if (TerminateNowMsgID == msgid) {
1185 isTerminating = YES;
1187 // Not keyboard or mouse event, queue it and handle later.
1188 //NSLog(@"Add event %s to input event queue", MessageStrings[msgid]);
1189 [inputQueue addObject:[NSNumber numberWithInt:msgid]];
1190 [inputQueue addObject:(data ? (id)data : (id)[NSNull null])];
1193 // See waitForInput: for an explanation of this flag.
1194 inputReceived = YES;
1197 - (oneway void)processInputAndData:(in bycopy NSArray *)messages
1199 // TODO: Get rid of this method?
1200 //NSLog(@"%s%@", _cmd, messages);
1202 unsigned i, count = [messages count];
1203 for (i = 0; i < count; i += 2) {
1204 int msgid = [[messages objectAtIndex:i] intValue];
1205 id data = [messages objectAtIndex:i+1];
1206 if ([data isEqual:[NSNull null]])
1209 [self processInput:msgid data:data];
1213 - (NSString *)evaluateExpression:(in bycopy NSString *)expr
1215 NSString *eval = nil;
1216 char_u *s = (char_u*)[expr UTF8String];
1219 s = CONVERT_FROM_UTF8(s);
1222 char_u *res = eval_client_expr_to_string(s);
1225 CONVERT_FROM_UTF8_FREE(s);
1231 s = CONVERT_TO_UTF8(s);
1233 eval = [NSString stringWithUTF8String:(char*)s];
1235 CONVERT_TO_UTF8_FREE(s);
1243 - (BOOL)starRegisterToPasteboard:(byref NSPasteboard *)pboard
1245 if (VIsual_active && (State & NORMAL) && clip_star.available) {
1246 // If there is no pasteboard, return YES to indicate that there is text
1251 clip_copy_selection();
1253 // Get the text to put on the pasteboard.
1254 long_u llen = 0; char_u *str = 0;
1255 int type = clip_convert_selection(&str, &llen, &clip_star);
1259 // TODO: Avoid overflow.
1260 int len = (int)llen;
1262 if (output_conv.vc_type != CONV_NONE) {
1263 char_u *conv_str = string_convert(&output_conv, str, &len);
1271 NSString *string = [[NSString alloc]
1272 initWithBytes:str length:len encoding:NSUTF8StringEncoding];
1274 NSArray *types = [NSArray arrayWithObject:NSStringPboardType];
1275 [pboard declareTypes:types owner:nil];
1276 BOOL ok = [pboard setString:string forType:NSStringPboardType];
1287 - (oneway void)addReply:(in bycopy NSString *)reply
1288 server:(in byref id <MMVimServerProtocol>)server
1290 //NSLog(@"addReply:%@ server:%@", reply, (id)server);
1292 // Replies might come at any time and in any order so we keep them in an
1293 // array inside a dictionary with the send port used as key.
1295 NSConnection *conn = [(NSDistantObject*)server connectionForProxy];
1296 // HACK! Assume connection uses mach ports.
1297 int port = [(NSMachPort*)[conn sendPort] machPort];
1298 NSNumber *key = [NSNumber numberWithInt:port];
1300 NSMutableArray *replies = [serverReplyDict objectForKey:key];
1302 replies = [NSMutableArray array];
1303 [serverReplyDict setObject:replies forKey:key];
1306 [replies addObject:reply];
1309 - (void)addInput:(in bycopy NSString *)input
1310 client:(in byref id <MMVimClientProtocol>)client
1312 //NSLog(@"addInput:%@ client:%@", input, (id)client);
1314 [self addInput:input];
1315 [self addClient:(id)client];
1317 inputReceived = YES;
1320 - (NSString *)evaluateExpression:(in bycopy NSString *)expr
1321 client:(in byref id <MMVimClientProtocol>)client
1323 [self addClient:(id)client];
1324 return [self evaluateExpression:expr];
1327 - (void)registerServerWithName:(NSString *)name
1329 NSString *svrName = name;
1330 NSConnection *svrConn = [NSConnection defaultConnection];
1333 for (i = 0; i < MMServerMax; ++i) {
1334 NSString *connName = [self connectionNameFromServerName:svrName];
1336 if ([svrConn registerName:connName]) {
1337 //NSLog(@"Registered server with name: %@", svrName);
1339 // TODO: Set request/reply time-outs to something else?
1341 // Don't wait for requests (time-out means that the message is
1343 [svrConn setRequestTimeout:0];
1344 //[svrConn setReplyTimeout:MMReplyTimeout];
1345 [svrConn setRootObject:self];
1347 char_u *s = (char_u*)[svrName UTF8String];
1349 s = CONVERT_FROM_UTF8(s);
1351 // NOTE: 'serverName' is a global variable
1352 serverName = vim_strsave(s);
1354 CONVERT_FROM_UTF8_FREE(s);
1357 set_vim_var_string(VV_SEND_SERVER, serverName, -1);
1360 need_maketitle = TRUE;
1362 [self queueMessage:SetServerNameMsgID data:
1363 [svrName dataUsingEncoding:NSUTF8StringEncoding]];
1367 svrName = [NSString stringWithFormat:@"%@%d", name, i+1];
1371 - (BOOL)sendToServer:(NSString *)name string:(NSString *)string
1372 reply:(char_u **)reply port:(int *)port expression:(BOOL)expr
1375 // NOTE: If 'name' equals 'serverName' then the request is local (client
1376 // and server are the same). This case is not handled separately, so a
1377 // connection will be set up anyway (this simplifies the code).
1379 NSConnection *conn = [self connectionForServerName:name];
1382 char_u *s = (char_u*)[name UTF8String];
1384 s = CONVERT_FROM_UTF8(s);
1386 EMSG2(_(e_noserver), s);
1388 CONVERT_FROM_UTF8_FREE(s);
1395 // HACK! Assume connection uses mach ports.
1396 *port = [(NSMachPort*)[conn sendPort] machPort];
1399 id proxy = [conn rootProxy];
1400 [proxy setProtocolForProxy:@protocol(MMVimServerProtocol)];
1404 NSString *eval = [proxy evaluateExpression:string client:self];
1407 char_u *r = (char_u*)[eval UTF8String];
1409 r = CONVERT_FROM_UTF8(r);
1411 *reply = vim_strsave(r);
1413 CONVERT_FROM_UTF8_FREE(r);
1416 *reply = vim_strsave((char_u*)_(e_invexprmsg));
1423 [proxy addInput:string client:self];
1426 @catch (NSException *e) {
1427 NSLog(@"WARNING: Caught exception in %s: \"%@\"", _cmd, e);
1434 - (NSArray *)serverList
1436 NSArray *list = nil;
1438 if ([self connection]) {
1439 id proxy = [connection rootProxy];
1440 [proxy setProtocolForProxy:@protocol(MMAppProtocol)];
1443 list = [proxy serverList];
1445 @catch (NSException *e) {
1446 NSLog(@"Exception caught when listing servers: \"%@\"", e);
1449 EMSG(_("E???: No connection to MacVim, server listing not possible."));
1455 - (NSString *)peekForReplyOnPort:(int)port
1457 //NSLog(@"%s%d", _cmd, port);
1459 NSNumber *key = [NSNumber numberWithInt:port];
1460 NSMutableArray *replies = [serverReplyDict objectForKey:key];
1461 if (replies && [replies count]) {
1462 //NSLog(@" %d replies, topmost is: %@", [replies count],
1463 // [replies objectAtIndex:0]);
1464 return [replies objectAtIndex:0];
1467 //NSLog(@" No replies");
1471 - (NSString *)waitForReplyOnPort:(int)port
1473 //NSLog(@"%s%d", _cmd, port);
1475 NSConnection *conn = [self connectionForServerPort:port];
1479 NSNumber *key = [NSNumber numberWithInt:port];
1480 NSMutableArray *replies = nil;
1481 NSString *reply = nil;
1483 // Wait for reply as long as the connection to the server is valid (unless
1484 // user interrupts wait with Ctrl-C).
1485 while (!got_int && [conn isValid] &&
1486 !(replies = [serverReplyDict objectForKey:key])) {
1487 [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
1488 beforeDate:[NSDate distantFuture]];
1492 if ([replies count] > 0) {
1493 reply = [[replies objectAtIndex:0] retain];
1494 //NSLog(@" Got reply: %@", reply);
1495 [replies removeObjectAtIndex:0];
1496 [reply autorelease];
1499 if ([replies count] == 0)
1500 [serverReplyDict removeObjectForKey:key];
1506 - (BOOL)sendReply:(NSString *)reply toPort:(int)port
1508 id client = [clientProxyDict objectForKey:[NSNumber numberWithInt:port]];
1511 //NSLog(@"sendReply:%@ toPort:%d", reply, port);
1512 [client addReply:reply server:self];
1515 @catch (NSException *e) {
1516 NSLog(@"WARNING: Exception caught in %s: \"%@\"", _cmd, e);
1519 EMSG2(_("E???: server2client failed; no client with id 0x%x"), port);
1529 @implementation MMBackend (Private)
1531 - (void)processInputQueue
1533 // NOTE: One of the input events may cause this method to be called
1534 // recursively, so copy the input queue to a local variable and clear it
1535 // before starting to process input events (otherwise we could get stuck in
1536 // an endless loop).
1537 NSArray *q = [inputQueue copy];
1538 unsigned i, count = [q count];
1540 [inputQueue removeAllObjects];
1542 for (i = 0; i < count-1; i += 2) {
1543 int msgid = [[q objectAtIndex:i] intValue];
1544 id data = [q objectAtIndex:i+1];
1545 if ([data isEqual:[NSNull null]])
1548 //NSLog(@"(%d) %s:%s", i, _cmd, MessageStrings[msgid]);
1549 [self handleInputEvent:msgid data:data];
1553 //NSLog(@"Clear input event queue");
1556 - (void)handleInputEvent:(int)msgid data:(NSData *)data
1558 // NOTE: Be careful with what you do in this method. Ideally, a message
1559 // should be handled by adding something to the input buffer and returning
1560 // immediately. If you call a Vim function then it should not enter a loop
1561 // waiting for key presses or in any other way block the process. The
1562 // reason for this being that only one message can be processed at a time,
1563 // so if another message is received while processing, then the new message
1564 // is dropped. See also the comment in processInput:data:.
1566 //NSLog(@"%s%s", _cmd, MessageStrings[msgid]);
1568 if (SelectTabMsgID == msgid) {
1570 const void *bytes = [data bytes];
1571 int idx = *((int*)bytes) + 1;
1572 //NSLog(@"Selecting tab %d", idx);
1573 send_tabline_event(idx);
1574 } else if (CloseTabMsgID == msgid) {
1576 const void *bytes = [data bytes];
1577 int idx = *((int*)bytes) + 1;
1578 //NSLog(@"Closing tab %d", idx);
1579 send_tabline_menu_event(idx, TABLINE_MENU_CLOSE);
1580 } else if (AddNewTabMsgID == msgid) {
1581 //NSLog(@"Adding new tab");
1582 send_tabline_menu_event(0, TABLINE_MENU_NEW);
1583 } else if (DraggedTabMsgID == msgid) {
1585 const void *bytes = [data bytes];
1586 // NOTE! The destination index is 0 based, so do not add 1 to make it 1
1588 int idx = *((int*)bytes);
1591 } else if (SetTextDimensionsMsgID == msgid || LiveResizeMsgID == msgid) {
1593 const void *bytes = [data bytes];
1594 int rows = *((int*)bytes); bytes += sizeof(int);
1595 int cols = *((int*)bytes); bytes += sizeof(int);
1597 // NOTE! Vim doesn't call gui_mch_set_shellsize() after
1598 // gui_resize_shell(), so we have to manually set the rows and columns
1599 // here. (MacVim doesn't change the rows and columns to avoid
1600 // inconsistent states between Vim and MacVim.)
1601 [self queueMessage:msgid data:data];
1603 //NSLog(@"[VimTask] Resizing shell to %dx%d.", cols, rows);
1604 gui_resize_shell(cols, rows);
1605 } else if (ExecuteMenuMsgID == msgid) {
1607 const void *bytes = [data bytes];
1608 int tag = *((int*)bytes); bytes += sizeof(int);
1610 vimmenu_T *menu = (vimmenu_T*)tag;
1611 // TODO! Make sure 'menu' is a valid menu pointer!
1615 } else if (ToggleToolbarMsgID == msgid) {
1616 [self handleToggleToolbar];
1617 } else if (ScrollbarEventMsgID == msgid) {
1618 [self handleScrollbarEvent:data];
1619 } else if (SetFontMsgID == msgid) {
1620 [self handleSetFont:data];
1621 } else if (VimShouldCloseMsgID == msgid) {
1623 } else if (DropFilesMsgID == msgid) {
1624 [self handleDropFiles:data];
1625 } else if (DropStringMsgID == msgid) {
1626 [self handleDropString:data];
1627 } else if (GotFocusMsgID == msgid) {
1629 [self focusChange:YES];
1630 } else if (LostFocusMsgID == msgid) {
1632 [self focusChange:NO];
1633 } else if (SetMouseShapeMsgID == msgid) {
1634 const void *bytes = [data bytes];
1635 int shape = *((int*)bytes); bytes += sizeof(int);
1636 update_mouseshape(shape);
1637 } else if (ODBEditMsgID == msgid) {
1638 [self handleOdbEdit:data];
1639 } else if (XcodeModMsgID == msgid) {
1640 [self handleXcodeMod:data];
1642 NSLog(@"WARNING: Unknown message received (msgid=%d)", msgid);
1646 + (NSDictionary *)specialKeys
1648 static NSDictionary *specialKeys = nil;
1651 NSBundle *mainBundle = [NSBundle mainBundle];
1652 NSString *path = [mainBundle pathForResource:@"SpecialKeys"
1654 specialKeys = [[NSDictionary alloc] initWithContentsOfFile:path];
1660 - (void)handleInsertText:(NSData *)data
1664 NSString *key = [[NSString alloc] initWithData:data
1665 encoding:NSUTF8StringEncoding];
1666 char_u *str = (char_u*)[key UTF8String];
1667 int i, len = [key lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
1670 char_u *conv_str = NULL;
1671 if (input_conv.vc_type != CONV_NONE) {
1672 conv_str = string_convert(&input_conv, str, &len);
1678 if (len == 1 && ((str[0] == Ctrl_C && ctrl_c_interrupts)
1679 || (str[0] == intr_char && intr_char != Ctrl_C))) {
1684 for (i = 0; i < len; ++i) {
1685 add_to_input_buf(str+i, 1);
1686 if (CSI == str[i]) {
1687 // NOTE: If the converted string contains the byte CSI, then it
1688 // must be followed by the bytes KS_EXTRA, KE_CSI or things
1690 static char_u extra[2] = { KS_EXTRA, KE_CSI };
1691 add_to_input_buf(extra, 2);
1702 - (void)handleKeyDown:(NSString *)key modifiers:(int)mods
1706 char_u *chars = (char_u*)[key UTF8String];
1708 char_u *conv_str = NULL;
1710 int length = [key lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
1712 // Special keys (arrow keys, function keys, etc.) are stored in a plist so
1713 // that new keys can easily be added.
1714 NSString *specialString = [[MMBackend specialKeys]
1716 if (specialString && [specialString length] > 1) {
1717 //NSLog(@"special key: %@", specialString);
1718 int ikey = TO_SPECIAL([specialString characterAtIndex:0],
1719 [specialString characterAtIndex:1]);
1721 ikey = simplify_key(ikey, &mods);
1726 special[1] = K_SECOND(ikey);
1727 special[2] = K_THIRD(ikey);
1731 } else if (1 == length && TAB == chars[0]) {
1732 // Tab is a trouble child:
1733 // - <Tab> is added to the input buffer as is
1734 // - <S-Tab> is translated to, {CSI,'k','B'} (i.e. 'Back-tab')
1735 // - <M-Tab> should be 0x80|TAB but this is not valid utf-8 so it needs
1736 // to be converted to utf-8
1737 // - <S-M-Tab> is translated to <S-Tab> with ALT modifier
1738 // - <C-Tab> is reserved by Mac OS X
1739 // - <D-Tab> is reserved by Mac OS X
1744 if (mods & MOD_MASK_SHIFT) {
1745 mods &= ~MOD_MASK_SHIFT;
1747 special[1] = K_SECOND(K_S_TAB);
1748 special[2] = K_THIRD(K_S_TAB);
1750 } else if (mods & MOD_MASK_ALT) {
1751 int mtab = 0x80 | TAB;
1755 special[0] = (mtab >> 6) + 0xc0;
1756 special[1] = mtab & 0xbf;
1764 mods &= ~MOD_MASK_ALT;
1766 } else if (length > 0) {
1767 unichar c = [key characterAtIndex:0];
1769 //NSLog(@"non-special: %@ (hex=%x, mods=%d)", key,
1770 // [key characterAtIndex:0], mods);
1772 if (length == 1 && ((c == Ctrl_C && ctrl_c_interrupts)
1773 || (c == intr_char && intr_char != Ctrl_C))) {
1778 // HACK! In most circumstances the Ctrl and Shift modifiers should be
1779 // cleared since they are already added to the key by the AppKit.
1780 // Unfortunately, the only way to deal with when to clear the modifiers
1781 // or not seems to be to have hard-wired rules like this.
1782 if ( !((' ' == c) || (0xa0 == c) || (mods & MOD_MASK_CMD)
1783 || 0x9 == c || 0xd == c || ESC == c) ) {
1784 mods &= ~MOD_MASK_SHIFT;
1785 mods &= ~MOD_MASK_CTRL;
1786 //NSLog(@"clear shift ctrl");
1789 // HACK! All Option+key presses go via 'insert text' messages, except
1790 // for <M-Space>. If the Alt flag is not cleared for <M-Space> it does
1791 // not work to map to it.
1792 if (0xa0 == c && !(mods & MOD_MASK_CMD)) {
1793 //NSLog(@"clear alt");
1794 mods &= ~MOD_MASK_ALT;
1798 if (input_conv.vc_type != CONV_NONE) {
1799 conv_str = string_convert(&input_conv, chars, &length);
1806 if (chars && length > 0) {
1808 //NSLog(@"adding mods: %d", mods);
1810 modChars[1] = KS_MODIFIER;
1812 add_to_input_buf(modChars, 3);
1815 //NSLog(@"add to input buf: 0x%x", chars[0]);
1816 // TODO: Check for CSI bytes?
1817 add_to_input_buf(chars, length);
1826 - (void)queueMessage:(int)msgid data:(NSData *)data
1828 [outputQueue addObject:[NSData dataWithBytes:&msgid length:sizeof(int)]];
1830 [outputQueue addObject:data];
1832 [outputQueue addObject:[NSData data]];
1835 - (void)connectionDidDie:(NSNotification *)notification
1837 // If the main connection to MacVim is lost this means that MacVim was
1838 // either quit (by the user chosing Quit on the MacVim menu), or it has
1839 // crashed. In the former case the flag 'isTerminating' is set and we then
1840 // quit cleanly; in the latter case we make sure the swap files are left
1843 //NSLog(@"%s isTerminating=%d", _cmd, isTerminating);
1847 getout_preserve_modified(1);
1850 - (void)blinkTimerFired:(NSTimer *)timer
1852 NSTimeInterval timeInterval = 0;
1854 [blinkTimer release];
1857 if (MMBlinkStateOn == blinkState) {
1858 gui_undraw_cursor();
1859 blinkState = MMBlinkStateOff;
1860 timeInterval = blinkOffInterval;
1861 } else if (MMBlinkStateOff == blinkState) {
1862 gui_update_cursor(TRUE, FALSE);
1863 blinkState = MMBlinkStateOn;
1864 timeInterval = blinkOnInterval;
1867 if (timeInterval > 0) {
1869 [[NSTimer scheduledTimerWithTimeInterval:timeInterval target:self
1870 selector:@selector(blinkTimerFired:)
1871 userInfo:nil repeats:NO] retain];
1872 [self flushQueue:YES];
1876 - (void)focusChange:(BOOL)on
1878 gui_focus_change(on);
1881 - (void)handleToggleToolbar
1883 // If 'go' contains 'T', then remove it, else add it.
1885 char_u go[sizeof(GO_ALL)+2];
1890 p = vim_strchr(go, GO_TOOLBAR);
1894 char_u *end = go + len;
1900 go[len] = GO_TOOLBAR;
1904 set_option_value((char_u*)"guioptions", 0, go, 0);
1906 // Force screen redraw (does it have to be this complicated?).
1907 redraw_all_later(CLEAR);
1908 update_screen(NOT_VALID);
1911 gui_update_cursor(FALSE, FALSE);
1915 - (void)handleScrollbarEvent:(NSData *)data
1919 const void *bytes = [data bytes];
1920 long ident = *((long*)bytes); bytes += sizeof(long);
1921 int hitPart = *((int*)bytes); bytes += sizeof(int);
1922 float fval = *((float*)bytes); bytes += sizeof(float);
1923 scrollbar_T *sb = gui_find_scrollbar(ident);
1926 scrollbar_T *sb_info = sb->wp ? &sb->wp->w_scrollbars[0] : sb;
1927 long value = sb_info->value;
1928 long size = sb_info->size;
1929 long max = sb_info->max;
1930 BOOL isStillDragging = NO;
1931 BOOL updateKnob = YES;
1934 case NSScrollerDecrementPage:
1935 value -= (size > 2 ? size - 2 : 1);
1937 case NSScrollerIncrementPage:
1938 value += (size > 2 ? size - 2 : 1);
1940 case NSScrollerDecrementLine:
1943 case NSScrollerIncrementLine:
1946 case NSScrollerKnob:
1947 isStillDragging = YES;
1949 case NSScrollerKnobSlot:
1950 value = (long)(fval * (max - size + 1));
1957 //NSLog(@"value %d -> %d", sb_info->value, value);
1958 gui_drag_scrollbar(sb, value, isStillDragging);
1961 // Dragging the knob or option+clicking automatically updates
1962 // the knob position (on the actual NSScroller), so we only
1963 // need to set the knob position in the other cases.
1965 // Update both the left&right vertical scrollbars.
1966 long identLeft = sb->wp->w_scrollbars[SBAR_LEFT].ident;
1967 long identRight = sb->wp->w_scrollbars[SBAR_RIGHT].ident;
1968 [self setScrollbarThumbValue:value size:size max:max
1969 identifier:identLeft];
1970 [self setScrollbarThumbValue:value size:size max:max
1971 identifier:identRight];
1973 // Update the horizontal scrollbar.
1974 [self setScrollbarThumbValue:value size:size max:max
1981 - (void)handleSetFont:(NSData *)data
1985 const void *bytes = [data bytes];
1986 float pointSize = *((float*)bytes); bytes += sizeof(float);
1987 //unsigned len = *((unsigned*)bytes); bytes += sizeof(unsigned);
1988 bytes += sizeof(unsigned); // len not used
1990 NSMutableString *name = [NSMutableString stringWithUTF8String:bytes];
1991 [name appendString:[NSString stringWithFormat:@":h%.2f", pointSize]];
1992 char_u *s = (char_u*)[name UTF8String];
1995 s = CONVERT_FROM_UTF8(s);
1998 set_option_value((char_u*)"guifont", 0, s, 0);
2001 CONVERT_FROM_UTF8_FREE(s);
2004 // Force screen redraw (does it have to be this complicated?).
2005 redraw_all_later(CLEAR);
2006 update_screen(NOT_VALID);
2009 gui_update_cursor(FALSE, FALSE);
2013 - (void)handleDropFiles:(NSData *)data
2015 // TODO: Get rid of this method; instead use Vim script directly. At the
2016 // moment I know how to do this to open files in tabs, but I'm not sure how
2017 // to add the filenames to the command line when in command line mode.
2022 const void *bytes = [data bytes];
2023 const void *end = [data bytes] + [data length];
2024 BOOL forceOpen = *((BOOL*)bytes); bytes += sizeof(BOOL);
2025 int n = *((int*)bytes); bytes += sizeof(int);
2027 if (!forceOpen && (State & CMDLINE)) {
2028 // HACK! If Vim is in command line mode then the files names
2029 // should be added to the command line, instead of opening the
2030 // files in tabs (unless forceOpen is set). This is taken care of by
2031 // gui_handle_drop().
2032 char_u **fnames = (char_u **)alloc(n * sizeof(char_u *));
2035 while (bytes < end && i < n) {
2036 int len = *((int*)bytes); bytes += sizeof(int);
2037 char_u *s = (char_u*)bytes;
2039 s = CONVERT_FROM_UTF8(s);
2041 fnames[i++] = vim_strsave(s);
2043 CONVERT_FROM_UTF8_FREE(s);
2048 // NOTE! This function will free 'fnames'.
2049 // HACK! It is assumed that the 'x' and 'y' arguments are
2050 // unused when in command line mode.
2051 gui_handle_drop(0, 0, 0, fnames, i < n ? i : n);
2054 // HACK! I'm not sure how to get Vim to open a list of files in
2055 // tabs, so instead I create a ':tab drop' command with all the
2056 // files to open and execute it.
2057 NSMutableString *cmd = [NSMutableString stringWithString:@":tab drop"];
2060 for (i = 0; i < n && bytes < end; ++i) {
2061 int len = *((int*)bytes); bytes += sizeof(int);
2062 NSString *file = [NSString stringWithUTF8String:bytes];
2063 file = [file stringByEscapingSpecialFilenameCharacters];
2066 [cmd appendString:@" "];
2067 [cmd appendString:file];
2070 // By going to the last tabpage we ensure that the new tabs will
2071 // appear last (if this call is left out, the taborder becomes
2075 char_u *s = (char_u*)[cmd UTF8String];
2077 s = CONVERT_FROM_UTF8(s);
2081 CONVERT_FROM_UTF8_FREE(s);
2084 // Force screen redraw (does it have to be this complicated?).
2085 // (This code was taken from the end of gui_handle_drop().)
2086 update_screen(NOT_VALID);
2089 gui_update_cursor(FALSE, FALSE);
2096 - (void)handleDropString:(NSData *)data
2101 char_u dropkey[3] = { CSI, KS_EXTRA, (char_u)KE_DROP };
2102 const void *bytes = [data bytes];
2103 int len = *((int*)bytes); bytes += sizeof(int);
2104 NSMutableString *string = [NSMutableString stringWithUTF8String:bytes];
2106 // Replace unrecognized end-of-line sequences with \x0a (line feed).
2107 NSRange range = { 0, [string length] };
2108 unsigned n = [string replaceOccurrencesOfString:@"\x0d\x0a"
2109 withString:@"\x0a" options:0
2112 n = [string replaceOccurrencesOfString:@"\x0d" withString:@"\x0a"
2113 options:0 range:range];
2116 len = [string lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
2117 char_u *s = (char_u*)[string UTF8String];
2119 if (input_conv.vc_type != CONV_NONE)
2120 s = string_convert(&input_conv, s, &len);
2122 dnd_yank_drag_data(s, len);
2124 if (input_conv.vc_type != CONV_NONE)
2127 add_to_input_buf(dropkey, sizeof(dropkey));
2131 - (void)handleOdbEdit:(NSData *)data
2133 #ifdef FEAT_ODB_EDITOR
2134 const void *bytes = [data bytes];
2136 OSType serverID = *((OSType*)bytes); bytes += sizeof(OSType);
2138 char_u *path = NULL;
2139 int pathLen = *((int*)bytes); bytes += sizeof(int);
2141 path = (char_u*)bytes;
2144 path = CONVERT_FROM_UTF8(path);
2148 NSAppleEventDescriptor *token = nil;
2149 DescType tokenType = *((DescType*)bytes); bytes += sizeof(DescType);
2150 int descLen = *((int*)bytes); bytes += sizeof(int);
2152 token = [NSAppleEventDescriptor descriptorWithDescriptorType:tokenType
2158 unsigned i, numFiles = *((unsigned*)bytes); bytes += sizeof(unsigned);
2159 for (i = 0; i < numFiles; ++i) {
2160 int len = *((int*)bytes); bytes += sizeof(int);
2161 char_u *filename = (char_u*)bytes;
2163 filename = CONVERT_FROM_UTF8(filename);
2165 buf_T *buf = buflist_findname(filename);
2167 if (buf->b_odb_token) {
2168 [(NSAppleEventDescriptor*)(buf->b_odb_token) release];
2169 buf->b_odb_token = NULL;
2172 if (buf->b_odb_fname) {
2173 vim_free(buf->b_odb_fname);
2174 buf->b_odb_fname = NULL;
2177 buf->b_odb_server_id = serverID;
2180 buf->b_odb_token = [token retain];
2182 buf->b_odb_fname = vim_strsave(path);
2184 NSLog(@"WARNING: Could not find buffer '%s' for ODB editing.",
2189 CONVERT_FROM_UTF8_FREE(filename);
2194 CONVERT_FROM_UTF8_FREE(path);
2196 #endif // FEAT_ODB_EDITOR
2199 - (void)handleXcodeMod:(NSData *)data
2202 const void *bytes = [data bytes];
2203 DescType type = *((DescType*)bytes); bytes += sizeof(DescType);
2204 unsigned len = *((unsigned*)bytes); bytes += sizeof(unsigned);
2208 NSAppleEventDescriptor *replyEvent = [NSAppleEventDescriptor
2209 descriptorWithDescriptorType:type
2215 - (BOOL)checkForModifiedBuffers
2218 for (buf = firstbuf; buf != NULL; buf = buf->b_next) {
2219 if (bufIsChanged(buf)) {
2227 - (void)addInput:(NSString *)input
2229 char_u *s = (char_u*)[input UTF8String];
2232 s = CONVERT_FROM_UTF8(s);
2235 server_to_input_buf(s);
2238 CONVERT_FROM_UTF8_FREE(s);
2242 @end // MMBackend (Private)
2247 @implementation MMBackend (ClientServer)
2249 - (NSString *)connectionNameFromServerName:(NSString *)name
2251 NSString *bundleIdentifier = [[NSBundle mainBundle] bundleIdentifier];
2253 return [[NSString stringWithFormat:@"%@.%@", bundleIdentifier, name]
2257 - (NSConnection *)connectionForServerName:(NSString *)name
2259 // TODO: Try 'name%d' if 'name' fails.
2260 NSString *connName = [self connectionNameFromServerName:name];
2261 NSConnection *svrConn = [connectionNameDict objectForKey:connName];
2264 svrConn = [NSConnection connectionWithRegisteredName:connName
2266 // Try alternate server...
2267 if (!svrConn && alternateServerName) {
2268 //NSLog(@" trying to connect to alternate server: %@",
2269 // alternateServerName);
2270 connName = [self connectionNameFromServerName:alternateServerName];
2271 svrConn = [NSConnection connectionWithRegisteredName:connName
2275 // Try looking for alternate servers...
2277 //NSLog(@" looking for alternate servers...");
2278 NSString *alt = [self alternateServerNameForName:name];
2279 if (alt != alternateServerName) {
2280 //NSLog(@" found alternate server: %@", string);
2281 [alternateServerName release];
2282 alternateServerName = [alt copy];
2286 // Try alternate server again...
2287 if (!svrConn && alternateServerName) {
2288 //NSLog(@" trying to connect to alternate server: %@",
2289 // alternateServerName);
2290 connName = [self connectionNameFromServerName:alternateServerName];
2291 svrConn = [NSConnection connectionWithRegisteredName:connName
2296 [connectionNameDict setObject:svrConn forKey:connName];
2298 //NSLog(@"Adding %@ as connection observer for %@", self, svrConn);
2299 [[NSNotificationCenter defaultCenter] addObserver:self
2300 selector:@selector(serverConnectionDidDie:)
2301 name:NSConnectionDidDieNotification object:svrConn];
2308 - (NSConnection *)connectionForServerPort:(int)port
2311 NSEnumerator *e = [connectionNameDict objectEnumerator];
2313 while ((conn = [e nextObject])) {
2314 // HACK! Assume connection uses mach ports.
2315 if (port == [(NSMachPort*)[conn sendPort] machPort])
2322 - (void)serverConnectionDidDie:(NSNotification *)notification
2324 //NSLog(@"%s%@", _cmd, notification);
2326 NSConnection *svrConn = [notification object];
2328 //NSLog(@"Removing %@ as connection observer from %@", self, svrConn);
2329 [[NSNotificationCenter defaultCenter]
2331 name:NSConnectionDidDieNotification
2334 [connectionNameDict removeObjectsForKeys:
2335 [connectionNameDict allKeysForObject:svrConn]];
2337 // HACK! Assume connection uses mach ports.
2338 int port = [(NSMachPort*)[svrConn sendPort] machPort];
2339 NSNumber *key = [NSNumber numberWithInt:port];
2341 [clientProxyDict removeObjectForKey:key];
2342 [serverReplyDict removeObjectForKey:key];
2345 - (void)addClient:(NSDistantObject *)client
2347 NSConnection *conn = [client connectionForProxy];
2348 // HACK! Assume connection uses mach ports.
2349 int port = [(NSMachPort*)[conn sendPort] machPort];
2350 NSNumber *key = [NSNumber numberWithInt:port];
2352 if (![clientProxyDict objectForKey:key]) {
2353 [client setProtocolForProxy:@protocol(MMVimClientProtocol)];
2354 [clientProxyDict setObject:client forKey:key];
2357 // NOTE: 'clientWindow' is a global variable which is used by <client>
2358 clientWindow = port;
2361 - (NSString *)alternateServerNameForName:(NSString *)name
2363 if (!(name && [name length] > 0))
2366 // Only look for alternates if 'name' doesn't end in a digit.
2367 unichar lastChar = [name characterAtIndex:[name length]-1];
2368 if (lastChar >= '0' && lastChar <= '9')
2371 // Look for alternates among all current servers.
2372 NSArray *list = [self serverList];
2373 if (!(list && [list count] > 0))
2376 // Filter out servers starting with 'name' and ending with a number. The
2377 // (?i) pattern ensures that the match is case insensitive.
2378 NSString *pat = [NSString stringWithFormat:@"(?i)%@[0-9]+\\z", name];
2379 NSPredicate *pred = [NSPredicate predicateWithFormat:
2380 @"SELF MATCHES %@", pat];
2381 list = [list filteredArrayUsingPredicate:pred];
2382 if ([list count] > 0) {
2383 list = [list sortedArrayUsingSelector:@selector(serverNameCompare:)];
2384 return [list objectAtIndex:0];
2390 @end // MMBackend (ClientServer)
2395 @implementation NSString (MMServerNameCompare)
2396 - (NSComparisonResult)serverNameCompare:(NSString *)string
2398 return [self compare:string
2399 options:NSCaseInsensitiveSearch|NSNumericSearch];
2406 static int eventModifierFlagsToVimModMask(int modifierFlags)
2410 if (modifierFlags & NSShiftKeyMask)
2411 modMask |= MOD_MASK_SHIFT;
2412 if (modifierFlags & NSControlKeyMask)
2413 modMask |= MOD_MASK_CTRL;
2414 if (modifierFlags & NSAlternateKeyMask)
2415 modMask |= MOD_MASK_ALT;
2416 if (modifierFlags & NSCommandKeyMask)
2417 modMask |= MOD_MASK_CMD;
2422 static int vimModMaskToEventModifierFlags(int mods)
2426 if (mods & MOD_MASK_SHIFT)
2427 flags |= NSShiftKeyMask;
2428 if (mods & MOD_MASK_CTRL)
2429 flags |= NSControlKeyMask;
2430 if (mods & MOD_MASK_ALT)
2431 flags |= NSAlternateKeyMask;
2432 if (mods & MOD_MASK_CMD)
2433 flags |= NSCommandKeyMask;
2438 static int eventModifierFlagsToVimMouseModMask(int modifierFlags)
2442 if (modifierFlags & NSShiftKeyMask)
2443 modMask |= MOUSE_SHIFT;
2444 if (modifierFlags & NSControlKeyMask)
2445 modMask |= MOUSE_CTRL;
2446 if (modifierFlags & NSAlternateKeyMask)
2447 modMask |= MOUSE_ALT;
2452 static int eventButtonNumberToVimMouseButton(int buttonNumber)
2454 static int mouseButton[] = { MOUSE_LEFT, MOUSE_RIGHT, MOUSE_MIDDLE };
2456 return (buttonNumber >= 0 && buttonNumber < 3)
2457 ? mouseButton[buttonNumber] : -1;
2460 static int specialKeyToNSKey(int key)
2462 if (!IS_SPECIAL(key))
2469 { K_UP, NSUpArrowFunctionKey },
2470 { K_DOWN, NSDownArrowFunctionKey },
2471 { K_LEFT, NSLeftArrowFunctionKey },
2472 { K_RIGHT, NSRightArrowFunctionKey },
2473 { K_F1, NSF1FunctionKey },
2474 { K_F2, NSF2FunctionKey },
2475 { K_F3, NSF3FunctionKey },
2476 { K_F4, NSF4FunctionKey },
2477 { K_F5, NSF5FunctionKey },
2478 { K_F6, NSF6FunctionKey },
2479 { K_F7, NSF7FunctionKey },
2480 { K_F8, NSF8FunctionKey },
2481 { K_F9, NSF9FunctionKey },
2482 { K_F10, NSF10FunctionKey },
2483 { K_F11, NSF11FunctionKey },
2484 { K_F12, NSF12FunctionKey },
2485 { K_F13, NSF13FunctionKey },
2486 { K_F14, NSF14FunctionKey },
2487 { K_F15, NSF15FunctionKey },
2488 { K_F16, NSF16FunctionKey },
2489 { K_F17, NSF17FunctionKey },
2490 { K_F18, NSF18FunctionKey },
2491 { K_F19, NSF19FunctionKey },
2492 { K_F20, NSF20FunctionKey },
2493 { K_F21, NSF21FunctionKey },
2494 { K_F22, NSF22FunctionKey },
2495 { K_F23, NSF23FunctionKey },
2496 { K_F24, NSF24FunctionKey },
2497 { K_F25, NSF25FunctionKey },
2498 { K_F26, NSF26FunctionKey },
2499 { K_F27, NSF27FunctionKey },
2500 { K_F28, NSF28FunctionKey },
2501 { K_F29, NSF29FunctionKey },
2502 { K_F30, NSF30FunctionKey },
2503 { K_F31, NSF31FunctionKey },
2504 { K_F32, NSF32FunctionKey },
2505 { K_F33, NSF33FunctionKey },
2506 { K_F34, NSF34FunctionKey },
2507 { K_F35, NSF35FunctionKey },
2508 { K_DEL, NSBackspaceCharacter },
2509 { K_BS, NSDeleteCharacter },
2510 { K_HOME, NSHomeFunctionKey },
2511 { K_END, NSEndFunctionKey },
2512 { K_PAGEUP, NSPageUpFunctionKey },
2513 { K_PAGEDOWN, NSPageDownFunctionKey }
2517 for (i = 0; i < sizeof(sp2ns)/sizeof(sp2ns[0]); ++i) {
2518 if (sp2ns[i].special == key)
2519 return sp2ns[i].nskey;