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 path = [mainBundle pathForResource:@"Actions" ofType:@"plist"];
147 actionDict = [[NSDictionary dictionaryWithContentsOfFile:path] retain];
149 if (!(colorDict && sysColorDict && actionDict))
150 NSLog(@"ERROR: Failed to load dictionaries.%@",
151 MMSymlinkWarningString);
158 //NSLog(@"%@ %s", [self className], _cmd);
159 [[NSNotificationCenter defaultCenter] removeObserver:self];
161 [oldWideFont release]; oldWideFont = nil;
162 [blinkTimer release]; blinkTimer = nil;
163 [alternateServerName release]; alternateServerName = nil;
164 [serverReplyDict release]; serverReplyDict = nil;
165 [clientProxyDict release]; clientProxyDict = nil;
166 [connectionNameDict release]; connectionNameDict = nil;
167 [inputQueue release]; inputQueue = nil;
168 [outputQueue release]; outputQueue = nil;
169 [drawData release]; drawData = nil;
170 [frontendProxy release]; frontendProxy = nil;
171 [connection release]; connection = nil;
172 [sysColorDict release]; sysColorDict = nil;
173 [colorDict release]; colorDict = nil;
178 - (void)setBackgroundColor:(int)color
180 backgroundColor = MM_COLOR_WITH_TRANSP(color,p_transp);
183 - (void)setForegroundColor:(int)color
185 foregroundColor = MM_COLOR(color);
188 - (void)setSpecialColor:(int)color
190 specialColor = MM_COLOR(color);
193 - (void)setDefaultColorsBackground:(int)bg foreground:(int)fg
195 defaultBackgroundColor = MM_COLOR_WITH_TRANSP(bg,p_transp);
196 defaultForegroundColor = MM_COLOR(fg);
198 NSMutableData *data = [NSMutableData data];
200 [data appendBytes:&defaultBackgroundColor length:sizeof(unsigned)];
201 [data appendBytes:&defaultForegroundColor length:sizeof(unsigned)];
203 [self queueMessage:SetDefaultColorsMsgID data:data];
206 - (NSConnection *)connection
209 // NOTE! If the name of the connection changes here it must also be
210 // updated in MMAppController.m.
211 NSString *name = [NSString stringWithFormat:@"%@-connection",
212 [[NSBundle mainBundle] bundleIdentifier]];
214 connection = [NSConnection connectionWithRegisteredName:name host:nil];
218 // NOTE: 'connection' may be nil here.
222 - (NSDictionary *)actionDict
229 if (![self connection]) {
230 NSBundle *mainBundle = [NSBundle mainBundle];
235 // Launch MacVim using Launch Services (NSWorkspace would be nicer, but
236 // the API to pass Apple Event parameters is broken on 10.4).
237 NSString *path = [mainBundle bundlePath];
238 status = FSPathMakeRef((const UInt8 *)[path UTF8String], &ref, NULL);
239 if (noErr == status) {
240 // Pass parameter to the 'Open' Apple Event that tells MacVim not
241 // to open an untitled window.
242 NSAppleEventDescriptor *desc =
243 [NSAppleEventDescriptor recordDescriptor];
244 [desc setParamDescriptor:
245 [NSAppleEventDescriptor descriptorWithBoolean:NO]
246 forKeyword:keyMMUntitledWindow];
248 LSLaunchFSRefSpec spec = { &ref, 0, NULL, [desc aeDesc],
249 kLSLaunchDefaults, NULL };
250 status = LSOpenFromRefSpec(&spec, NULL);
253 if (noErr != status) {
254 NSLog(@"ERROR: Failed to launch MacVim (path=%@).%@",
255 path, MMSymlinkWarningString);
259 // Launch MacVim using NSTask. For some reason the above code using
260 // Launch Services sometimes fails on LSOpenFromRefSpec() (when it
261 // fails, the dock icon starts bouncing and never stops). It seems
262 // like rebuilding the Launch Services database takes care of this
263 // problem, but the NSTask way seems more stable so stick with it.
265 // NOTE! Using NSTask to launch the GUI has the negative side-effect
266 // that the GUI won't be activated (or raised) so there is a hack in
267 // MMAppController which raises the app when a new window is opened.
268 NSMutableArray *args = [NSMutableArray arrayWithObjects:
269 [NSString stringWithFormat:@"-%@", MMNoWindowKey], @"yes", nil];
270 NSString *exeName = [[mainBundle infoDictionary]
271 objectForKey:@"CFBundleExecutable"];
272 NSString *path = [mainBundle pathForAuxiliaryExecutable:exeName];
274 NSLog(@"ERROR: Could not find MacVim executable in bundle.%@",
275 MMSymlinkWarningString);
279 [NSTask launchedTaskWithLaunchPath:path arguments:args];
282 // HACK! Poll the mach bootstrap server until it returns a valid
283 // connection to detect that MacVim has finished launching. Also set a
284 // time-out date so that we don't get stuck doing this forever.
285 NSDate *timeOutDate = [NSDate dateWithTimeIntervalSinceNow:10];
286 while (![self connection] &&
287 NSOrderedDescending == [timeOutDate compare:[NSDate date]])
288 [[NSRunLoop currentRunLoop]
289 runMode:NSDefaultRunLoopMode
290 beforeDate:[NSDate dateWithTimeIntervalSinceNow:1]];
292 // NOTE: [self connection] will set 'connection' as a side-effect.
294 NSLog(@"WARNING: Timed-out waiting for GUI to launch.");
301 [[NSNotificationCenter defaultCenter] addObserver:self
302 selector:@selector(connectionDidDie:)
303 name:NSConnectionDidDieNotification object:connection];
305 id proxy = [connection rootProxy];
306 [proxy setProtocolForProxy:@protocol(MMAppProtocol)];
308 int pid = [[NSProcessInfo processInfo] processIdentifier];
310 frontendProxy = [proxy connectBackend:self pid:pid];
312 [frontendProxy retain];
313 [frontendProxy setProtocolForProxy:@protocol(MMAppProtocol)];
317 @catch (NSException *e) {
318 NSLog(@"Exception caught when trying to connect backend: \"%@\"", e);
324 - (BOOL)openVimWindow
326 [self queueMessage:OpenVimWindowMsgID data:nil];
332 int type = ClearAllDrawType;
334 // Any draw commands in queue are effectively obsolete since this clearAll
335 // will negate any effect they have, therefore we may as well clear the
337 [drawData setLength:0];
339 [drawData appendBytes:&type length:sizeof(int)];
342 - (void)clearBlockFromRow:(int)row1 column:(int)col1
343 toRow:(int)row2 column:(int)col2
345 int type = ClearBlockDrawType;
347 [drawData appendBytes:&type length:sizeof(int)];
349 [drawData appendBytes:&defaultBackgroundColor length:sizeof(unsigned)];
350 [drawData appendBytes:&row1 length:sizeof(int)];
351 [drawData appendBytes:&col1 length:sizeof(int)];
352 [drawData appendBytes:&row2 length:sizeof(int)];
353 [drawData appendBytes:&col2 length:sizeof(int)];
356 - (void)deleteLinesFromRow:(int)row count:(int)count
357 scrollBottom:(int)bottom left:(int)left right:(int)right
359 int type = DeleteLinesDrawType;
361 [drawData appendBytes:&type length:sizeof(int)];
363 [drawData appendBytes:&defaultBackgroundColor length:sizeof(unsigned)];
364 [drawData appendBytes:&row length:sizeof(int)];
365 [drawData appendBytes:&count length:sizeof(int)];
366 [drawData appendBytes:&bottom length:sizeof(int)];
367 [drawData appendBytes:&left length:sizeof(int)];
368 [drawData appendBytes:&right length:sizeof(int)];
371 - (void)drawString:(char*)s length:(int)len row:(int)row column:(int)col
372 cells:(int)cells flags:(int)flags
374 if (len <= 0 || cells <= 0) return;
376 int type = DrawStringDrawType;
378 [drawData appendBytes:&type length:sizeof(int)];
380 [drawData appendBytes:&backgroundColor length:sizeof(unsigned)];
381 [drawData appendBytes:&foregroundColor length:sizeof(unsigned)];
382 [drawData appendBytes:&specialColor length:sizeof(unsigned)];
383 [drawData appendBytes:&row length:sizeof(int)];
384 [drawData appendBytes:&col length:sizeof(int)];
385 [drawData appendBytes:&cells length:sizeof(int)];
386 [drawData appendBytes:&flags length:sizeof(int)];
387 [drawData appendBytes:&len length:sizeof(int)];
388 [drawData appendBytes:s length:len];
391 - (void)insertLinesFromRow:(int)row count:(int)count
392 scrollBottom:(int)bottom left:(int)left right:(int)right
394 int type = InsertLinesDrawType;
396 [drawData appendBytes:&type length:sizeof(int)];
398 [drawData appendBytes:&defaultBackgroundColor length:sizeof(unsigned)];
399 [drawData appendBytes:&row length:sizeof(int)];
400 [drawData appendBytes:&count length:sizeof(int)];
401 [drawData appendBytes:&bottom length:sizeof(int)];
402 [drawData appendBytes:&left length:sizeof(int)];
403 [drawData appendBytes:&right length:sizeof(int)];
406 - (void)drawCursorAtRow:(int)row column:(int)col shape:(int)shape
407 fraction:(int)percent color:(int)color
409 int type = DrawCursorDrawType;
410 unsigned uc = MM_COLOR(color);
412 [drawData appendBytes:&type length:sizeof(int)];
414 [drawData appendBytes:&uc length:sizeof(unsigned)];
415 [drawData appendBytes:&row length:sizeof(int)];
416 [drawData appendBytes:&col length:sizeof(int)];
417 [drawData appendBytes:&shape length:sizeof(int)];
418 [drawData appendBytes:&percent length:sizeof(int)];
423 // Tend to the run loop, returning immediately if there are no events
425 [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
426 beforeDate:[NSDate distantPast]];
429 // Keyboard and mouse input is handled directly, other input is queued and
430 // processed here. This call may enter a blocking loop.
431 if ([inputQueue count] > 0)
432 [self processInputQueue];
436 - (void)flushQueue:(BOOL)force
438 // NOTE! This method gets called a lot; if we were to flush every time it
439 // got called MacVim would feel unresponsive. So there is a time out which
440 // ensures that the queue isn't flushed too often.
441 if (!force && lastFlushDate && -[lastFlushDate timeIntervalSinceNow]
442 < MMFlushTimeoutInterval
443 && [drawData length] < MMFlushQueueLenHint)
446 if ([drawData length] > 0) {
447 // HACK! Detect changes to 'guifontwide'.
448 if (gui.wide_font != (GuiFont)oldWideFont) {
449 [oldWideFont release];
450 oldWideFont = [(NSFont*)gui.wide_font retain];
451 [self setWideFont:oldWideFont];
454 int type = SetCursorPosDrawType;
455 [drawData appendBytes:&type length:sizeof(type)];
456 [drawData appendBytes:&gui.row length:sizeof(gui.row)];
457 [drawData appendBytes:&gui.col length:sizeof(gui.col)];
459 [self queueMessage:BatchDrawMsgID data:[drawData copy]];
460 [drawData setLength:0];
463 if ([outputQueue count] > 0) {
465 [frontendProxy processCommandQueue:outputQueue];
467 @catch (NSException *e) {
468 NSLog(@"Exception caught when processing command queue: \"%@\"", e);
471 [outputQueue removeAllObjects];
473 [lastFlushDate release];
474 lastFlushDate = [[NSDate date] retain];
478 - (BOOL)waitForInput:(int)milliseconds
480 //NSLog(@"|ENTER| %s%d", _cmd, milliseconds);
482 // Only start the run loop if the input queue is empty, otherwise process
483 // the input first so that the input on queue isn't delayed.
484 if ([inputQueue count]) {
487 NSDate *date = milliseconds > 0 ?
488 [NSDate dateWithTimeIntervalSinceNow:.001*milliseconds] :
489 [NSDate distantFuture];
491 [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
495 // I know of no way to figure out if the run loop exited because input was
496 // found or because of a time out, so I need to manually indicate when
497 // input was received in processInput:data: and then reset it every time
499 BOOL yn = inputReceived;
502 // Keyboard and mouse input is handled directly, other input is queued and
503 // processed here. This call may enter a blocking loop.
504 if ([inputQueue count] > 0)
505 [self processInputQueue];
507 //NSLog(@"|LEAVE| %s input=%d", _cmd, yn);
513 #ifdef MAC_CLIENTSERVER
514 // The default connection is used for the client/server code.
515 [[NSConnection defaultConnection] setRootObject:nil];
516 [[NSConnection defaultConnection] invalidate];
519 // By invalidating the NSConnection the MMWindowController immediately
520 // finds out that the connection is down and as a result
521 // [MMWindowController connectionDidDie:] is invoked.
522 //NSLog(@"%@ %s", [self className], _cmd);
523 [[NSNotificationCenter defaultCenter] removeObserver:self];
524 [connection invalidate];
526 if (fontContainerRef) {
527 ATSFontDeactivate(fontContainerRef, NULL, kATSOptionFlagsDefault);
528 fontContainerRef = 0;
533 - (void)selectTab:(int)index
535 //NSLog(@"%s%d", _cmd, index);
538 NSData *data = [NSData dataWithBytes:&index length:sizeof(int)];
539 [self queueMessage:SelectTabMsgID data:data];
544 //NSLog(@"%s", _cmd);
546 NSMutableData *data = [NSMutableData data];
548 int idx = tabpage_index(curtab) - 1;
549 [data appendBytes:&idx length:sizeof(int)];
552 for (tp = first_tabpage; tp != NULL; tp = tp->tp_next) {
553 // This function puts the label of the tab in the global 'NameBuff'.
554 get_tabline_label(tp, FALSE);
555 char_u *s = NameBuff;
557 if (len <= 0) continue;
560 s = CONVERT_TO_UTF8(s);
563 // Count the number of windows in the tabpage.
564 //win_T *wp = tp->tp_firstwin;
566 //for (wincount = 0; wp != NULL; wp = wp->w_next, ++wincount);
568 //[data appendBytes:&wincount length:sizeof(int)];
569 [data appendBytes:&len length:sizeof(int)];
570 [data appendBytes:s length:len];
573 CONVERT_TO_UTF8_FREE(s);
577 [self queueMessage:UpdateTabBarMsgID data:data];
580 - (BOOL)tabBarVisible
582 return tabBarVisible;
585 - (void)showTabBar:(BOOL)enable
587 tabBarVisible = enable;
589 int msgid = enable ? ShowTabBarMsgID : HideTabBarMsgID;
590 [self queueMessage:msgid data:nil];
593 - (void)setRows:(int)rows columns:(int)cols
595 //NSLog(@"[VimTask] setRows:%d columns:%d", rows, cols);
597 int dim[] = { rows, cols };
598 NSData *data = [NSData dataWithBytes:&dim length:2*sizeof(int)];
600 [self queueMessage:SetTextDimensionsMsgID data:data];
603 - (void)setWindowTitle:(char *)title
605 NSMutableData *data = [NSMutableData data];
606 int len = strlen(title);
607 if (len <= 0) return;
609 [data appendBytes:&len length:sizeof(int)];
610 [data appendBytes:title length:len];
612 [self queueMessage:SetWindowTitleMsgID data:data];
615 - (char *)browseForFileInDirectory:(char *)dir title:(char *)title
618 //NSLog(@"browseForFileInDirectory:%s title:%s saving:%d", dir, title,
622 NSString *ds = dir ? [NSString stringWithUTF8String:dir] : nil;
623 NSString *ts = title ? [NSString stringWithUTF8String:title] : nil;
625 [frontendProxy showSavePanelForDirectory:ds title:ts saving:saving];
627 // Wait until a reply is sent from MMVimController.
628 [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
629 beforeDate:[NSDate distantFuture]];
631 if (dialogReturn && [dialogReturn isKindOfClass:[NSString class]]) {
632 char_u *ret = (char_u*)[dialogReturn UTF8String];
634 ret = CONVERT_FROM_UTF8(ret);
636 s = vim_strsave(ret);
638 CONVERT_FROM_UTF8_FREE(ret);
642 [dialogReturn release]; dialogReturn = nil;
644 @catch (NSException *e) {
645 NSLog(@"Exception caught when showing save panel: \"%@\"", e);
651 - (oneway void)setDialogReturn:(in bycopy id)obj
653 // NOTE: This is called by
654 // - [MMVimController panelDidEnd:::], and
655 // - [MMVimController alertDidEnd:::],
656 // to indicate that a save/open panel or alert has finished.
658 if (obj != dialogReturn) {
659 [dialogReturn release];
660 dialogReturn = [obj retain];
664 - (int)presentDialogWithType:(int)type title:(char *)title message:(char *)msg
665 buttons:(char *)btns textField:(char *)txtfield
668 NSString *message = nil, *text = nil, *textFieldString = nil;
669 NSArray *buttons = nil;
670 int style = NSInformationalAlertStyle;
672 if (VIM_WARNING == type) style = NSWarningAlertStyle;
673 else if (VIM_ERROR == type) style = NSCriticalAlertStyle;
676 NSString *btnString = [NSString stringWithUTF8String:btns];
677 buttons = [btnString componentsSeparatedByString:@"\n"];
680 message = [NSString stringWithUTF8String:title];
682 text = [NSString stringWithUTF8String:msg];
684 // HACK! If there is a '\n\n' or '\n' sequence in the message, then
685 // make the part up to there into the title. We only do this
686 // because Vim has lots of dialogs without a title and they look
688 // TODO: Fix the actual dialog texts.
689 NSRange eolRange = [text rangeOfString:@"\n\n"];
690 if (NSNotFound == eolRange.location)
691 eolRange = [text rangeOfString:@"\n"];
692 if (NSNotFound != eolRange.location) {
693 message = [text substringToIndex:eolRange.location];
694 text = [text substringFromIndex:NSMaxRange(eolRange)];
699 textFieldString = [NSString stringWithUTF8String:txtfield];
702 [frontendProxy presentDialogWithStyle:style message:message
703 informativeText:text buttonTitles:buttons
704 textFieldString:textFieldString];
706 // Wait until a reply is sent from MMVimController.
707 [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
708 beforeDate:[NSDate distantFuture]];
710 if (dialogReturn && [dialogReturn isKindOfClass:[NSArray class]]
711 && [dialogReturn count]) {
712 retval = [[dialogReturn objectAtIndex:0] intValue];
713 if (txtfield && [dialogReturn count] > 1) {
714 NSString *retString = [dialogReturn objectAtIndex:1];
715 char_u *ret = (char_u*)[retString UTF8String];
717 ret = CONVERT_FROM_UTF8(ret);
719 vim_strncpy((char_u*)txtfield, ret, IOSIZE - 1);
721 CONVERT_FROM_UTF8_FREE(ret);
726 [dialogReturn release]; dialogReturn = nil;
728 @catch (NSException *e) {
729 NSLog(@"Exception caught while showing alert dialog: \"%@\"", e);
735 - (void)addMenuWithTag:(int)tag parent:(int)parentTag name:(char *)name
738 //NSLog(@"addMenuWithTag:%d parent:%d name:%s atIndex:%d", tag, parentTag,
741 int namelen = name ? strlen(name) : 0;
742 NSMutableData *data = [NSMutableData data];
744 [data appendBytes:&tag length:sizeof(int)];
745 [data appendBytes:&parentTag length:sizeof(int)];
746 [data appendBytes:&namelen length:sizeof(int)];
747 if (namelen > 0) [data appendBytes:name length:namelen];
748 [data appendBytes:&index length:sizeof(int)];
750 [self queueMessage:AddMenuMsgID data:data];
753 - (void)addMenuItemWithTag:(int)tag parent:(int)parentTag name:(char *)name
754 tip:(char *)tip icon:(char *)icon
755 keyEquivalent:(int)key modifiers:(int)mods
756 action:(NSString *)action atIndex:(int)index
758 //NSLog(@"addMenuItemWithTag:%d parent:%d name:%s tip:%s atIndex:%d", tag,
759 // parentTag, name, tip, index);
761 int namelen = name ? strlen(name) : 0;
762 int tiplen = tip ? strlen(tip) : 0;
763 int iconlen = icon ? strlen(icon) : 0;
764 int eventFlags = vimModMaskToEventModifierFlags(mods);
765 int actionlen = [action lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
766 NSMutableData *data = [NSMutableData data];
768 key = specialKeyToNSKey(key);
770 [data appendBytes:&tag length:sizeof(int)];
771 [data appendBytes:&parentTag length:sizeof(int)];
772 [data appendBytes:&namelen length:sizeof(int)];
773 if (namelen > 0) [data appendBytes:name length:namelen];
774 [data appendBytes:&tiplen length:sizeof(int)];
775 if (tiplen > 0) [data appendBytes:tip length:tiplen];
776 [data appendBytes:&iconlen length:sizeof(int)];
777 if (iconlen > 0) [data appendBytes:icon length:iconlen];
778 [data appendBytes:&actionlen length:sizeof(int)];
779 if (actionlen > 0) [data appendBytes:[action UTF8String] length:actionlen];
780 [data appendBytes:&index length:sizeof(int)];
781 [data appendBytes:&key length:sizeof(int)];
782 [data appendBytes:&eventFlags length:sizeof(int)];
784 [self queueMessage:AddMenuItemMsgID data:data];
787 - (void)removeMenuItemWithTag:(int)tag
789 NSMutableData *data = [NSMutableData data];
790 [data appendBytes:&tag length:sizeof(int)];
792 [self queueMessage:RemoveMenuItemMsgID data:data];
795 - (void)enableMenuItemWithTag:(int)tag state:(int)enabled
797 NSMutableData *data = [NSMutableData data];
799 [data appendBytes:&tag length:sizeof(int)];
800 [data appendBytes:&enabled length:sizeof(int)];
802 [self queueMessage:EnableMenuItemMsgID data:data];
805 - (void)showPopupMenuWithName:(char *)name atMouseLocation:(BOOL)mouse
807 int len = strlen(name);
808 int row = -1, col = -1;
810 if (len <= 0) return;
812 if (!mouse && curwin) {
813 row = curwin->w_wrow;
814 col = curwin->w_wcol;
817 NSMutableData *data = [NSMutableData data];
819 [data appendBytes:&row length:sizeof(int)];
820 [data appendBytes:&col length:sizeof(int)];
821 [data appendBytes:&len length:sizeof(int)];
822 [data appendBytes:name length:len];
824 [self queueMessage:ShowPopupMenuMsgID data:data];
827 - (void)showToolbar:(int)enable flags:(int)flags
829 NSMutableData *data = [NSMutableData data];
831 [data appendBytes:&enable length:sizeof(int)];
832 [data appendBytes:&flags length:sizeof(int)];
834 [self queueMessage:ShowToolbarMsgID data:data];
837 - (void)createScrollbarWithIdentifier:(long)ident type:(int)type
839 NSMutableData *data = [NSMutableData data];
841 [data appendBytes:&ident length:sizeof(long)];
842 [data appendBytes:&type length:sizeof(int)];
844 [self queueMessage:CreateScrollbarMsgID data:data];
847 - (void)destroyScrollbarWithIdentifier:(long)ident
849 NSMutableData *data = [NSMutableData data];
850 [data appendBytes:&ident length:sizeof(long)];
852 [self queueMessage:DestroyScrollbarMsgID data:data];
855 - (void)showScrollbarWithIdentifier:(long)ident state:(int)visible
857 NSMutableData *data = [NSMutableData data];
859 [data appendBytes:&ident length:sizeof(long)];
860 [data appendBytes:&visible length:sizeof(int)];
862 [self queueMessage:ShowScrollbarMsgID data:data];
865 - (void)setScrollbarPosition:(int)pos length:(int)len identifier:(long)ident
867 NSMutableData *data = [NSMutableData data];
869 [data appendBytes:&ident length:sizeof(long)];
870 [data appendBytes:&pos length:sizeof(int)];
871 [data appendBytes:&len length:sizeof(int)];
873 [self queueMessage:SetScrollbarPositionMsgID data:data];
876 - (void)setScrollbarThumbValue:(long)val size:(long)size max:(long)max
877 identifier:(long)ident
879 float fval = max-size+1 > 0 ? (float)val/(max-size+1) : 0;
880 float prop = (float)size/(max+1);
881 if (fval < 0) fval = 0;
882 else if (fval > 1.0f) fval = 1.0f;
883 if (prop < 0) prop = 0;
884 else if (prop > 1.0f) prop = 1.0f;
886 NSMutableData *data = [NSMutableData data];
888 [data appendBytes:&ident length:sizeof(long)];
889 [data appendBytes:&fval length:sizeof(float)];
890 [data appendBytes:&prop length:sizeof(float)];
892 [self queueMessage:SetScrollbarThumbMsgID data:data];
895 - (void)setFont:(NSFont *)font
897 NSString *fontName = [font displayName];
898 float size = [font pointSize];
899 int len = [fontName lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
901 NSMutableData *data = [NSMutableData data];
903 [data appendBytes:&size length:sizeof(float)];
904 [data appendBytes:&len length:sizeof(int)];
905 [data appendBytes:[fontName UTF8String] length:len];
907 [self queueMessage:SetFontMsgID data:data];
911 - (void)setWideFont:(NSFont *)font
913 NSString *fontName = [font displayName];
914 float size = [font pointSize];
915 int len = [fontName lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
916 NSMutableData *data = [NSMutableData data];
918 [data appendBytes:&size length:sizeof(float)];
919 [data appendBytes:&len length:sizeof(int)];
921 [data appendBytes:[fontName UTF8String] length:len];
923 [self queueMessage:SetWideFontMsgID data:data];
926 - (void)executeActionWithName:(NSString *)name
928 int len = [name lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
931 NSMutableData *data = [NSMutableData data];
933 [data appendBytes:&len length:sizeof(int)];
934 [data appendBytes:[name UTF8String] length:len];
936 [self queueMessage:ExecuteActionMsgID data:data];
940 - (void)setMouseShape:(int)shape
942 NSMutableData *data = [NSMutableData data];
943 [data appendBytes:&shape length:sizeof(int)];
944 [self queueMessage:SetMouseShapeMsgID data:data];
947 - (void)setBlinkWait:(int)wait on:(int)on off:(int)off
949 // Vim specifies times in milliseconds, whereas Cocoa wants them in
951 blinkWaitInterval = .001f*wait;
952 blinkOnInterval = .001f*on;
953 blinkOffInterval = .001f*off;
959 [blinkTimer invalidate];
960 [blinkTimer release];
964 if (blinkWaitInterval > 0 && blinkOnInterval > 0 && blinkOffInterval > 0
966 blinkState = MMBlinkStateOn;
968 [[NSTimer scheduledTimerWithTimeInterval:blinkWaitInterval
970 selector:@selector(blinkTimerFired:)
971 userInfo:nil repeats:NO] retain];
972 gui_update_cursor(TRUE, FALSE);
973 [self flushQueue:YES];
979 if (MMBlinkStateOff == blinkState) {
980 gui_update_cursor(TRUE, FALSE);
981 [self flushQueue:YES];
984 blinkState = MMBlinkStateNone;
987 - (void)adjustLinespace:(int)linespace
989 NSMutableData *data = [NSMutableData data];
990 [data appendBytes:&linespace length:sizeof(int)];
991 [self queueMessage:AdjustLinespaceMsgID data:data];
996 [self queueMessage:ActivateMsgID data:nil];
999 - (void)setPreEditRow:(int)row column:(int)col
1001 NSMutableData *data = [NSMutableData data];
1002 [data appendBytes:&row length:sizeof(int)];
1003 [data appendBytes:&col length:sizeof(int)];
1004 [self queueMessage:SetPreEditPositionMsgID data:data];
1007 - (int)lookupColorWithKey:(NSString *)key
1009 if (!(key && [key length] > 0))
1012 NSString *stripKey = [[[[key lowercaseString]
1013 stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]
1014 componentsSeparatedByString:@" "]
1015 componentsJoinedByString:@""];
1017 if (stripKey && [stripKey length] > 0) {
1018 // First of all try to lookup key in the color dictionary; note that
1019 // all keys in this dictionary are lowercase with no whitespace.
1020 id obj = [colorDict objectForKey:stripKey];
1021 if (obj) return [obj intValue];
1023 // The key was not in the dictionary; is it perhaps of the form
1025 if ([stripKey length] > 1 && [stripKey characterAtIndex:0] == '#') {
1026 NSScanner *scanner = [NSScanner scannerWithString:stripKey];
1027 [scanner setScanLocation:1];
1029 if ([scanner scanHexInt:&hex]) {
1034 // As a last resort, check if it is one of the system defined colors.
1035 // The keys in this dictionary are also lowercase with no whitespace.
1036 obj = [sysColorDict objectForKey:stripKey];
1038 NSColor *col = [NSColor performSelector:NSSelectorFromString(obj)];
1041 col = [col colorUsingColorSpaceName:NSCalibratedRGBColorSpace];
1042 [col getRed:&r green:&g blue:&b alpha:&a];
1043 return (((int)(r*255+.5f) & 0xff) << 16)
1044 + (((int)(g*255+.5f) & 0xff) << 8)
1045 + ((int)(b*255+.5f) & 0xff);
1050 //NSLog(@"WARNING: No color with key %@ found.", stripKey);
1054 - (BOOL)hasSpecialKeyWithValue:(NSString *)value
1056 NSEnumerator *e = [[MMBackend specialKeys] objectEnumerator];
1059 while ((obj = [e nextObject])) {
1060 if ([value isEqual:obj])
1067 - (void)enterFullscreen:(int)fuoptions
1069 NSMutableData *data = [NSMutableData data];
1070 [data appendBytes:&fuoptions length:sizeof(int)];
1071 [self queueMessage:EnterFullscreenMsgID data:data];
1074 - (void)leaveFullscreen
1076 [self queueMessage:LeaveFullscreenMsgID data:nil];
1079 - (void)setAntialias:(BOOL)antialias
1081 int msgid = antialias ? EnableAntialiasMsgID : DisableAntialiasMsgID;
1083 [self queueMessage:msgid data:nil];
1086 - (void)updateModifiedFlag
1088 // Notify MacVim if _any_ buffer has changed from unmodified to modified or
1090 int msgid = [self checkForModifiedBuffers]
1091 ? BuffersModifiedMsgID : BuffersNotModifiedMsgID;
1093 [self queueMessage:msgid data:nil];
1096 - (oneway void)processInput:(int)msgid data:(in bycopy NSData *)data
1098 // NOTE: This method might get called whenever the run loop is tended to.
1099 // Normal keyboard and mouse input is added to input buffers, so there is
1100 // no risk in handling these events directly (they return immediately, and
1101 // do not call any other Vim functions). However, other events such
1102 // as 'VimShouldCloseMsgID' may enter blocking loops that wait for key
1103 // events which would cause this method to be called recursively. This
1104 // in turn leads to various difficulties that we do not want to have to
1105 // deal with. To avoid recursive calls here we add all events except
1106 // keyboard and mouse events to an input queue which is processed whenever
1107 // gui_mch_update() is called (see processInputQueue).
1109 //NSLog(@"%s%s", _cmd, MessageStrings[msgid]);
1111 // Don't flush too soon after receiving input or update speed will suffer.
1112 [lastFlushDate release];
1113 lastFlushDate = [[NSDate date] retain];
1115 // Handle keyboard and mouse input now. All other events are queued.
1116 if (InsertTextMsgID == msgid) {
1117 [self handleInsertText:data];
1118 } else if (KeyDownMsgID == msgid || CmdKeyMsgID == msgid) {
1120 const void *bytes = [data bytes];
1121 int mods = *((int*)bytes); bytes += sizeof(int);
1122 int len = *((int*)bytes); bytes += sizeof(int);
1123 NSString *key = [[NSString alloc] initWithBytes:bytes length:len
1124 encoding:NSUTF8StringEncoding];
1125 mods = eventModifierFlagsToVimModMask(mods);
1127 [self handleKeyDown:key modifiers:mods];
1130 } else if (ScrollWheelMsgID == msgid) {
1132 const void *bytes = [data bytes];
1134 int row = *((int*)bytes); bytes += sizeof(int);
1135 int col = *((int*)bytes); bytes += sizeof(int);
1136 int flags = *((int*)bytes); bytes += sizeof(int);
1137 float dy = *((float*)bytes); bytes += sizeof(float);
1139 int button = MOUSE_5;
1140 if (dy > 0) button = MOUSE_4;
1142 flags = eventModifierFlagsToVimMouseModMask(flags);
1144 int numLines = (int)round(dy);
1145 if (numLines < 0) numLines = -numLines;
1146 if (numLines == 0) numLines = 1;
1148 #ifdef FEAT_GUI_SCROLL_WHEEL_FORCE
1149 gui.scroll_wheel_force = numLines;
1152 gui_send_mouse_event(button, col, row, NO, flags);
1153 } else if (MouseDownMsgID == msgid) {
1155 const void *bytes = [data bytes];
1157 int row = *((int*)bytes); bytes += sizeof(int);
1158 int col = *((int*)bytes); bytes += sizeof(int);
1159 int button = *((int*)bytes); bytes += sizeof(int);
1160 int flags = *((int*)bytes); bytes += sizeof(int);
1161 int count = *((int*)bytes); bytes += sizeof(int);
1163 button = eventButtonNumberToVimMouseButton(button);
1165 flags = eventModifierFlagsToVimMouseModMask(flags);
1166 gui_send_mouse_event(button, col, row, count>1, flags);
1168 } else if (MouseUpMsgID == msgid) {
1170 const void *bytes = [data bytes];
1172 int row = *((int*)bytes); bytes += sizeof(int);
1173 int col = *((int*)bytes); bytes += sizeof(int);
1174 int flags = *((int*)bytes); bytes += sizeof(int);
1176 flags = eventModifierFlagsToVimMouseModMask(flags);
1178 gui_send_mouse_event(MOUSE_RELEASE, col, row, NO, flags);
1179 } else if (MouseDraggedMsgID == msgid) {
1181 const void *bytes = [data bytes];
1183 int row = *((int*)bytes); bytes += sizeof(int);
1184 int col = *((int*)bytes); bytes += sizeof(int);
1185 int flags = *((int*)bytes); bytes += sizeof(int);
1187 flags = eventModifierFlagsToVimMouseModMask(flags);
1189 gui_send_mouse_event(MOUSE_DRAG, col, row, NO, flags);
1190 } else if (MouseMovedMsgID == msgid) {
1191 const void *bytes = [data bytes];
1192 int row = *((int*)bytes); bytes += sizeof(int);
1193 int col = *((int*)bytes); bytes += sizeof(int);
1195 gui_mouse_moved(col, row);
1196 } else if (AddInputMsgID == msgid) {
1197 NSString *string = [[NSString alloc] initWithData:data
1198 encoding:NSUTF8StringEncoding];
1200 [self addInput:string];
1203 } else if (TerminateNowMsgID == msgid) {
1204 isTerminating = YES;
1206 // Not keyboard or mouse event, queue it and handle later.
1207 //NSLog(@"Add event %s to input event queue", MessageStrings[msgid]);
1208 [inputQueue addObject:[NSNumber numberWithInt:msgid]];
1209 [inputQueue addObject:(data ? (id)data : (id)[NSNull null])];
1212 // See waitForInput: for an explanation of this flag.
1213 inputReceived = YES;
1216 - (oneway void)processInputAndData:(in bycopy NSArray *)messages
1218 // TODO: Get rid of this method?
1219 //NSLog(@"%s%@", _cmd, messages);
1221 unsigned i, count = [messages count];
1222 for (i = 0; i < count; i += 2) {
1223 int msgid = [[messages objectAtIndex:i] intValue];
1224 id data = [messages objectAtIndex:i+1];
1225 if ([data isEqual:[NSNull null]])
1228 [self processInput:msgid data:data];
1232 - (NSString *)evaluateExpression:(in bycopy NSString *)expr
1234 NSString *eval = nil;
1235 char_u *s = (char_u*)[expr UTF8String];
1238 s = CONVERT_FROM_UTF8(s);
1241 char_u *res = eval_client_expr_to_string(s);
1244 CONVERT_FROM_UTF8_FREE(s);
1250 s = CONVERT_TO_UTF8(s);
1252 eval = [NSString stringWithUTF8String:(char*)s];
1254 CONVERT_TO_UTF8_FREE(s);
1262 - (BOOL)starRegisterToPasteboard:(byref NSPasteboard *)pboard
1264 if (VIsual_active && (State & NORMAL) && clip_star.available) {
1265 // If there is no pasteboard, return YES to indicate that there is text
1270 clip_copy_selection();
1272 // Get the text to put on the pasteboard.
1273 long_u llen = 0; char_u *str = 0;
1274 int type = clip_convert_selection(&str, &llen, &clip_star);
1278 // TODO: Avoid overflow.
1279 int len = (int)llen;
1281 if (output_conv.vc_type != CONV_NONE) {
1282 char_u *conv_str = string_convert(&output_conv, str, &len);
1290 NSString *string = [[NSString alloc]
1291 initWithBytes:str length:len encoding:NSUTF8StringEncoding];
1293 NSArray *types = [NSArray arrayWithObject:NSStringPboardType];
1294 [pboard declareTypes:types owner:nil];
1295 BOOL ok = [pboard setString:string forType:NSStringPboardType];
1306 - (oneway void)addReply:(in bycopy NSString *)reply
1307 server:(in byref id <MMVimServerProtocol>)server
1309 //NSLog(@"addReply:%@ server:%@", reply, (id)server);
1311 // Replies might come at any time and in any order so we keep them in an
1312 // array inside a dictionary with the send port used as key.
1314 NSConnection *conn = [(NSDistantObject*)server connectionForProxy];
1315 // HACK! Assume connection uses mach ports.
1316 int port = [(NSMachPort*)[conn sendPort] machPort];
1317 NSNumber *key = [NSNumber numberWithInt:port];
1319 NSMutableArray *replies = [serverReplyDict objectForKey:key];
1321 replies = [NSMutableArray array];
1322 [serverReplyDict setObject:replies forKey:key];
1325 [replies addObject:reply];
1328 - (void)addInput:(in bycopy NSString *)input
1329 client:(in byref id <MMVimClientProtocol>)client
1331 //NSLog(@"addInput:%@ client:%@", input, (id)client);
1333 [self addInput:input];
1334 [self addClient:(id)client];
1336 inputReceived = YES;
1339 - (NSString *)evaluateExpression:(in bycopy NSString *)expr
1340 client:(in byref id <MMVimClientProtocol>)client
1342 [self addClient:(id)client];
1343 return [self evaluateExpression:expr];
1346 - (void)registerServerWithName:(NSString *)name
1348 NSString *svrName = name;
1349 NSConnection *svrConn = [NSConnection defaultConnection];
1352 for (i = 0; i < MMServerMax; ++i) {
1353 NSString *connName = [self connectionNameFromServerName:svrName];
1355 if ([svrConn registerName:connName]) {
1356 //NSLog(@"Registered server with name: %@", svrName);
1358 // TODO: Set request/reply time-outs to something else?
1360 // Don't wait for requests (time-out means that the message is
1362 [svrConn setRequestTimeout:0];
1363 //[svrConn setReplyTimeout:MMReplyTimeout];
1364 [svrConn setRootObject:self];
1366 char_u *s = (char_u*)[svrName UTF8String];
1368 s = CONVERT_FROM_UTF8(s);
1370 // NOTE: 'serverName' is a global variable
1371 serverName = vim_strsave(s);
1373 CONVERT_FROM_UTF8_FREE(s);
1376 set_vim_var_string(VV_SEND_SERVER, serverName, -1);
1379 need_maketitle = TRUE;
1381 [self queueMessage:SetServerNameMsgID data:
1382 [svrName dataUsingEncoding:NSUTF8StringEncoding]];
1386 svrName = [NSString stringWithFormat:@"%@%d", name, i+1];
1390 - (BOOL)sendToServer:(NSString *)name string:(NSString *)string
1391 reply:(char_u **)reply port:(int *)port expression:(BOOL)expr
1394 // NOTE: If 'name' equals 'serverName' then the request is local (client
1395 // and server are the same). This case is not handled separately, so a
1396 // connection will be set up anyway (this simplifies the code).
1398 NSConnection *conn = [self connectionForServerName:name];
1401 char_u *s = (char_u*)[name UTF8String];
1403 s = CONVERT_FROM_UTF8(s);
1405 EMSG2(_(e_noserver), s);
1407 CONVERT_FROM_UTF8_FREE(s);
1414 // HACK! Assume connection uses mach ports.
1415 *port = [(NSMachPort*)[conn sendPort] machPort];
1418 id proxy = [conn rootProxy];
1419 [proxy setProtocolForProxy:@protocol(MMVimServerProtocol)];
1423 NSString *eval = [proxy evaluateExpression:string client:self];
1426 char_u *r = (char_u*)[eval UTF8String];
1428 r = CONVERT_FROM_UTF8(r);
1430 *reply = vim_strsave(r);
1432 CONVERT_FROM_UTF8_FREE(r);
1435 *reply = vim_strsave((char_u*)_(e_invexprmsg));
1442 [proxy addInput:string client:self];
1445 @catch (NSException *e) {
1446 NSLog(@"WARNING: Caught exception in %s: \"%@\"", _cmd, e);
1453 - (NSArray *)serverList
1455 NSArray *list = nil;
1457 if ([self connection]) {
1458 id proxy = [connection rootProxy];
1459 [proxy setProtocolForProxy:@protocol(MMAppProtocol)];
1462 list = [proxy serverList];
1464 @catch (NSException *e) {
1465 NSLog(@"Exception caught when listing servers: \"%@\"", e);
1468 EMSG(_("E???: No connection to MacVim, server listing not possible."));
1474 - (NSString *)peekForReplyOnPort:(int)port
1476 //NSLog(@"%s%d", _cmd, port);
1478 NSNumber *key = [NSNumber numberWithInt:port];
1479 NSMutableArray *replies = [serverReplyDict objectForKey:key];
1480 if (replies && [replies count]) {
1481 //NSLog(@" %d replies, topmost is: %@", [replies count],
1482 // [replies objectAtIndex:0]);
1483 return [replies objectAtIndex:0];
1486 //NSLog(@" No replies");
1490 - (NSString *)waitForReplyOnPort:(int)port
1492 //NSLog(@"%s%d", _cmd, port);
1494 NSConnection *conn = [self connectionForServerPort:port];
1498 NSNumber *key = [NSNumber numberWithInt:port];
1499 NSMutableArray *replies = nil;
1500 NSString *reply = nil;
1502 // Wait for reply as long as the connection to the server is valid (unless
1503 // user interrupts wait with Ctrl-C).
1504 while (!got_int && [conn isValid] &&
1505 !(replies = [serverReplyDict objectForKey:key])) {
1506 [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
1507 beforeDate:[NSDate distantFuture]];
1511 if ([replies count] > 0) {
1512 reply = [[replies objectAtIndex:0] retain];
1513 //NSLog(@" Got reply: %@", reply);
1514 [replies removeObjectAtIndex:0];
1515 [reply autorelease];
1518 if ([replies count] == 0)
1519 [serverReplyDict removeObjectForKey:key];
1525 - (BOOL)sendReply:(NSString *)reply toPort:(int)port
1527 id client = [clientProxyDict objectForKey:[NSNumber numberWithInt:port]];
1530 //NSLog(@"sendReply:%@ toPort:%d", reply, port);
1531 [client addReply:reply server:self];
1534 @catch (NSException *e) {
1535 NSLog(@"WARNING: Exception caught in %s: \"%@\"", _cmd, e);
1538 EMSG2(_("E???: server2client failed; no client with id 0x%x"), port);
1548 @implementation MMBackend (Private)
1550 - (void)processInputQueue
1552 // NOTE: One of the input events may cause this method to be called
1553 // recursively, so copy the input queue to a local variable and clear it
1554 // before starting to process input events (otherwise we could get stuck in
1555 // an endless loop).
1556 NSArray *q = [inputQueue copy];
1557 unsigned i, count = [q count];
1559 [inputQueue removeAllObjects];
1561 for (i = 0; i < count-1; i += 2) {
1562 int msgid = [[q objectAtIndex:i] intValue];
1563 id data = [q objectAtIndex:i+1];
1564 if ([data isEqual:[NSNull null]])
1567 //NSLog(@"(%d) %s:%s", i, _cmd, MessageStrings[msgid]);
1568 [self handleInputEvent:msgid data:data];
1572 //NSLog(@"Clear input event queue");
1575 - (void)handleInputEvent:(int)msgid data:(NSData *)data
1577 // NOTE: Be careful with what you do in this method. Ideally, a message
1578 // should be handled by adding something to the input buffer and returning
1579 // immediately. If you call a Vim function then it should not enter a loop
1580 // waiting for key presses or in any other way block the process. The
1581 // reason for this being that only one message can be processed at a time,
1582 // so if another message is received while processing, then the new message
1583 // is dropped. See also the comment in processInput:data:.
1585 //NSLog(@"%s%s", _cmd, MessageStrings[msgid]);
1587 if (SelectTabMsgID == msgid) {
1589 const void *bytes = [data bytes];
1590 int idx = *((int*)bytes) + 1;
1591 //NSLog(@"Selecting tab %d", idx);
1592 send_tabline_event(idx);
1593 } else if (CloseTabMsgID == msgid) {
1595 const void *bytes = [data bytes];
1596 int idx = *((int*)bytes) + 1;
1597 //NSLog(@"Closing tab %d", idx);
1598 send_tabline_menu_event(idx, TABLINE_MENU_CLOSE);
1599 } else if (AddNewTabMsgID == msgid) {
1600 //NSLog(@"Adding new tab");
1601 send_tabline_menu_event(0, TABLINE_MENU_NEW);
1602 } else if (DraggedTabMsgID == msgid) {
1604 const void *bytes = [data bytes];
1605 // NOTE! The destination index is 0 based, so do not add 1 to make it 1
1607 int idx = *((int*)bytes);
1610 } else if (SetTextDimensionsMsgID == msgid || LiveResizeMsgID == msgid) {
1612 const void *bytes = [data bytes];
1613 int rows = *((int*)bytes); bytes += sizeof(int);
1614 int cols = *((int*)bytes); bytes += sizeof(int);
1616 // NOTE! Vim doesn't call gui_mch_set_shellsize() after
1617 // gui_resize_shell(), so we have to manually set the rows and columns
1618 // here. (MacVim doesn't change the rows and columns to avoid
1619 // inconsistent states between Vim and MacVim.)
1620 [self queueMessage:msgid data:data];
1622 //NSLog(@"[VimTask] Resizing shell to %dx%d.", cols, rows);
1623 gui_resize_shell(cols, rows);
1624 } else if (ExecuteMenuMsgID == msgid) {
1626 const void *bytes = [data bytes];
1627 int tag = *((int*)bytes); bytes += sizeof(int);
1629 vimmenu_T *menu = (vimmenu_T*)tag;
1630 // TODO! Make sure 'menu' is a valid menu pointer!
1634 } else if (ToggleToolbarMsgID == msgid) {
1635 [self handleToggleToolbar];
1636 } else if (ScrollbarEventMsgID == msgid) {
1637 [self handleScrollbarEvent:data];
1638 } else if (SetFontMsgID == msgid) {
1639 [self handleSetFont:data];
1640 } else if (VimShouldCloseMsgID == msgid) {
1642 } else if (DropFilesMsgID == msgid) {
1643 [self handleDropFiles:data];
1644 } else if (DropStringMsgID == msgid) {
1645 [self handleDropString:data];
1646 } else if (GotFocusMsgID == msgid) {
1648 [self focusChange:YES];
1649 } else if (LostFocusMsgID == msgid) {
1651 [self focusChange:NO];
1652 } else if (SetMouseShapeMsgID == msgid) {
1653 const void *bytes = [data bytes];
1654 int shape = *((int*)bytes); bytes += sizeof(int);
1655 update_mouseshape(shape);
1656 } else if (ODBEditMsgID == msgid) {
1657 [self handleOdbEdit:data];
1658 } else if (XcodeModMsgID == msgid) {
1659 [self handleXcodeMod:data];
1660 } else if (CloseMsgID == msgid) {
1661 // If in Ex mode, then simply exit Ex mode (^U:vi<CR>). Otherwise
1662 // try to close one (Vim-)window by going to Normal mode first
1663 // (CTRL-\_CTRL-N) and then sending ":q<CR>", but only if the
1664 // command-line window is not open. If the command-line window is open
1665 // then we just go back to normal mode (since CTRL-\_CTRL-N closes the
1666 // command-line window).
1667 if (exmode_active) {
1669 add_to_input_buf((char_u*)"\x15:vi\n", 5);
1671 // Go to normal mode
1672 add_to_input_buf((char_u*)"\x1c\xe", 2);
1673 if (0 == cmdwin_type) {
1674 // Command-line window was not open, so :q
1675 add_to_input_buf((char_u*)":q\n", 3);
1679 NSLog(@"WARNING: Unknown message received (msgid=%d)", msgid);
1683 + (NSDictionary *)specialKeys
1685 static NSDictionary *specialKeys = nil;
1688 NSBundle *mainBundle = [NSBundle mainBundle];
1689 NSString *path = [mainBundle pathForResource:@"SpecialKeys"
1691 specialKeys = [[NSDictionary alloc] initWithContentsOfFile:path];
1697 - (void)handleInsertText:(NSData *)data
1701 NSString *key = [[NSString alloc] initWithData:data
1702 encoding:NSUTF8StringEncoding];
1703 char_u *str = (char_u*)[key UTF8String];
1704 int i, len = [key lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
1707 char_u *conv_str = NULL;
1708 if (input_conv.vc_type != CONV_NONE) {
1709 conv_str = string_convert(&input_conv, str, &len);
1715 if (len == 1 && ((str[0] == Ctrl_C && ctrl_c_interrupts)
1716 || (str[0] == intr_char && intr_char != Ctrl_C))) {
1721 for (i = 0; i < len; ++i) {
1722 add_to_input_buf(str+i, 1);
1723 if (CSI == str[i]) {
1724 // NOTE: If the converted string contains the byte CSI, then it
1725 // must be followed by the bytes KS_EXTRA, KE_CSI or things
1727 static char_u extra[2] = { KS_EXTRA, KE_CSI };
1728 add_to_input_buf(extra, 2);
1739 - (void)handleKeyDown:(NSString *)key modifiers:(int)mods
1743 char_u *chars = (char_u*)[key UTF8String];
1745 char_u *conv_str = NULL;
1747 int length = [key lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
1749 // Special keys (arrow keys, function keys, etc.) are stored in a plist so
1750 // that new keys can easily be added.
1751 NSString *specialString = [[MMBackend specialKeys]
1753 if (specialString && [specialString length] > 1) {
1754 //NSLog(@"special key: %@", specialString);
1755 int ikey = TO_SPECIAL([specialString characterAtIndex:0],
1756 [specialString characterAtIndex:1]);
1758 ikey = simplify_key(ikey, &mods);
1763 special[1] = K_SECOND(ikey);
1764 special[2] = K_THIRD(ikey);
1768 } else if (1 == length && TAB == chars[0]) {
1769 // Tab is a trouble child:
1770 // - <Tab> is added to the input buffer as is
1771 // - <S-Tab> is translated to, {CSI,'k','B'} (i.e. 'Back-tab')
1772 // - <M-Tab> should be 0x80|TAB but this is not valid utf-8 so it needs
1773 // to be converted to utf-8
1774 // - <S-M-Tab> is translated to <S-Tab> with ALT modifier
1775 // - <C-Tab> is reserved by Mac OS X
1776 // - <D-Tab> is reserved by Mac OS X
1781 if (mods & MOD_MASK_SHIFT) {
1782 mods &= ~MOD_MASK_SHIFT;
1784 special[1] = K_SECOND(K_S_TAB);
1785 special[2] = K_THIRD(K_S_TAB);
1787 } else if (mods & MOD_MASK_ALT) {
1788 int mtab = 0x80 | TAB;
1792 special[0] = (mtab >> 6) + 0xc0;
1793 special[1] = mtab & 0xbf;
1801 mods &= ~MOD_MASK_ALT;
1803 } else if (length > 0) {
1804 unichar c = [key characterAtIndex:0];
1806 //NSLog(@"non-special: %@ (hex=%x, mods=%d)", key,
1807 // [key characterAtIndex:0], mods);
1809 if (length == 1 && ((c == Ctrl_C && ctrl_c_interrupts)
1810 || (c == intr_char && intr_char != Ctrl_C))) {
1815 // HACK! In most circumstances the Ctrl and Shift modifiers should be
1816 // cleared since they are already added to the key by the AppKit.
1817 // Unfortunately, the only way to deal with when to clear the modifiers
1818 // or not seems to be to have hard-wired rules like this.
1819 if ( !((' ' == c) || (0xa0 == c) || (mods & MOD_MASK_CMD)
1820 || 0x9 == c || 0xd == c || ESC == c) ) {
1821 mods &= ~MOD_MASK_SHIFT;
1822 mods &= ~MOD_MASK_CTRL;
1823 //NSLog(@"clear shift ctrl");
1826 // HACK! All Option+key presses go via 'insert text' messages, except
1827 // for <M-Space>. If the Alt flag is not cleared for <M-Space> it does
1828 // not work to map to it.
1829 if (0xa0 == c && !(mods & MOD_MASK_CMD)) {
1830 //NSLog(@"clear alt");
1831 mods &= ~MOD_MASK_ALT;
1835 if (input_conv.vc_type != CONV_NONE) {
1836 conv_str = string_convert(&input_conv, chars, &length);
1843 if (chars && length > 0) {
1845 //NSLog(@"adding mods: %d", mods);
1847 modChars[1] = KS_MODIFIER;
1849 add_to_input_buf(modChars, 3);
1852 //NSLog(@"add to input buf: 0x%x", chars[0]);
1853 // TODO: Check for CSI bytes?
1854 add_to_input_buf(chars, length);
1863 - (void)queueMessage:(int)msgid data:(NSData *)data
1865 [outputQueue addObject:[NSData dataWithBytes:&msgid length:sizeof(int)]];
1867 [outputQueue addObject:data];
1869 [outputQueue addObject:[NSData data]];
1872 - (void)connectionDidDie:(NSNotification *)notification
1874 // If the main connection to MacVim is lost this means that MacVim was
1875 // either quit (by the user chosing Quit on the MacVim menu), or it has
1876 // crashed. In the former case the flag 'isTerminating' is set and we then
1877 // quit cleanly; in the latter case we make sure the swap files are left
1880 //NSLog(@"%s isTerminating=%d", _cmd, isTerminating);
1884 getout_preserve_modified(1);
1887 - (void)blinkTimerFired:(NSTimer *)timer
1889 NSTimeInterval timeInterval = 0;
1891 [blinkTimer release];
1894 if (MMBlinkStateOn == blinkState) {
1895 gui_undraw_cursor();
1896 blinkState = MMBlinkStateOff;
1897 timeInterval = blinkOffInterval;
1898 } else if (MMBlinkStateOff == blinkState) {
1899 gui_update_cursor(TRUE, FALSE);
1900 blinkState = MMBlinkStateOn;
1901 timeInterval = blinkOnInterval;
1904 if (timeInterval > 0) {
1906 [[NSTimer scheduledTimerWithTimeInterval:timeInterval target:self
1907 selector:@selector(blinkTimerFired:)
1908 userInfo:nil repeats:NO] retain];
1909 [self flushQueue:YES];
1913 - (void)focusChange:(BOOL)on
1915 gui_focus_change(on);
1918 - (void)handleToggleToolbar
1920 // If 'go' contains 'T', then remove it, else add it.
1922 char_u go[sizeof(GO_ALL)+2];
1927 p = vim_strchr(go, GO_TOOLBAR);
1931 char_u *end = go + len;
1937 go[len] = GO_TOOLBAR;
1941 set_option_value((char_u*)"guioptions", 0, go, 0);
1943 // Force screen redraw (does it have to be this complicated?).
1944 redraw_all_later(CLEAR);
1945 update_screen(NOT_VALID);
1948 gui_update_cursor(FALSE, FALSE);
1952 - (void)handleScrollbarEvent:(NSData *)data
1956 const void *bytes = [data bytes];
1957 long ident = *((long*)bytes); bytes += sizeof(long);
1958 int hitPart = *((int*)bytes); bytes += sizeof(int);
1959 float fval = *((float*)bytes); bytes += sizeof(float);
1960 scrollbar_T *sb = gui_find_scrollbar(ident);
1963 scrollbar_T *sb_info = sb->wp ? &sb->wp->w_scrollbars[0] : sb;
1964 long value = sb_info->value;
1965 long size = sb_info->size;
1966 long max = sb_info->max;
1967 BOOL isStillDragging = NO;
1968 BOOL updateKnob = YES;
1971 case NSScrollerDecrementPage:
1972 value -= (size > 2 ? size - 2 : 1);
1974 case NSScrollerIncrementPage:
1975 value += (size > 2 ? size - 2 : 1);
1977 case NSScrollerDecrementLine:
1980 case NSScrollerIncrementLine:
1983 case NSScrollerKnob:
1984 isStillDragging = YES;
1986 case NSScrollerKnobSlot:
1987 value = (long)(fval * (max - size + 1));
1994 //NSLog(@"value %d -> %d", sb_info->value, value);
1995 gui_drag_scrollbar(sb, value, isStillDragging);
1998 // Dragging the knob or option+clicking automatically updates
1999 // the knob position (on the actual NSScroller), so we only
2000 // need to set the knob position in the other cases.
2002 // Update both the left&right vertical scrollbars.
2003 long identLeft = sb->wp->w_scrollbars[SBAR_LEFT].ident;
2004 long identRight = sb->wp->w_scrollbars[SBAR_RIGHT].ident;
2005 [self setScrollbarThumbValue:value size:size max:max
2006 identifier:identLeft];
2007 [self setScrollbarThumbValue:value size:size max:max
2008 identifier:identRight];
2010 // Update the horizontal scrollbar.
2011 [self setScrollbarThumbValue:value size:size max:max
2018 - (void)handleSetFont:(NSData *)data
2022 const void *bytes = [data bytes];
2023 float pointSize = *((float*)bytes); bytes += sizeof(float);
2024 //unsigned len = *((unsigned*)bytes); bytes += sizeof(unsigned);
2025 bytes += sizeof(unsigned); // len not used
2027 NSMutableString *name = [NSMutableString stringWithUTF8String:bytes];
2028 [name appendString:[NSString stringWithFormat:@":h%.2f", pointSize]];
2029 char_u *s = (char_u*)[name UTF8String];
2032 s = CONVERT_FROM_UTF8(s);
2035 set_option_value((char_u*)"guifont", 0, s, 0);
2038 CONVERT_FROM_UTF8_FREE(s);
2041 // Force screen redraw (does it have to be this complicated?).
2042 redraw_all_later(CLEAR);
2043 update_screen(NOT_VALID);
2046 gui_update_cursor(FALSE, FALSE);
2050 - (void)handleDropFiles:(NSData *)data
2052 // TODO: Get rid of this method; instead use Vim script directly. At the
2053 // moment I know how to do this to open files in tabs, but I'm not sure how
2054 // to add the filenames to the command line when in command line mode.
2059 const void *bytes = [data bytes];
2060 const void *end = [data bytes] + [data length];
2061 BOOL forceOpen = *((BOOL*)bytes); bytes += sizeof(BOOL);
2062 int n = *((int*)bytes); bytes += sizeof(int);
2064 if (!forceOpen && (State & CMDLINE)) {
2065 // HACK! If Vim is in command line mode then the files names
2066 // should be added to the command line, instead of opening the
2067 // files in tabs (unless forceOpen is set). This is taken care of by
2068 // gui_handle_drop().
2069 char_u **fnames = (char_u **)alloc(n * sizeof(char_u *));
2072 while (bytes < end && i < n) {
2073 int len = *((int*)bytes); bytes += sizeof(int);
2074 char_u *s = (char_u*)bytes;
2076 s = CONVERT_FROM_UTF8(s);
2078 fnames[i++] = vim_strsave(s);
2080 CONVERT_FROM_UTF8_FREE(s);
2085 // NOTE! This function will free 'fnames'.
2086 // HACK! It is assumed that the 'x' and 'y' arguments are
2087 // unused when in command line mode.
2088 gui_handle_drop(0, 0, 0, fnames, i < n ? i : n);
2091 // HACK! I'm not sure how to get Vim to open a list of files in
2092 // tabs, so instead I create a ':tab drop' command with all the
2093 // files to open and execute it.
2094 NSMutableString *cmd = [NSMutableString stringWithString:@":tab drop"];
2097 for (i = 0; i < n && bytes < end; ++i) {
2098 int len = *((int*)bytes); bytes += sizeof(int);
2099 NSString *file = [NSString stringWithUTF8String:bytes];
2100 file = [file stringByEscapingSpecialFilenameCharacters];
2103 [cmd appendString:@" "];
2104 [cmd appendString:file];
2107 // By going to the last tabpage we ensure that the new tabs will
2108 // appear last (if this call is left out, the taborder becomes
2112 char_u *s = (char_u*)[cmd UTF8String];
2114 s = CONVERT_FROM_UTF8(s);
2118 CONVERT_FROM_UTF8_FREE(s);
2121 // Force screen redraw (does it have to be this complicated?).
2122 // (This code was taken from the end of gui_handle_drop().)
2123 update_screen(NOT_VALID);
2126 gui_update_cursor(FALSE, FALSE);
2133 - (void)handleDropString:(NSData *)data
2138 char_u dropkey[3] = { CSI, KS_EXTRA, (char_u)KE_DROP };
2139 const void *bytes = [data bytes];
2140 int len = *((int*)bytes); bytes += sizeof(int);
2141 NSMutableString *string = [NSMutableString stringWithUTF8String:bytes];
2143 // Replace unrecognized end-of-line sequences with \x0a (line feed).
2144 NSRange range = { 0, [string length] };
2145 unsigned n = [string replaceOccurrencesOfString:@"\x0d\x0a"
2146 withString:@"\x0a" options:0
2149 n = [string replaceOccurrencesOfString:@"\x0d" withString:@"\x0a"
2150 options:0 range:range];
2153 len = [string lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
2154 char_u *s = (char_u*)[string UTF8String];
2156 if (input_conv.vc_type != CONV_NONE)
2157 s = string_convert(&input_conv, s, &len);
2159 dnd_yank_drag_data(s, len);
2161 if (input_conv.vc_type != CONV_NONE)
2164 add_to_input_buf(dropkey, sizeof(dropkey));
2168 - (void)handleOdbEdit:(NSData *)data
2170 #ifdef FEAT_ODB_EDITOR
2171 const void *bytes = [data bytes];
2173 OSType serverID = *((OSType*)bytes); bytes += sizeof(OSType);
2175 char_u *path = NULL;
2176 int pathLen = *((int*)bytes); bytes += sizeof(int);
2178 path = (char_u*)bytes;
2181 path = CONVERT_FROM_UTF8(path);
2185 NSAppleEventDescriptor *token = nil;
2186 DescType tokenType = *((DescType*)bytes); bytes += sizeof(DescType);
2187 int descLen = *((int*)bytes); bytes += sizeof(int);
2189 token = [NSAppleEventDescriptor descriptorWithDescriptorType:tokenType
2195 unsigned i, numFiles = *((unsigned*)bytes); bytes += sizeof(unsigned);
2196 for (i = 0; i < numFiles; ++i) {
2197 int len = *((int*)bytes); bytes += sizeof(int);
2198 char_u *filename = (char_u*)bytes;
2200 filename = CONVERT_FROM_UTF8(filename);
2202 buf_T *buf = buflist_findname(filename);
2204 if (buf->b_odb_token) {
2205 [(NSAppleEventDescriptor*)(buf->b_odb_token) release];
2206 buf->b_odb_token = NULL;
2209 if (buf->b_odb_fname) {
2210 vim_free(buf->b_odb_fname);
2211 buf->b_odb_fname = NULL;
2214 buf->b_odb_server_id = serverID;
2217 buf->b_odb_token = [token retain];
2219 buf->b_odb_fname = vim_strsave(path);
2221 NSLog(@"WARNING: Could not find buffer '%s' for ODB editing.",
2226 CONVERT_FROM_UTF8_FREE(filename);
2231 CONVERT_FROM_UTF8_FREE(path);
2233 #endif // FEAT_ODB_EDITOR
2236 - (void)handleXcodeMod:(NSData *)data
2239 const void *bytes = [data bytes];
2240 DescType type = *((DescType*)bytes); bytes += sizeof(DescType);
2241 unsigned len = *((unsigned*)bytes); bytes += sizeof(unsigned);
2245 NSAppleEventDescriptor *replyEvent = [NSAppleEventDescriptor
2246 descriptorWithDescriptorType:type
2252 - (BOOL)checkForModifiedBuffers
2255 for (buf = firstbuf; buf != NULL; buf = buf->b_next) {
2256 if (bufIsChanged(buf)) {
2264 - (void)addInput:(NSString *)input
2266 char_u *s = (char_u*)[input UTF8String];
2269 s = CONVERT_FROM_UTF8(s);
2272 server_to_input_buf(s);
2275 CONVERT_FROM_UTF8_FREE(s);
2279 @end // MMBackend (Private)
2284 @implementation MMBackend (ClientServer)
2286 - (NSString *)connectionNameFromServerName:(NSString *)name
2288 NSString *bundleIdentifier = [[NSBundle mainBundle] bundleIdentifier];
2290 return [[NSString stringWithFormat:@"%@.%@", bundleIdentifier, name]
2294 - (NSConnection *)connectionForServerName:(NSString *)name
2296 // TODO: Try 'name%d' if 'name' fails.
2297 NSString *connName = [self connectionNameFromServerName:name];
2298 NSConnection *svrConn = [connectionNameDict objectForKey:connName];
2301 svrConn = [NSConnection connectionWithRegisteredName:connName
2303 // Try alternate server...
2304 if (!svrConn && alternateServerName) {
2305 //NSLog(@" trying to connect to alternate server: %@",
2306 // alternateServerName);
2307 connName = [self connectionNameFromServerName:alternateServerName];
2308 svrConn = [NSConnection connectionWithRegisteredName:connName
2312 // Try looking for alternate servers...
2314 //NSLog(@" looking for alternate servers...");
2315 NSString *alt = [self alternateServerNameForName:name];
2316 if (alt != alternateServerName) {
2317 //NSLog(@" found alternate server: %@", string);
2318 [alternateServerName release];
2319 alternateServerName = [alt copy];
2323 // Try alternate server again...
2324 if (!svrConn && alternateServerName) {
2325 //NSLog(@" trying to connect to alternate server: %@",
2326 // alternateServerName);
2327 connName = [self connectionNameFromServerName:alternateServerName];
2328 svrConn = [NSConnection connectionWithRegisteredName:connName
2333 [connectionNameDict setObject:svrConn forKey:connName];
2335 //NSLog(@"Adding %@ as connection observer for %@", self, svrConn);
2336 [[NSNotificationCenter defaultCenter] addObserver:self
2337 selector:@selector(serverConnectionDidDie:)
2338 name:NSConnectionDidDieNotification object:svrConn];
2345 - (NSConnection *)connectionForServerPort:(int)port
2348 NSEnumerator *e = [connectionNameDict objectEnumerator];
2350 while ((conn = [e nextObject])) {
2351 // HACK! Assume connection uses mach ports.
2352 if (port == [(NSMachPort*)[conn sendPort] machPort])
2359 - (void)serverConnectionDidDie:(NSNotification *)notification
2361 //NSLog(@"%s%@", _cmd, notification);
2363 NSConnection *svrConn = [notification object];
2365 //NSLog(@"Removing %@ as connection observer from %@", self, svrConn);
2366 [[NSNotificationCenter defaultCenter]
2368 name:NSConnectionDidDieNotification
2371 [connectionNameDict removeObjectsForKeys:
2372 [connectionNameDict allKeysForObject:svrConn]];
2374 // HACK! Assume connection uses mach ports.
2375 int port = [(NSMachPort*)[svrConn sendPort] machPort];
2376 NSNumber *key = [NSNumber numberWithInt:port];
2378 [clientProxyDict removeObjectForKey:key];
2379 [serverReplyDict removeObjectForKey:key];
2382 - (void)addClient:(NSDistantObject *)client
2384 NSConnection *conn = [client connectionForProxy];
2385 // HACK! Assume connection uses mach ports.
2386 int port = [(NSMachPort*)[conn sendPort] machPort];
2387 NSNumber *key = [NSNumber numberWithInt:port];
2389 if (![clientProxyDict objectForKey:key]) {
2390 [client setProtocolForProxy:@protocol(MMVimClientProtocol)];
2391 [clientProxyDict setObject:client forKey:key];
2394 // NOTE: 'clientWindow' is a global variable which is used by <client>
2395 clientWindow = port;
2398 - (NSString *)alternateServerNameForName:(NSString *)name
2400 if (!(name && [name length] > 0))
2403 // Only look for alternates if 'name' doesn't end in a digit.
2404 unichar lastChar = [name characterAtIndex:[name length]-1];
2405 if (lastChar >= '0' && lastChar <= '9')
2408 // Look for alternates among all current servers.
2409 NSArray *list = [self serverList];
2410 if (!(list && [list count] > 0))
2413 // Filter out servers starting with 'name' and ending with a number. The
2414 // (?i) pattern ensures that the match is case insensitive.
2415 NSString *pat = [NSString stringWithFormat:@"(?i)%@[0-9]+\\z", name];
2416 NSPredicate *pred = [NSPredicate predicateWithFormat:
2417 @"SELF MATCHES %@", pat];
2418 list = [list filteredArrayUsingPredicate:pred];
2419 if ([list count] > 0) {
2420 list = [list sortedArrayUsingSelector:@selector(serverNameCompare:)];
2421 return [list objectAtIndex:0];
2427 @end // MMBackend (ClientServer)
2432 @implementation NSString (MMServerNameCompare)
2433 - (NSComparisonResult)serverNameCompare:(NSString *)string
2435 return [self compare:string
2436 options:NSCaseInsensitiveSearch|NSNumericSearch];
2443 static int eventModifierFlagsToVimModMask(int modifierFlags)
2447 if (modifierFlags & NSShiftKeyMask)
2448 modMask |= MOD_MASK_SHIFT;
2449 if (modifierFlags & NSControlKeyMask)
2450 modMask |= MOD_MASK_CTRL;
2451 if (modifierFlags & NSAlternateKeyMask)
2452 modMask |= MOD_MASK_ALT;
2453 if (modifierFlags & NSCommandKeyMask)
2454 modMask |= MOD_MASK_CMD;
2459 static int vimModMaskToEventModifierFlags(int mods)
2463 if (mods & MOD_MASK_SHIFT)
2464 flags |= NSShiftKeyMask;
2465 if (mods & MOD_MASK_CTRL)
2466 flags |= NSControlKeyMask;
2467 if (mods & MOD_MASK_ALT)
2468 flags |= NSAlternateKeyMask;
2469 if (mods & MOD_MASK_CMD)
2470 flags |= NSCommandKeyMask;
2475 static int eventModifierFlagsToVimMouseModMask(int modifierFlags)
2479 if (modifierFlags & NSShiftKeyMask)
2480 modMask |= MOUSE_SHIFT;
2481 if (modifierFlags & NSControlKeyMask)
2482 modMask |= MOUSE_CTRL;
2483 if (modifierFlags & NSAlternateKeyMask)
2484 modMask |= MOUSE_ALT;
2489 static int eventButtonNumberToVimMouseButton(int buttonNumber)
2491 static int mouseButton[] = { MOUSE_LEFT, MOUSE_RIGHT, MOUSE_MIDDLE };
2493 return (buttonNumber >= 0 && buttonNumber < 3)
2494 ? mouseButton[buttonNumber] : -1;
2497 static int specialKeyToNSKey(int key)
2499 if (!IS_SPECIAL(key))
2506 { K_UP, NSUpArrowFunctionKey },
2507 { K_DOWN, NSDownArrowFunctionKey },
2508 { K_LEFT, NSLeftArrowFunctionKey },
2509 { K_RIGHT, NSRightArrowFunctionKey },
2510 { K_F1, NSF1FunctionKey },
2511 { K_F2, NSF2FunctionKey },
2512 { K_F3, NSF3FunctionKey },
2513 { K_F4, NSF4FunctionKey },
2514 { K_F5, NSF5FunctionKey },
2515 { K_F6, NSF6FunctionKey },
2516 { K_F7, NSF7FunctionKey },
2517 { K_F8, NSF8FunctionKey },
2518 { K_F9, NSF9FunctionKey },
2519 { K_F10, NSF10FunctionKey },
2520 { K_F11, NSF11FunctionKey },
2521 { K_F12, NSF12FunctionKey },
2522 { K_F13, NSF13FunctionKey },
2523 { K_F14, NSF14FunctionKey },
2524 { K_F15, NSF15FunctionKey },
2525 { K_F16, NSF16FunctionKey },
2526 { K_F17, NSF17FunctionKey },
2527 { K_F18, NSF18FunctionKey },
2528 { K_F19, NSF19FunctionKey },
2529 { K_F20, NSF20FunctionKey },
2530 { K_F21, NSF21FunctionKey },
2531 { K_F22, NSF22FunctionKey },
2532 { K_F23, NSF23FunctionKey },
2533 { K_F24, NSF24FunctionKey },
2534 { K_F25, NSF25FunctionKey },
2535 { K_F26, NSF26FunctionKey },
2536 { K_F27, NSF27FunctionKey },
2537 { K_F28, NSF28FunctionKey },
2538 { K_F29, NSF29FunctionKey },
2539 { K_F30, NSF30FunctionKey },
2540 { K_F31, NSF31FunctionKey },
2541 { K_F32, NSF32FunctionKey },
2542 { K_F33, NSF33FunctionKey },
2543 { K_F34, NSF34FunctionKey },
2544 { K_F35, NSF35FunctionKey },
2545 { K_DEL, NSBackspaceCharacter },
2546 { K_BS, NSDeleteCharacter },
2547 { K_HOME, NSHomeFunctionKey },
2548 { K_END, NSEndFunctionKey },
2549 { K_PAGEUP, NSPageUpFunctionKey },
2550 { K_PAGEDOWN, NSPageDownFunctionKey }
2554 for (i = 0; i < sizeof(sp2ns)/sizeof(sp2ns[0]); ++i) {
2555 if (sp2ns[i].special == key)
2556 return sp2ns[i].nskey;