1 /* vi:set ts=8 sts=4 sw=4 ft=objc:
3 * VIM - Vi IMproved by Bram Moolenaar
4 * MacVim GUI port by Bjorn Winckler
6 * Do ":help uganda" in Vim to read copying and usage conditions.
7 * Do ":help credits" in Vim to see a list of people who contributed.
8 * See README.txt for an overview of the Vim source code.
13 * MMBackend communicates with the frontend (MacVim). It maintains a queue of
14 * output which is flushed to the frontend under controlled circumstances (so
15 * as to maintain a steady framerate). Input from the frontend is also handled
18 * The frontend communicates with the backend via the MMBackendProtocol. In
19 * particular, input is sent to the backend via processInput:data: and Vim
20 * state can be queried from the frontend with evaluateExpression:.
22 * It is very important to realize that all state is held by the backend, the
23 * frontend must either ask for state [MMBackend evaluateExpression:] or wait
24 * for the backend to update [MMVimController processCommandQueue:].
26 * The client/server functionality of Vim is handled by the backend. It sets
27 * up a named NSConnection to which other Vim processes can connect.
34 // NOTE: Colors in MMBackend are stored as unsigned ints on the form 0xaarrggbb
35 // whereas colors in Vim are int without the alpha component. Also note that
36 // 'transp' is assumed to be a value between 0 and 100.
37 #define MM_COLOR(col) ((unsigned)( ((col)&0xffffff) | 0xff000000 ))
38 #define MM_COLOR_WITH_TRANSP(col,transp) \
39 ((unsigned)( ((col)&0xffffff) \
40 | ((((unsigned)((((100-(transp))*255)/100)+.5f))&0xff)<<24) ))
43 // This constant controls how often the command queue may be flushed. If it is
44 // too small the app might feel unresponsive; if it is too large there might be
45 // long periods without the screen updating (e.g. when sourcing a large session
46 // file). (The unit is seconds.)
47 static float MMFlushTimeoutInterval = 0.1f;
48 static int MMFlushQueueLenHint = 80*40;
50 static unsigned MMServerMax = 1000;
52 // TODO: Move to separate file.
53 static int eventModifierFlagsToVimModMask(int modifierFlags);
54 static int vimModMaskToEventModifierFlags(int mods);
55 static int eventModifierFlagsToVimMouseModMask(int modifierFlags);
56 static int eventButtonNumberToVimMouseButton(int buttonNumber);
57 static int specialKeyToNSKey(int key);
65 static NSString *MMSymlinkWarningString =
66 @"\n\n\tMost likely this is because you have symlinked directly to\n"
67 "\tthe Vim binary, which Cocoa does not allow. Please use an\n"
68 "\talias or the mvim shell script instead. If you have not used\n"
69 "\ta symlink, then your MacVim.app bundle is incomplete.\n\n";
73 @interface NSString (MMServerNameCompare)
74 - (NSComparisonResult)serverNameCompare:(NSString *)string;
79 @interface MMBackend (Private)
80 - (void)processInputQueue;
81 - (void)handleInputEvent:(int)msgid data:(NSData *)data;
82 + (NSDictionary *)specialKeys;
83 - (void)handleInsertText:(NSData *)data;
84 - (void)handleKeyDown:(NSString *)key modifiers:(int)mods;
85 - (void)queueMessage:(int)msgid data:(NSData *)data;
86 - (void)connectionDidDie:(NSNotification *)notification;
87 - (void)blinkTimerFired:(NSTimer *)timer;
88 - (void)focusChange:(BOOL)on;
89 - (void)handleToggleToolbar;
90 - (void)handleScrollbarEvent:(NSData *)data;
91 - (void)handleSetFont:(NSData *)data;
92 - (void)handleDropFiles:(NSData *)data;
93 - (void)handleDropString:(NSData *)data;
94 - (void)handleOdbEdit:(NSData *)data;
95 - (void)handleXcodeMod:(NSData *)data;
96 - (BOOL)checkForModifiedBuffers;
97 - (void)addInput:(NSString *)input;
102 @interface MMBackend (ClientServer)
103 - (NSString *)connectionNameFromServerName:(NSString *)name;
104 - (NSConnection *)connectionForServerName:(NSString *)name;
105 - (NSConnection *)connectionForServerPort:(int)port;
106 - (void)serverConnectionDidDie:(NSNotification *)notification;
107 - (void)addClient:(NSDistantObject *)client;
108 - (NSString *)alternateServerNameForName:(NSString *)name;
113 @implementation MMBackend
115 + (MMBackend *)sharedInstance
117 static MMBackend *singleton = nil;
118 return singleton ? singleton : (singleton = [MMBackend new]);
124 if (!self) return nil;
126 fontContainerRef = loadFonts();
128 outputQueue = [[NSMutableArray alloc] init];
129 inputQueue = [[NSMutableArray alloc] init];
130 drawData = [[NSMutableData alloc] initWithCapacity:1024];
131 connectionNameDict = [[NSMutableDictionary alloc] init];
132 clientProxyDict = [[NSMutableDictionary alloc] init];
133 serverReplyDict = [[NSMutableDictionary alloc] init];
135 NSBundle *mainBundle = [NSBundle mainBundle];
136 NSString *path = [mainBundle pathForResource:@"Colors" ofType:@"plist"];
138 colorDict = [[NSDictionary dictionaryWithContentsOfFile:path] retain];
140 path = [mainBundle pathForResource:@"SystemColors" ofType:@"plist"];
142 sysColorDict = [[NSDictionary dictionaryWithContentsOfFile:path]
145 if (!(colorDict && sysColorDict))
146 NSLog(@"ERROR: Failed to load color dictionaries.%@",
147 MMSymlinkWarningString);
154 //NSLog(@"%@ %s", [self className], _cmd);
155 [[NSNotificationCenter defaultCenter] removeObserver:self];
157 [oldWideFont release]; oldWideFont = nil;
158 [blinkTimer release]; blinkTimer = nil;
159 [alternateServerName release]; alternateServerName = nil;
160 [serverReplyDict release]; serverReplyDict = nil;
161 [clientProxyDict release]; clientProxyDict = nil;
162 [connectionNameDict release]; connectionNameDict = nil;
163 [inputQueue release]; inputQueue = nil;
164 [outputQueue release]; outputQueue = nil;
165 [drawData release]; drawData = nil;
166 [frontendProxy release]; frontendProxy = nil;
167 [connection release]; connection = nil;
168 [sysColorDict release]; sysColorDict = nil;
169 [colorDict release]; colorDict = nil;
174 - (void)setBackgroundColor:(int)color
176 backgroundColor = MM_COLOR_WITH_TRANSP(color,p_transp);
179 - (void)setForegroundColor:(int)color
181 foregroundColor = MM_COLOR(color);
184 - (void)setSpecialColor:(int)color
186 specialColor = MM_COLOR(color);
189 - (void)setDefaultColorsBackground:(int)bg foreground:(int)fg
191 defaultBackgroundColor = MM_COLOR_WITH_TRANSP(bg,p_transp);
192 defaultForegroundColor = MM_COLOR(fg);
194 NSMutableData *data = [NSMutableData data];
196 [data appendBytes:&defaultBackgroundColor length:sizeof(unsigned)];
197 [data appendBytes:&defaultForegroundColor length:sizeof(unsigned)];
199 [self queueMessage:SetDefaultColorsMsgID data:data];
202 - (NSConnection *)connection
205 // NOTE! If the name of the connection changes here it must also be
206 // updated in MMAppController.m.
207 NSString *name = [NSString stringWithFormat:@"%@-connection",
208 [[NSBundle mainBundle] bundleIdentifier]];
210 connection = [NSConnection connectionWithRegisteredName:name host:nil];
214 // NOTE: 'connection' may be nil here.
220 if (![self connection]) {
221 NSBundle *mainBundle = [NSBundle mainBundle];
226 // Launch MacVim using Launch Services (NSWorkspace would be nicer, but
227 // the API to pass Apple Event parameters is broken on 10.4).
228 NSString *path = [mainBundle bundlePath];
229 status = FSPathMakeRef((const UInt8 *)[path UTF8String], &ref, NULL);
230 if (noErr == status) {
231 // Pass parameter to the 'Open' Apple Event that tells MacVim not
232 // to open an untitled window.
233 NSAppleEventDescriptor *desc =
234 [NSAppleEventDescriptor recordDescriptor];
235 [desc setParamDescriptor:
236 [NSAppleEventDescriptor descriptorWithBoolean:NO]
237 forKeyword:keyMMUntitledWindow];
239 LSLaunchFSRefSpec spec = { &ref, 0, NULL, [desc aeDesc],
240 kLSLaunchDefaults, NULL };
241 status = LSOpenFromRefSpec(&spec, NULL);
244 if (noErr != status) {
245 NSLog(@"ERROR: Failed to launch MacVim (path=%@).%@",
246 path, MMSymlinkWarningString);
250 // Launch MacVim using NSTask. For some reason the above code using
251 // Launch Services sometimes fails on LSOpenFromRefSpec() (when it
252 // fails, the dock icon starts bouncing and never stops). It seems
253 // like rebuilding the Launch Services database takes care of this
254 // problem, but the NSTask way seems more stable so stick with it.
256 // NOTE! Using NSTask to launch the GUI has the negative side-effect
257 // that the GUI won't be activated (or raised) so there is a hack in
258 // MMAppController which raises the app when a new window is opened.
259 NSMutableArray *args = [NSMutableArray arrayWithObjects:
260 [NSString stringWithFormat:@"-%@", MMNoWindowKey], @"yes", nil];
261 NSString *exeName = [[mainBundle infoDictionary]
262 objectForKey:@"CFBundleExecutable"];
263 NSString *path = [mainBundle pathForAuxiliaryExecutable:exeName];
265 NSLog(@"ERROR: Could not find MacVim executable in bundle.%@",
266 MMSymlinkWarningString);
270 [NSTask launchedTaskWithLaunchPath:path arguments:args];
273 // HACK! Poll the mach bootstrap server until it returns a valid
274 // connection to detect that MacVim has finished launching. Also set a
275 // time-out date so that we don't get stuck doing this forever.
276 NSDate *timeOutDate = [NSDate dateWithTimeIntervalSinceNow:10];
277 while (![self connection] &&
278 NSOrderedDescending == [timeOutDate compare:[NSDate date]])
279 [[NSRunLoop currentRunLoop]
280 runMode:NSDefaultRunLoopMode
281 beforeDate:[NSDate dateWithTimeIntervalSinceNow:1]];
283 // NOTE: [self connection] will set 'connection' as a side-effect.
285 NSLog(@"WARNING: Timed-out waiting for GUI to launch.");
292 [[NSNotificationCenter defaultCenter] addObserver:self
293 selector:@selector(connectionDidDie:)
294 name:NSConnectionDidDieNotification object:connection];
296 id proxy = [connection rootProxy];
297 [proxy setProtocolForProxy:@protocol(MMAppProtocol)];
299 int pid = [[NSProcessInfo processInfo] processIdentifier];
301 frontendProxy = [proxy connectBackend:self pid:pid];
303 [frontendProxy retain];
304 [frontendProxy setProtocolForProxy:@protocol(MMAppProtocol)];
308 @catch (NSException *e) {
309 NSLog(@"Exception caught when trying to connect backend: \"%@\"", e);
315 - (BOOL)openVimWindow
317 [self queueMessage:OpenVimWindowMsgID data:nil];
323 int type = ClearAllDrawType;
325 // Any draw commands in queue are effectively obsolete since this clearAll
326 // will negate any effect they have, therefore we may as well clear the
328 [drawData setLength:0];
330 [drawData appendBytes:&type length:sizeof(int)];
333 - (void)clearBlockFromRow:(int)row1 column:(int)col1
334 toRow:(int)row2 column:(int)col2
336 int type = ClearBlockDrawType;
338 [drawData appendBytes:&type length:sizeof(int)];
340 [drawData appendBytes:&defaultBackgroundColor length:sizeof(unsigned)];
341 [drawData appendBytes:&row1 length:sizeof(int)];
342 [drawData appendBytes:&col1 length:sizeof(int)];
343 [drawData appendBytes:&row2 length:sizeof(int)];
344 [drawData appendBytes:&col2 length:sizeof(int)];
347 - (void)deleteLinesFromRow:(int)row count:(int)count
348 scrollBottom:(int)bottom left:(int)left right:(int)right
350 int type = DeleteLinesDrawType;
352 [drawData appendBytes:&type length:sizeof(int)];
354 [drawData appendBytes:&defaultBackgroundColor length:sizeof(unsigned)];
355 [drawData appendBytes:&row length:sizeof(int)];
356 [drawData appendBytes:&count length:sizeof(int)];
357 [drawData appendBytes:&bottom length:sizeof(int)];
358 [drawData appendBytes:&left length:sizeof(int)];
359 [drawData appendBytes:&right length:sizeof(int)];
362 - (void)drawString:(char*)s length:(int)len row:(int)row column:(int)col
363 cells:(int)cells flags:(int)flags
365 if (len <= 0 || cells <= 0) return;
367 int type = DrawStringDrawType;
369 [drawData appendBytes:&type length:sizeof(int)];
371 [drawData appendBytes:&backgroundColor length:sizeof(unsigned)];
372 [drawData appendBytes:&foregroundColor length:sizeof(unsigned)];
373 [drawData appendBytes:&specialColor length:sizeof(unsigned)];
374 [drawData appendBytes:&row length:sizeof(int)];
375 [drawData appendBytes:&col length:sizeof(int)];
376 [drawData appendBytes:&cells length:sizeof(int)];
377 [drawData appendBytes:&flags length:sizeof(int)];
378 [drawData appendBytes:&len length:sizeof(int)];
379 [drawData appendBytes:s length:len];
382 - (void)insertLinesFromRow:(int)row count:(int)count
383 scrollBottom:(int)bottom left:(int)left right:(int)right
385 int type = InsertLinesDrawType;
387 [drawData appendBytes:&type length:sizeof(int)];
389 [drawData appendBytes:&defaultBackgroundColor length:sizeof(unsigned)];
390 [drawData appendBytes:&row length:sizeof(int)];
391 [drawData appendBytes:&count length:sizeof(int)];
392 [drawData appendBytes:&bottom length:sizeof(int)];
393 [drawData appendBytes:&left length:sizeof(int)];
394 [drawData appendBytes:&right length:sizeof(int)];
397 - (void)drawCursorAtRow:(int)row column:(int)col shape:(int)shape
398 fraction:(int)percent color:(int)color
400 int type = DrawCursorDrawType;
401 unsigned uc = MM_COLOR(color);
403 [drawData appendBytes:&type length:sizeof(int)];
405 [drawData appendBytes:&uc length:sizeof(unsigned)];
406 [drawData appendBytes:&row length:sizeof(int)];
407 [drawData appendBytes:&col length:sizeof(int)];
408 [drawData appendBytes:&shape length:sizeof(int)];
409 [drawData appendBytes:&percent length:sizeof(int)];
414 // Tend to the run loop, returning immediately if there are no events
416 [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
417 beforeDate:[NSDate distantPast]];
420 // Keyboard and mouse input is handled directly, other input is queued and
421 // processed here. This call may enter a blocking loop.
422 if ([inputQueue count] > 0)
423 [self processInputQueue];
427 - (void)flushQueue:(BOOL)force
429 // NOTE! This method gets called a lot; if we were to flush every time it
430 // got called MacVim would feel unresponsive. So there is a time out which
431 // ensures that the queue isn't flushed too often.
432 if (!force && lastFlushDate && -[lastFlushDate timeIntervalSinceNow]
433 < MMFlushTimeoutInterval
434 && [drawData length] < MMFlushQueueLenHint)
437 if ([drawData length] > 0) {
438 // HACK! Detect changes to 'guifontwide'.
439 if (gui.wide_font != (GuiFont)oldWideFont) {
440 [oldWideFont release];
441 oldWideFont = [(NSFont*)gui.wide_font retain];
442 [self setWideFont:oldWideFont];
445 int type = SetCursorPosDrawType;
446 [drawData appendBytes:&type length:sizeof(type)];
447 [drawData appendBytes:&gui.row length:sizeof(gui.row)];
448 [drawData appendBytes:&gui.col length:sizeof(gui.col)];
450 [self queueMessage:BatchDrawMsgID data:[drawData copy]];
451 [drawData setLength:0];
454 if ([outputQueue count] > 0) {
456 [frontendProxy processCommandQueue:outputQueue];
458 @catch (NSException *e) {
459 NSLog(@"Exception caught when processing command queue: \"%@\"", e);
462 [outputQueue removeAllObjects];
464 [lastFlushDate release];
465 lastFlushDate = [[NSDate date] retain];
469 - (BOOL)waitForInput:(int)milliseconds
471 //NSLog(@"|ENTER| %s%d", _cmd, milliseconds);
473 // Only start the run loop if the input queue is empty, otherwise process
474 // the input first so that the input on queue isn't delayed.
475 if ([inputQueue count]) {
478 NSDate *date = milliseconds > 0 ?
479 [NSDate dateWithTimeIntervalSinceNow:.001*milliseconds] :
480 [NSDate distantFuture];
482 [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
486 // I know of no way to figure out if the run loop exited because input was
487 // found or because of a time out, so I need to manually indicate when
488 // input was received in processInput:data: and then reset it every time
490 BOOL yn = inputReceived;
493 // Keyboard and mouse input is handled directly, other input is queued and
494 // processed here. This call may enter a blocking loop.
495 if ([inputQueue count] > 0)
496 [self processInputQueue];
498 //NSLog(@"|LEAVE| %s input=%d", _cmd, yn);
504 #ifdef MAC_CLIENTSERVER
505 // The default connection is used for the client/server code.
506 [[NSConnection defaultConnection] setRootObject:nil];
507 [[NSConnection defaultConnection] invalidate];
510 // By invalidating the NSConnection the MMWindowController immediately
511 // finds out that the connection is down and as a result
512 // [MMWindowController connectionDidDie:] is invoked.
513 //NSLog(@"%@ %s", [self className], _cmd);
514 [[NSNotificationCenter defaultCenter] removeObserver:self];
515 [connection invalidate];
517 if (fontContainerRef) {
518 ATSFontDeactivate(fontContainerRef, NULL, kATSOptionFlagsDefault);
519 fontContainerRef = 0;
524 - (void)selectTab:(int)index
526 //NSLog(@"%s%d", _cmd, index);
529 NSData *data = [NSData dataWithBytes:&index length:sizeof(int)];
530 [self queueMessage:SelectTabMsgID data:data];
535 //NSLog(@"%s", _cmd);
537 NSMutableData *data = [NSMutableData data];
539 int idx = tabpage_index(curtab) - 1;
540 [data appendBytes:&idx length:sizeof(int)];
543 for (tp = first_tabpage; tp != NULL; tp = tp->tp_next) {
544 // This function puts the label of the tab in the global 'NameBuff'.
545 get_tabline_label(tp, FALSE);
546 char_u *s = NameBuff;
548 if (len <= 0) continue;
551 s = CONVERT_TO_UTF8(s);
554 // Count the number of windows in the tabpage.
555 //win_T *wp = tp->tp_firstwin;
557 //for (wincount = 0; wp != NULL; wp = wp->w_next, ++wincount);
559 //[data appendBytes:&wincount length:sizeof(int)];
560 [data appendBytes:&len length:sizeof(int)];
561 [data appendBytes:s length:len];
564 CONVERT_TO_UTF8_FREE(s);
568 [self queueMessage:UpdateTabBarMsgID data:data];
571 - (BOOL)tabBarVisible
573 return tabBarVisible;
576 - (void)showTabBar:(BOOL)enable
578 tabBarVisible = enable;
580 int msgid = enable ? ShowTabBarMsgID : HideTabBarMsgID;
581 [self queueMessage:msgid data:nil];
584 - (void)setRows:(int)rows columns:(int)cols
586 //NSLog(@"[VimTask] setRows:%d columns:%d", rows, cols);
588 int dim[] = { rows, cols };
589 NSData *data = [NSData dataWithBytes:&dim length:2*sizeof(int)];
591 [self queueMessage:SetTextDimensionsMsgID data:data];
594 - (void)setWindowTitle:(char *)title
596 NSMutableData *data = [NSMutableData data];
597 int len = strlen(title);
598 if (len <= 0) return;
600 [data appendBytes:&len length:sizeof(int)];
601 [data appendBytes:title length:len];
603 [self queueMessage:SetWindowTitleMsgID data:data];
606 - (char *)browseForFileInDirectory:(char *)dir title:(char *)title
609 //NSLog(@"browseForFileInDirectory:%s title:%s saving:%d", dir, title,
613 NSString *ds = dir ? [NSString stringWithUTF8String:dir] : nil;
614 NSString *ts = title ? [NSString stringWithUTF8String:title] : nil;
616 [frontendProxy showSavePanelForDirectory:ds title:ts saving:saving];
618 // Wait until a reply is sent from MMVimController.
619 [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
620 beforeDate:[NSDate distantFuture]];
622 if (dialogReturn && [dialogReturn isKindOfClass:[NSString class]]) {
623 char_u *ret = (char_u*)[dialogReturn UTF8String];
625 ret = CONVERT_FROM_UTF8(ret);
627 s = vim_strsave(ret);
629 CONVERT_FROM_UTF8_FREE(ret);
633 [dialogReturn release]; dialogReturn = nil;
635 @catch (NSException *e) {
636 NSLog(@"Exception caught when showing save panel: \"%@\"", e);
642 - (oneway void)setDialogReturn:(in bycopy id)obj
644 // NOTE: This is called by
645 // - [MMVimController panelDidEnd:::], and
646 // - [MMVimController alertDidEnd:::],
647 // to indicate that a save/open panel or alert has finished.
649 if (obj != dialogReturn) {
650 [dialogReturn release];
651 dialogReturn = [obj retain];
655 - (int)presentDialogWithType:(int)type title:(char *)title message:(char *)msg
656 buttons:(char *)btns textField:(char *)txtfield
659 NSString *message = nil, *text = nil, *textFieldString = nil;
660 NSArray *buttons = nil;
661 int style = NSInformationalAlertStyle;
663 if (VIM_WARNING == type) style = NSWarningAlertStyle;
664 else if (VIM_ERROR == type) style = NSCriticalAlertStyle;
667 NSString *btnString = [NSString stringWithUTF8String:btns];
668 buttons = [btnString componentsSeparatedByString:@"\n"];
671 message = [NSString stringWithUTF8String:title];
673 text = [NSString stringWithUTF8String:msg];
675 // HACK! If there is a '\n\n' or '\n' sequence in the message, then
676 // make the part up to there into the title. We only do this
677 // because Vim has lots of dialogs without a title and they look
679 // TODO: Fix the actual dialog texts.
680 NSRange eolRange = [text rangeOfString:@"\n\n"];
681 if (NSNotFound == eolRange.location)
682 eolRange = [text rangeOfString:@"\n"];
683 if (NSNotFound != eolRange.location) {
684 message = [text substringToIndex:eolRange.location];
685 text = [text substringFromIndex:NSMaxRange(eolRange)];
690 textFieldString = [NSString stringWithUTF8String:txtfield];
693 [frontendProxy presentDialogWithStyle:style message:message
694 informativeText:text buttonTitles:buttons
695 textFieldString:textFieldString];
697 // Wait until a reply is sent from MMVimController.
698 [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
699 beforeDate:[NSDate distantFuture]];
701 if (dialogReturn && [dialogReturn isKindOfClass:[NSArray class]]
702 && [dialogReturn count]) {
703 retval = [[dialogReturn objectAtIndex:0] intValue];
704 if (txtfield && [dialogReturn count] > 1) {
705 NSString *retString = [dialogReturn objectAtIndex:1];
706 char_u *ret = (char_u*)[retString UTF8String];
708 ret = CONVERT_FROM_UTF8(ret);
710 vim_strncpy((char_u*)txtfield, ret, IOSIZE - 1);
712 CONVERT_FROM_UTF8_FREE(ret);
717 [dialogReturn release]; dialogReturn = nil;
719 @catch (NSException *e) {
720 NSLog(@"Exception caught while showing alert dialog: \"%@\"", e);
726 - (void)addMenuWithTag:(int)tag parent:(int)parentTag name:(char *)name
729 //NSLog(@"addMenuWithTag:%d parent:%d name:%s atIndex:%d", tag, parentTag,
732 int namelen = name ? strlen(name) : 0;
733 NSMutableData *data = [NSMutableData data];
735 [data appendBytes:&tag length:sizeof(int)];
736 [data appendBytes:&parentTag length:sizeof(int)];
737 [data appendBytes:&namelen length:sizeof(int)];
738 if (namelen > 0) [data appendBytes:name length:namelen];
739 [data appendBytes:&index length:sizeof(int)];
741 [self queueMessage:AddMenuMsgID data:data];
744 - (void)addMenuItemWithTag:(int)tag parent:(int)parentTag name:(char *)name
745 tip:(char *)tip icon:(char *)icon
746 keyEquivalent:(int)key modifiers:(int)mods
747 action:(NSString *)action atIndex:(int)index
749 //NSLog(@"addMenuItemWithTag:%d parent:%d name:%s tip:%s atIndex:%d", tag,
750 // parentTag, name, tip, index);
752 int namelen = name ? strlen(name) : 0;
753 int tiplen = tip ? strlen(tip) : 0;
754 int iconlen = icon ? strlen(icon) : 0;
755 int eventFlags = vimModMaskToEventModifierFlags(mods);
756 int actionlen = [action lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
757 NSMutableData *data = [NSMutableData data];
759 key = specialKeyToNSKey(key);
761 [data appendBytes:&tag length:sizeof(int)];
762 [data appendBytes:&parentTag length:sizeof(int)];
763 [data appendBytes:&namelen length:sizeof(int)];
764 if (namelen > 0) [data appendBytes:name length:namelen];
765 [data appendBytes:&tiplen length:sizeof(int)];
766 if (tiplen > 0) [data appendBytes:tip length:tiplen];
767 [data appendBytes:&iconlen length:sizeof(int)];
768 if (iconlen > 0) [data appendBytes:icon length:iconlen];
769 [data appendBytes:&actionlen length:sizeof(int)];
770 if (actionlen > 0) [data appendBytes:[action UTF8String] length:actionlen];
771 [data appendBytes:&index length:sizeof(int)];
772 [data appendBytes:&key length:sizeof(int)];
773 [data appendBytes:&eventFlags length:sizeof(int)];
775 [self queueMessage:AddMenuItemMsgID data:data];
778 - (void)removeMenuItemWithTag:(int)tag
780 NSMutableData *data = [NSMutableData data];
781 [data appendBytes:&tag length:sizeof(int)];
783 [self queueMessage:RemoveMenuItemMsgID data:data];
786 - (void)enableMenuItemWithTag:(int)tag state:(int)enabled
788 NSMutableData *data = [NSMutableData data];
790 [data appendBytes:&tag length:sizeof(int)];
791 [data appendBytes:&enabled length:sizeof(int)];
793 [self queueMessage:EnableMenuItemMsgID data:data];
796 - (void)showPopupMenuWithName:(char *)name atMouseLocation:(BOOL)mouse
798 int len = strlen(name);
799 int row = -1, col = -1;
801 if (len <= 0) return;
803 if (!mouse && curwin) {
804 row = curwin->w_wrow;
805 col = curwin->w_wcol;
808 NSMutableData *data = [NSMutableData data];
810 [data appendBytes:&row length:sizeof(int)];
811 [data appendBytes:&col length:sizeof(int)];
812 [data appendBytes:&len length:sizeof(int)];
813 [data appendBytes:name length:len];
815 [self queueMessage:ShowPopupMenuMsgID data:data];
818 - (void)showToolbar:(int)enable flags:(int)flags
820 NSMutableData *data = [NSMutableData data];
822 [data appendBytes:&enable length:sizeof(int)];
823 [data appendBytes:&flags length:sizeof(int)];
825 [self queueMessage:ShowToolbarMsgID data:data];
828 - (void)createScrollbarWithIdentifier:(long)ident type:(int)type
830 NSMutableData *data = [NSMutableData data];
832 [data appendBytes:&ident length:sizeof(long)];
833 [data appendBytes:&type length:sizeof(int)];
835 [self queueMessage:CreateScrollbarMsgID data:data];
838 - (void)destroyScrollbarWithIdentifier:(long)ident
840 NSMutableData *data = [NSMutableData data];
841 [data appendBytes:&ident length:sizeof(long)];
843 [self queueMessage:DestroyScrollbarMsgID data:data];
846 - (void)showScrollbarWithIdentifier:(long)ident state:(int)visible
848 NSMutableData *data = [NSMutableData data];
850 [data appendBytes:&ident length:sizeof(long)];
851 [data appendBytes:&visible length:sizeof(int)];
853 [self queueMessage:ShowScrollbarMsgID data:data];
856 - (void)setScrollbarPosition:(int)pos length:(int)len identifier:(long)ident
858 NSMutableData *data = [NSMutableData data];
860 [data appendBytes:&ident length:sizeof(long)];
861 [data appendBytes:&pos length:sizeof(int)];
862 [data appendBytes:&len length:sizeof(int)];
864 [self queueMessage:SetScrollbarPositionMsgID data:data];
867 - (void)setScrollbarThumbValue:(long)val size:(long)size max:(long)max
868 identifier:(long)ident
870 float fval = max-size+1 > 0 ? (float)val/(max-size+1) : 0;
871 float prop = (float)size/(max+1);
872 if (fval < 0) fval = 0;
873 else if (fval > 1.0f) fval = 1.0f;
874 if (prop < 0) prop = 0;
875 else if (prop > 1.0f) prop = 1.0f;
877 NSMutableData *data = [NSMutableData data];
879 [data appendBytes:&ident length:sizeof(long)];
880 [data appendBytes:&fval length:sizeof(float)];
881 [data appendBytes:&prop length:sizeof(float)];
883 [self queueMessage:SetScrollbarThumbMsgID data:data];
886 - (void)setFont:(NSFont *)font
888 NSString *fontName = [font displayName];
889 float size = [font pointSize];
890 int len = [fontName lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
892 NSMutableData *data = [NSMutableData data];
894 [data appendBytes:&size length:sizeof(float)];
895 [data appendBytes:&len length:sizeof(int)];
896 [data appendBytes:[fontName UTF8String] length:len];
898 [self queueMessage:SetFontMsgID data:data];
902 - (void)setWideFont:(NSFont *)font
904 NSString *fontName = [font displayName];
905 float size = [font pointSize];
906 int len = [fontName lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
907 NSMutableData *data = [NSMutableData data];
909 [data appendBytes:&size length:sizeof(float)];
910 [data appendBytes:&len length:sizeof(int)];
912 [data appendBytes:[fontName UTF8String] length:len];
914 [self queueMessage:SetWideFontMsgID data:data];
917 - (void)executeActionWithName:(NSString *)name
919 int len = [name lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
922 NSMutableData *data = [NSMutableData data];
924 [data appendBytes:&len length:sizeof(int)];
925 [data appendBytes:[name UTF8String] length:len];
927 [self queueMessage:ExecuteActionMsgID data:data];
931 - (void)setMouseShape:(int)shape
933 NSMutableData *data = [NSMutableData data];
934 [data appendBytes:&shape length:sizeof(int)];
935 [self queueMessage:SetMouseShapeMsgID data:data];
938 - (void)setBlinkWait:(int)wait on:(int)on off:(int)off
940 // Vim specifies times in milliseconds, whereas Cocoa wants them in
942 blinkWaitInterval = .001f*wait;
943 blinkOnInterval = .001f*on;
944 blinkOffInterval = .001f*off;
950 [blinkTimer invalidate];
951 [blinkTimer release];
955 if (blinkWaitInterval > 0 && blinkOnInterval > 0 && blinkOffInterval > 0
957 blinkState = MMBlinkStateOn;
959 [[NSTimer scheduledTimerWithTimeInterval:blinkWaitInterval
961 selector:@selector(blinkTimerFired:)
962 userInfo:nil repeats:NO] retain];
963 gui_update_cursor(TRUE, FALSE);
964 [self flushQueue:YES];
970 if (MMBlinkStateOff == blinkState) {
971 gui_update_cursor(TRUE, FALSE);
972 [self flushQueue:YES];
975 blinkState = MMBlinkStateNone;
978 - (void)adjustLinespace:(int)linespace
980 NSMutableData *data = [NSMutableData data];
981 [data appendBytes:&linespace length:sizeof(int)];
982 [self queueMessage:AdjustLinespaceMsgID data:data];
987 [self queueMessage:ActivateMsgID data:nil];
990 - (void)setPreEditRow:(int)row column:(int)col
992 NSMutableData *data = [NSMutableData data];
993 [data appendBytes:&row length:sizeof(int)];
994 [data appendBytes:&col length:sizeof(int)];
995 [self queueMessage:SetPreEditPositionMsgID data:data];
998 - (int)lookupColorWithKey:(NSString *)key
1000 if (!(key && [key length] > 0))
1003 NSString *stripKey = [[[[key lowercaseString]
1004 stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]
1005 componentsSeparatedByString:@" "]
1006 componentsJoinedByString:@""];
1008 if (stripKey && [stripKey length] > 0) {
1009 // First of all try to lookup key in the color dictionary; note that
1010 // all keys in this dictionary are lowercase with no whitespace.
1011 id obj = [colorDict objectForKey:stripKey];
1012 if (obj) return [obj intValue];
1014 // The key was not in the dictionary; is it perhaps of the form
1016 if ([stripKey length] > 1 && [stripKey characterAtIndex:0] == '#') {
1017 NSScanner *scanner = [NSScanner scannerWithString:stripKey];
1018 [scanner setScanLocation:1];
1020 if ([scanner scanHexInt:&hex]) {
1025 // As a last resort, check if it is one of the system defined colors.
1026 // The keys in this dictionary are also lowercase with no whitespace.
1027 obj = [sysColorDict objectForKey:stripKey];
1029 NSColor *col = [NSColor performSelector:NSSelectorFromString(obj)];
1032 col = [col colorUsingColorSpaceName:NSCalibratedRGBColorSpace];
1033 [col getRed:&r green:&g blue:&b alpha:&a];
1034 return (((int)(r*255+.5f) & 0xff) << 16)
1035 + (((int)(g*255+.5f) & 0xff) << 8)
1036 + ((int)(b*255+.5f) & 0xff);
1041 //NSLog(@"WARNING: No color with key %@ found.", stripKey);
1045 - (BOOL)hasSpecialKeyWithValue:(NSString *)value
1047 NSEnumerator *e = [[MMBackend specialKeys] objectEnumerator];
1050 while ((obj = [e nextObject])) {
1051 if ([value isEqual:obj])
1058 - (void)enterFullscreen:(int)fuoptions
1060 NSMutableData *data = [NSMutableData data];
1061 [data appendBytes:&fuoptions length:sizeof(int)];
1062 [self queueMessage:EnterFullscreenMsgID data:data];
1065 - (void)leaveFullscreen
1067 [self queueMessage:LeaveFullscreenMsgID data:nil];
1070 - (void)setAntialias:(BOOL)antialias
1072 int msgid = antialias ? EnableAntialiasMsgID : DisableAntialiasMsgID;
1074 [self queueMessage:msgid data:nil];
1077 - (void)updateModifiedFlag
1079 // Notify MacVim if _any_ buffer has changed from unmodified to modified or
1081 int msgid = [self checkForModifiedBuffers]
1082 ? BuffersModifiedMsgID : BuffersNotModifiedMsgID;
1084 [self queueMessage:msgid data:nil];
1087 - (oneway void)processInput:(int)msgid data:(in bycopy NSData *)data
1089 // NOTE: This method might get called whenever the run loop is tended to.
1090 // Normal keyboard and mouse input is added to input buffers, so there is
1091 // no risk in handling these events directly (they return immediately, and
1092 // do not call any other Vim functions). However, other events such
1093 // as 'VimShouldCloseMsgID' may enter blocking loops that wait for key
1094 // events which would cause this method to be called recursively. This
1095 // in turn leads to various difficulties that we do not want to have to
1096 // deal with. To avoid recursive calls here we add all events except
1097 // keyboard and mouse events to an input queue which is processed whenever
1098 // gui_mch_update() is called (see processInputQueue).
1100 //NSLog(@"%s%s", _cmd, MessageStrings[msgid]);
1102 // Don't flush too soon after receiving input or update speed will suffer.
1103 [lastFlushDate release];
1104 lastFlushDate = [[NSDate date] retain];
1106 // Handle keyboard and mouse input now. All other events are queued.
1107 if (InsertTextMsgID == msgid) {
1108 [self handleInsertText:data];
1109 } else if (KeyDownMsgID == msgid || CmdKeyMsgID == msgid) {
1111 const void *bytes = [data bytes];
1112 int mods = *((int*)bytes); bytes += sizeof(int);
1113 int len = *((int*)bytes); bytes += sizeof(int);
1114 NSString *key = [[NSString alloc] initWithBytes:bytes length:len
1115 encoding:NSUTF8StringEncoding];
1116 mods = eventModifierFlagsToVimModMask(mods);
1118 [self handleKeyDown:key modifiers:mods];
1121 } else if (ScrollWheelMsgID == msgid) {
1123 const void *bytes = [data bytes];
1125 int row = *((int*)bytes); bytes += sizeof(int);
1126 int col = *((int*)bytes); bytes += sizeof(int);
1127 int flags = *((int*)bytes); bytes += sizeof(int);
1128 float dy = *((float*)bytes); bytes += sizeof(float);
1130 int button = MOUSE_5;
1131 if (dy > 0) button = MOUSE_4;
1133 flags = eventModifierFlagsToVimMouseModMask(flags);
1135 int numLines = (int)round(dy);
1136 if (numLines < 0) numLines = -numLines;
1137 if (numLines == 0) numLines = 1;
1139 #ifdef FEAT_GUI_SCROLL_WHEEL_FORCE
1140 gui.scroll_wheel_force = numLines;
1143 gui_send_mouse_event(button, col, row, NO, flags);
1144 } else if (MouseDownMsgID == msgid) {
1146 const void *bytes = [data bytes];
1148 int row = *((int*)bytes); bytes += sizeof(int);
1149 int col = *((int*)bytes); bytes += sizeof(int);
1150 int button = *((int*)bytes); bytes += sizeof(int);
1151 int flags = *((int*)bytes); bytes += sizeof(int);
1152 int count = *((int*)bytes); bytes += sizeof(int);
1154 button = eventButtonNumberToVimMouseButton(button);
1156 flags = eventModifierFlagsToVimMouseModMask(flags);
1157 gui_send_mouse_event(button, col, row, count>1, flags);
1159 } else if (MouseUpMsgID == msgid) {
1161 const void *bytes = [data bytes];
1163 int row = *((int*)bytes); bytes += sizeof(int);
1164 int col = *((int*)bytes); bytes += sizeof(int);
1165 int flags = *((int*)bytes); bytes += sizeof(int);
1167 flags = eventModifierFlagsToVimMouseModMask(flags);
1169 gui_send_mouse_event(MOUSE_RELEASE, col, row, NO, flags);
1170 } else if (MouseDraggedMsgID == msgid) {
1172 const void *bytes = [data bytes];
1174 int row = *((int*)bytes); bytes += sizeof(int);
1175 int col = *((int*)bytes); bytes += sizeof(int);
1176 int flags = *((int*)bytes); bytes += sizeof(int);
1178 flags = eventModifierFlagsToVimMouseModMask(flags);
1180 gui_send_mouse_event(MOUSE_DRAG, col, row, NO, flags);
1181 } else if (MouseMovedMsgID == msgid) {
1182 const void *bytes = [data bytes];
1183 int row = *((int*)bytes); bytes += sizeof(int);
1184 int col = *((int*)bytes); bytes += sizeof(int);
1186 gui_mouse_moved(col, row);
1187 } else if (AddInputMsgID == msgid) {
1188 NSString *string = [[NSString alloc] initWithData:data
1189 encoding:NSUTF8StringEncoding];
1191 [self addInput:string];
1194 } else if (TerminateNowMsgID == msgid) {
1195 isTerminating = YES;
1197 // Not keyboard or mouse event, queue it and handle later.
1198 //NSLog(@"Add event %s to input event queue", MessageStrings[msgid]);
1199 [inputQueue addObject:[NSNumber numberWithInt:msgid]];
1200 [inputQueue addObject:(data ? (id)data : (id)[NSNull null])];
1203 // See waitForInput: for an explanation of this flag.
1204 inputReceived = YES;
1207 - (oneway void)processInputAndData:(in bycopy NSArray *)messages
1209 // TODO: Get rid of this method?
1210 //NSLog(@"%s%@", _cmd, messages);
1212 unsigned i, count = [messages count];
1213 for (i = 0; i < count; i += 2) {
1214 int msgid = [[messages objectAtIndex:i] intValue];
1215 id data = [messages objectAtIndex:i+1];
1216 if ([data isEqual:[NSNull null]])
1219 [self processInput:msgid data:data];
1223 - (NSString *)evaluateExpression:(in bycopy NSString *)expr
1225 NSString *eval = nil;
1226 char_u *s = (char_u*)[expr UTF8String];
1229 s = CONVERT_FROM_UTF8(s);
1232 char_u *res = eval_client_expr_to_string(s);
1235 CONVERT_FROM_UTF8_FREE(s);
1241 s = CONVERT_TO_UTF8(s);
1243 eval = [NSString stringWithUTF8String:(char*)s];
1245 CONVERT_TO_UTF8_FREE(s);
1253 - (BOOL)starRegisterToPasteboard:(byref NSPasteboard *)pboard
1255 if (VIsual_active && (State & NORMAL) && clip_star.available) {
1256 // If there is no pasteboard, return YES to indicate that there is text
1261 clip_copy_selection();
1263 // Get the text to put on the pasteboard.
1264 long_u llen = 0; char_u *str = 0;
1265 int type = clip_convert_selection(&str, &llen, &clip_star);
1269 // TODO: Avoid overflow.
1270 int len = (int)llen;
1272 if (output_conv.vc_type != CONV_NONE) {
1273 char_u *conv_str = string_convert(&output_conv, str, &len);
1281 NSString *string = [[NSString alloc]
1282 initWithBytes:str length:len encoding:NSUTF8StringEncoding];
1284 NSArray *types = [NSArray arrayWithObject:NSStringPboardType];
1285 [pboard declareTypes:types owner:nil];
1286 BOOL ok = [pboard setString:string forType:NSStringPboardType];
1297 - (oneway void)addReply:(in bycopy NSString *)reply
1298 server:(in byref id <MMVimServerProtocol>)server
1300 //NSLog(@"addReply:%@ server:%@", reply, (id)server);
1302 // Replies might come at any time and in any order so we keep them in an
1303 // array inside a dictionary with the send port used as key.
1305 NSConnection *conn = [(NSDistantObject*)server connectionForProxy];
1306 // HACK! Assume connection uses mach ports.
1307 int port = [(NSMachPort*)[conn sendPort] machPort];
1308 NSNumber *key = [NSNumber numberWithInt:port];
1310 NSMutableArray *replies = [serverReplyDict objectForKey:key];
1312 replies = [NSMutableArray array];
1313 [serverReplyDict setObject:replies forKey:key];
1316 [replies addObject:reply];
1319 - (void)addInput:(in bycopy NSString *)input
1320 client:(in byref id <MMVimClientProtocol>)client
1322 //NSLog(@"addInput:%@ client:%@", input, (id)client);
1324 [self addInput:input];
1325 [self addClient:(id)client];
1327 inputReceived = YES;
1330 - (NSString *)evaluateExpression:(in bycopy NSString *)expr
1331 client:(in byref id <MMVimClientProtocol>)client
1333 [self addClient:(id)client];
1334 return [self evaluateExpression:expr];
1337 - (void)registerServerWithName:(NSString *)name
1339 NSString *svrName = name;
1340 NSConnection *svrConn = [NSConnection defaultConnection];
1343 for (i = 0; i < MMServerMax; ++i) {
1344 NSString *connName = [self connectionNameFromServerName:svrName];
1346 if ([svrConn registerName:connName]) {
1347 //NSLog(@"Registered server with name: %@", svrName);
1349 // TODO: Set request/reply time-outs to something else?
1351 // Don't wait for requests (time-out means that the message is
1353 [svrConn setRequestTimeout:0];
1354 //[svrConn setReplyTimeout:MMReplyTimeout];
1355 [svrConn setRootObject:self];
1357 char_u *s = (char_u*)[svrName UTF8String];
1359 s = CONVERT_FROM_UTF8(s);
1361 // NOTE: 'serverName' is a global variable
1362 serverName = vim_strsave(s);
1364 CONVERT_FROM_UTF8_FREE(s);
1367 set_vim_var_string(VV_SEND_SERVER, serverName, -1);
1370 need_maketitle = TRUE;
1372 [self queueMessage:SetServerNameMsgID data:
1373 [svrName dataUsingEncoding:NSUTF8StringEncoding]];
1377 svrName = [NSString stringWithFormat:@"%@%d", name, i+1];
1381 - (BOOL)sendToServer:(NSString *)name string:(NSString *)string
1382 reply:(char_u **)reply port:(int *)port expression:(BOOL)expr
1385 // NOTE: If 'name' equals 'serverName' then the request is local (client
1386 // and server are the same). This case is not handled separately, so a
1387 // connection will be set up anyway (this simplifies the code).
1389 NSConnection *conn = [self connectionForServerName:name];
1392 char_u *s = (char_u*)[name UTF8String];
1394 s = CONVERT_FROM_UTF8(s);
1396 EMSG2(_(e_noserver), s);
1398 CONVERT_FROM_UTF8_FREE(s);
1405 // HACK! Assume connection uses mach ports.
1406 *port = [(NSMachPort*)[conn sendPort] machPort];
1409 id proxy = [conn rootProxy];
1410 [proxy setProtocolForProxy:@protocol(MMVimServerProtocol)];
1414 NSString *eval = [proxy evaluateExpression:string client:self];
1417 char_u *r = (char_u*)[eval UTF8String];
1419 r = CONVERT_FROM_UTF8(r);
1421 *reply = vim_strsave(r);
1423 CONVERT_FROM_UTF8_FREE(r);
1426 *reply = vim_strsave((char_u*)_(e_invexprmsg));
1433 [proxy addInput:string client:self];
1436 @catch (NSException *e) {
1437 NSLog(@"WARNING: Caught exception in %s: \"%@\"", _cmd, e);
1444 - (NSArray *)serverList
1446 NSArray *list = nil;
1448 if ([self connection]) {
1449 id proxy = [connection rootProxy];
1450 [proxy setProtocolForProxy:@protocol(MMAppProtocol)];
1453 list = [proxy serverList];
1455 @catch (NSException *e) {
1456 NSLog(@"Exception caught when listing servers: \"%@\"", e);
1459 EMSG(_("E???: No connection to MacVim, server listing not possible."));
1465 - (NSString *)peekForReplyOnPort:(int)port
1467 //NSLog(@"%s%d", _cmd, port);
1469 NSNumber *key = [NSNumber numberWithInt:port];
1470 NSMutableArray *replies = [serverReplyDict objectForKey:key];
1471 if (replies && [replies count]) {
1472 //NSLog(@" %d replies, topmost is: %@", [replies count],
1473 // [replies objectAtIndex:0]);
1474 return [replies objectAtIndex:0];
1477 //NSLog(@" No replies");
1481 - (NSString *)waitForReplyOnPort:(int)port
1483 //NSLog(@"%s%d", _cmd, port);
1485 NSConnection *conn = [self connectionForServerPort:port];
1489 NSNumber *key = [NSNumber numberWithInt:port];
1490 NSMutableArray *replies = nil;
1491 NSString *reply = nil;
1493 // Wait for reply as long as the connection to the server is valid (unless
1494 // user interrupts wait with Ctrl-C).
1495 while (!got_int && [conn isValid] &&
1496 !(replies = [serverReplyDict objectForKey:key])) {
1497 [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
1498 beforeDate:[NSDate distantFuture]];
1502 if ([replies count] > 0) {
1503 reply = [[replies objectAtIndex:0] retain];
1504 //NSLog(@" Got reply: %@", reply);
1505 [replies removeObjectAtIndex:0];
1506 [reply autorelease];
1509 if ([replies count] == 0)
1510 [serverReplyDict removeObjectForKey:key];
1516 - (BOOL)sendReply:(NSString *)reply toPort:(int)port
1518 id client = [clientProxyDict objectForKey:[NSNumber numberWithInt:port]];
1521 //NSLog(@"sendReply:%@ toPort:%d", reply, port);
1522 [client addReply:reply server:self];
1525 @catch (NSException *e) {
1526 NSLog(@"WARNING: Exception caught in %s: \"%@\"", _cmd, e);
1529 EMSG2(_("E???: server2client failed; no client with id 0x%x"), port);
1539 @implementation MMBackend (Private)
1541 - (void)processInputQueue
1543 // NOTE: One of the input events may cause this method to be called
1544 // recursively, so copy the input queue to a local variable and clear it
1545 // before starting to process input events (otherwise we could get stuck in
1546 // an endless loop).
1547 NSArray *q = [inputQueue copy];
1548 unsigned i, count = [q count];
1550 [inputQueue removeAllObjects];
1552 for (i = 0; i < count-1; i += 2) {
1553 int msgid = [[q objectAtIndex:i] intValue];
1554 id data = [q objectAtIndex:i+1];
1555 if ([data isEqual:[NSNull null]])
1558 //NSLog(@"(%d) %s:%s", i, _cmd, MessageStrings[msgid]);
1559 [self handleInputEvent:msgid data:data];
1563 //NSLog(@"Clear input event queue");
1566 - (void)handleInputEvent:(int)msgid data:(NSData *)data
1568 // NOTE: Be careful with what you do in this method. Ideally, a message
1569 // should be handled by adding something to the input buffer and returning
1570 // immediately. If you call a Vim function then it should not enter a loop
1571 // waiting for key presses or in any other way block the process. The
1572 // reason for this being that only one message can be processed at a time,
1573 // so if another message is received while processing, then the new message
1574 // is dropped. See also the comment in processInput:data:.
1576 //NSLog(@"%s%s", _cmd, MessageStrings[msgid]);
1578 if (SelectTabMsgID == msgid) {
1580 const void *bytes = [data bytes];
1581 int idx = *((int*)bytes) + 1;
1582 //NSLog(@"Selecting tab %d", idx);
1583 send_tabline_event(idx);
1584 } else if (CloseTabMsgID == msgid) {
1586 const void *bytes = [data bytes];
1587 int idx = *((int*)bytes) + 1;
1588 //NSLog(@"Closing tab %d", idx);
1589 send_tabline_menu_event(idx, TABLINE_MENU_CLOSE);
1590 } else if (AddNewTabMsgID == msgid) {
1591 //NSLog(@"Adding new tab");
1592 send_tabline_menu_event(0, TABLINE_MENU_NEW);
1593 } else if (DraggedTabMsgID == msgid) {
1595 const void *bytes = [data bytes];
1596 // NOTE! The destination index is 0 based, so do not add 1 to make it 1
1598 int idx = *((int*)bytes);
1601 } else if (SetTextDimensionsMsgID == msgid || LiveResizeMsgID == msgid) {
1603 const void *bytes = [data bytes];
1604 int rows = *((int*)bytes); bytes += sizeof(int);
1605 int cols = *((int*)bytes); bytes += sizeof(int);
1607 // NOTE! Vim doesn't call gui_mch_set_shellsize() after
1608 // gui_resize_shell(), so we have to manually set the rows and columns
1609 // here. (MacVim doesn't change the rows and columns to avoid
1610 // inconsistent states between Vim and MacVim.)
1611 [self queueMessage:msgid data:data];
1613 //NSLog(@"[VimTask] Resizing shell to %dx%d.", cols, rows);
1614 gui_resize_shell(cols, rows);
1615 } else if (ExecuteMenuMsgID == msgid) {
1617 const void *bytes = [data bytes];
1618 int tag = *((int*)bytes); bytes += sizeof(int);
1620 vimmenu_T *menu = (vimmenu_T*)tag;
1621 // TODO! Make sure 'menu' is a valid menu pointer!
1625 } else if (ToggleToolbarMsgID == msgid) {
1626 [self handleToggleToolbar];
1627 } else if (ScrollbarEventMsgID == msgid) {
1628 [self handleScrollbarEvent:data];
1629 } else if (SetFontMsgID == msgid) {
1630 [self handleSetFont:data];
1631 } else if (VimShouldCloseMsgID == msgid) {
1633 } else if (DropFilesMsgID == msgid) {
1634 [self handleDropFiles:data];
1635 } else if (DropStringMsgID == msgid) {
1636 [self handleDropString:data];
1637 } else if (GotFocusMsgID == msgid) {
1639 [self focusChange:YES];
1640 } else if (LostFocusMsgID == msgid) {
1642 [self focusChange:NO];
1643 } else if (SetMouseShapeMsgID == msgid) {
1644 const void *bytes = [data bytes];
1645 int shape = *((int*)bytes); bytes += sizeof(int);
1646 update_mouseshape(shape);
1647 } else if (ODBEditMsgID == msgid) {
1648 [self handleOdbEdit:data];
1649 } else if (XcodeModMsgID == msgid) {
1650 [self handleXcodeMod:data];
1651 } else if (CloseMsgID == msgid) {
1652 // If in Ex mode, then simply exit Ex mode (^U:vi<CR>). Otherwise
1653 // try to close one (Vim-)window by going to Normal mode first
1654 // (CTRL-\_CTRL-N) and then sending ":q<CR>", but only if the
1655 // command-line window is not open. If the command-line window is open
1656 // then we just go back to normal mode (since CTRL-\_CTRL-N closes the
1657 // command-line window).
1658 if (exmode_active) {
1660 add_to_input_buf((char_u*)"\x15:vi\n", 5);
1662 // Go to normal mode
1663 add_to_input_buf((char_u*)"\x1c\xe", 2);
1664 if (0 == cmdwin_type) {
1665 // Command-line window was not open, so :q
1666 add_to_input_buf((char_u*)":q\n", 3);
1670 NSLog(@"WARNING: Unknown message received (msgid=%d)", msgid);
1674 + (NSDictionary *)specialKeys
1676 static NSDictionary *specialKeys = nil;
1679 NSBundle *mainBundle = [NSBundle mainBundle];
1680 NSString *path = [mainBundle pathForResource:@"SpecialKeys"
1682 specialKeys = [[NSDictionary alloc] initWithContentsOfFile:path];
1688 - (void)handleInsertText:(NSData *)data
1692 NSString *key = [[NSString alloc] initWithData:data
1693 encoding:NSUTF8StringEncoding];
1694 char_u *str = (char_u*)[key UTF8String];
1695 int i, len = [key lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
1698 char_u *conv_str = NULL;
1699 if (input_conv.vc_type != CONV_NONE) {
1700 conv_str = string_convert(&input_conv, str, &len);
1706 if (len == 1 && ((str[0] == Ctrl_C && ctrl_c_interrupts)
1707 || (str[0] == intr_char && intr_char != Ctrl_C))) {
1712 for (i = 0; i < len; ++i) {
1713 add_to_input_buf(str+i, 1);
1714 if (CSI == str[i]) {
1715 // NOTE: If the converted string contains the byte CSI, then it
1716 // must be followed by the bytes KS_EXTRA, KE_CSI or things
1718 static char_u extra[2] = { KS_EXTRA, KE_CSI };
1719 add_to_input_buf(extra, 2);
1730 - (void)handleKeyDown:(NSString *)key modifiers:(int)mods
1734 char_u *chars = (char_u*)[key UTF8String];
1736 char_u *conv_str = NULL;
1738 int length = [key lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
1740 // Special keys (arrow keys, function keys, etc.) are stored in a plist so
1741 // that new keys can easily be added.
1742 NSString *specialString = [[MMBackend specialKeys]
1744 if (specialString && [specialString length] > 1) {
1745 //NSLog(@"special key: %@", specialString);
1746 int ikey = TO_SPECIAL([specialString characterAtIndex:0],
1747 [specialString characterAtIndex:1]);
1749 ikey = simplify_key(ikey, &mods);
1754 special[1] = K_SECOND(ikey);
1755 special[2] = K_THIRD(ikey);
1759 } else if (1 == length && TAB == chars[0]) {
1760 // Tab is a trouble child:
1761 // - <Tab> is added to the input buffer as is
1762 // - <S-Tab> is translated to, {CSI,'k','B'} (i.e. 'Back-tab')
1763 // - <M-Tab> should be 0x80|TAB but this is not valid utf-8 so it needs
1764 // to be converted to utf-8
1765 // - <S-M-Tab> is translated to <S-Tab> with ALT modifier
1766 // - <C-Tab> is reserved by Mac OS X
1767 // - <D-Tab> is reserved by Mac OS X
1772 if (mods & MOD_MASK_SHIFT) {
1773 mods &= ~MOD_MASK_SHIFT;
1775 special[1] = K_SECOND(K_S_TAB);
1776 special[2] = K_THIRD(K_S_TAB);
1778 } else if (mods & MOD_MASK_ALT) {
1779 int mtab = 0x80 | TAB;
1783 special[0] = (mtab >> 6) + 0xc0;
1784 special[1] = mtab & 0xbf;
1792 mods &= ~MOD_MASK_ALT;
1794 } else if (length > 0) {
1795 unichar c = [key characterAtIndex:0];
1797 //NSLog(@"non-special: %@ (hex=%x, mods=%d)", key,
1798 // [key characterAtIndex:0], mods);
1800 if (length == 1 && ((c == Ctrl_C && ctrl_c_interrupts)
1801 || (c == intr_char && intr_char != Ctrl_C))) {
1806 // HACK! In most circumstances the Ctrl and Shift modifiers should be
1807 // cleared since they are already added to the key by the AppKit.
1808 // Unfortunately, the only way to deal with when to clear the modifiers
1809 // or not seems to be to have hard-wired rules like this.
1810 if ( !((' ' == c) || (0xa0 == c) || (mods & MOD_MASK_CMD)
1811 || 0x9 == c || 0xd == c || ESC == c) ) {
1812 mods &= ~MOD_MASK_SHIFT;
1813 mods &= ~MOD_MASK_CTRL;
1814 //NSLog(@"clear shift ctrl");
1817 // HACK! All Option+key presses go via 'insert text' messages, except
1818 // for <M-Space>. If the Alt flag is not cleared for <M-Space> it does
1819 // not work to map to it.
1820 if (0xa0 == c && !(mods & MOD_MASK_CMD)) {
1821 //NSLog(@"clear alt");
1822 mods &= ~MOD_MASK_ALT;
1826 if (input_conv.vc_type != CONV_NONE) {
1827 conv_str = string_convert(&input_conv, chars, &length);
1834 if (chars && length > 0) {
1836 //NSLog(@"adding mods: %d", mods);
1838 modChars[1] = KS_MODIFIER;
1840 add_to_input_buf(modChars, 3);
1843 //NSLog(@"add to input buf: 0x%x", chars[0]);
1844 // TODO: Check for CSI bytes?
1845 add_to_input_buf(chars, length);
1854 - (void)queueMessage:(int)msgid data:(NSData *)data
1856 [outputQueue addObject:[NSData dataWithBytes:&msgid length:sizeof(int)]];
1858 [outputQueue addObject:data];
1860 [outputQueue addObject:[NSData data]];
1863 - (void)connectionDidDie:(NSNotification *)notification
1865 // If the main connection to MacVim is lost this means that MacVim was
1866 // either quit (by the user chosing Quit on the MacVim menu), or it has
1867 // crashed. In the former case the flag 'isTerminating' is set and we then
1868 // quit cleanly; in the latter case we make sure the swap files are left
1871 //NSLog(@"%s isTerminating=%d", _cmd, isTerminating);
1875 getout_preserve_modified(1);
1878 - (void)blinkTimerFired:(NSTimer *)timer
1880 NSTimeInterval timeInterval = 0;
1882 [blinkTimer release];
1885 if (MMBlinkStateOn == blinkState) {
1886 gui_undraw_cursor();
1887 blinkState = MMBlinkStateOff;
1888 timeInterval = blinkOffInterval;
1889 } else if (MMBlinkStateOff == blinkState) {
1890 gui_update_cursor(TRUE, FALSE);
1891 blinkState = MMBlinkStateOn;
1892 timeInterval = blinkOnInterval;
1895 if (timeInterval > 0) {
1897 [[NSTimer scheduledTimerWithTimeInterval:timeInterval target:self
1898 selector:@selector(blinkTimerFired:)
1899 userInfo:nil repeats:NO] retain];
1900 [self flushQueue:YES];
1904 - (void)focusChange:(BOOL)on
1906 gui_focus_change(on);
1909 - (void)handleToggleToolbar
1911 // If 'go' contains 'T', then remove it, else add it.
1913 char_u go[sizeof(GO_ALL)+2];
1918 p = vim_strchr(go, GO_TOOLBAR);
1922 char_u *end = go + len;
1928 go[len] = GO_TOOLBAR;
1932 set_option_value((char_u*)"guioptions", 0, go, 0);
1934 // Force screen redraw (does it have to be this complicated?).
1935 redraw_all_later(CLEAR);
1936 update_screen(NOT_VALID);
1939 gui_update_cursor(FALSE, FALSE);
1943 - (void)handleScrollbarEvent:(NSData *)data
1947 const void *bytes = [data bytes];
1948 long ident = *((long*)bytes); bytes += sizeof(long);
1949 int hitPart = *((int*)bytes); bytes += sizeof(int);
1950 float fval = *((float*)bytes); bytes += sizeof(float);
1951 scrollbar_T *sb = gui_find_scrollbar(ident);
1954 scrollbar_T *sb_info = sb->wp ? &sb->wp->w_scrollbars[0] : sb;
1955 long value = sb_info->value;
1956 long size = sb_info->size;
1957 long max = sb_info->max;
1958 BOOL isStillDragging = NO;
1959 BOOL updateKnob = YES;
1962 case NSScrollerDecrementPage:
1963 value -= (size > 2 ? size - 2 : 1);
1965 case NSScrollerIncrementPage:
1966 value += (size > 2 ? size - 2 : 1);
1968 case NSScrollerDecrementLine:
1971 case NSScrollerIncrementLine:
1974 case NSScrollerKnob:
1975 isStillDragging = YES;
1977 case NSScrollerKnobSlot:
1978 value = (long)(fval * (max - size + 1));
1985 //NSLog(@"value %d -> %d", sb_info->value, value);
1986 gui_drag_scrollbar(sb, value, isStillDragging);
1989 // Dragging the knob or option+clicking automatically updates
1990 // the knob position (on the actual NSScroller), so we only
1991 // need to set the knob position in the other cases.
1993 // Update both the left&right vertical scrollbars.
1994 long identLeft = sb->wp->w_scrollbars[SBAR_LEFT].ident;
1995 long identRight = sb->wp->w_scrollbars[SBAR_RIGHT].ident;
1996 [self setScrollbarThumbValue:value size:size max:max
1997 identifier:identLeft];
1998 [self setScrollbarThumbValue:value size:size max:max
1999 identifier:identRight];
2001 // Update the horizontal scrollbar.
2002 [self setScrollbarThumbValue:value size:size max:max
2009 - (void)handleSetFont:(NSData *)data
2013 const void *bytes = [data bytes];
2014 float pointSize = *((float*)bytes); bytes += sizeof(float);
2015 //unsigned len = *((unsigned*)bytes); bytes += sizeof(unsigned);
2016 bytes += sizeof(unsigned); // len not used
2018 NSMutableString *name = [NSMutableString stringWithUTF8String:bytes];
2019 [name appendString:[NSString stringWithFormat:@":h%.2f", pointSize]];
2020 char_u *s = (char_u*)[name UTF8String];
2023 s = CONVERT_FROM_UTF8(s);
2026 set_option_value((char_u*)"guifont", 0, s, 0);
2029 CONVERT_FROM_UTF8_FREE(s);
2032 // Force screen redraw (does it have to be this complicated?).
2033 redraw_all_later(CLEAR);
2034 update_screen(NOT_VALID);
2037 gui_update_cursor(FALSE, FALSE);
2041 - (void)handleDropFiles:(NSData *)data
2043 // TODO: Get rid of this method; instead use Vim script directly. At the
2044 // moment I know how to do this to open files in tabs, but I'm not sure how
2045 // to add the filenames to the command line when in command line mode.
2050 const void *bytes = [data bytes];
2051 const void *end = [data bytes] + [data length];
2052 BOOL forceOpen = *((BOOL*)bytes); bytes += sizeof(BOOL);
2053 int n = *((int*)bytes); bytes += sizeof(int);
2055 if (!forceOpen && (State & CMDLINE)) {
2056 // HACK! If Vim is in command line mode then the files names
2057 // should be added to the command line, instead of opening the
2058 // files in tabs (unless forceOpen is set). This is taken care of by
2059 // gui_handle_drop().
2060 char_u **fnames = (char_u **)alloc(n * sizeof(char_u *));
2063 while (bytes < end && i < n) {
2064 int len = *((int*)bytes); bytes += sizeof(int);
2065 char_u *s = (char_u*)bytes;
2067 s = CONVERT_FROM_UTF8(s);
2069 fnames[i++] = vim_strsave(s);
2071 CONVERT_FROM_UTF8_FREE(s);
2076 // NOTE! This function will free 'fnames'.
2077 // HACK! It is assumed that the 'x' and 'y' arguments are
2078 // unused when in command line mode.
2079 gui_handle_drop(0, 0, 0, fnames, i < n ? i : n);
2082 // HACK! I'm not sure how to get Vim to open a list of files in
2083 // tabs, so instead I create a ':tab drop' command with all the
2084 // files to open and execute it.
2085 NSMutableString *cmd = [NSMutableString stringWithString:@":tab drop"];
2088 for (i = 0; i < n && bytes < end; ++i) {
2089 int len = *((int*)bytes); bytes += sizeof(int);
2090 NSString *file = [NSString stringWithUTF8String:bytes];
2091 file = [file stringByEscapingSpecialFilenameCharacters];
2094 [cmd appendString:@" "];
2095 [cmd appendString:file];
2098 // By going to the last tabpage we ensure that the new tabs will
2099 // appear last (if this call is left out, the taborder becomes
2103 char_u *s = (char_u*)[cmd UTF8String];
2105 s = CONVERT_FROM_UTF8(s);
2109 CONVERT_FROM_UTF8_FREE(s);
2112 // Force screen redraw (does it have to be this complicated?).
2113 // (This code was taken from the end of gui_handle_drop().)
2114 update_screen(NOT_VALID);
2117 gui_update_cursor(FALSE, FALSE);
2124 - (void)handleDropString:(NSData *)data
2129 char_u dropkey[3] = { CSI, KS_EXTRA, (char_u)KE_DROP };
2130 const void *bytes = [data bytes];
2131 int len = *((int*)bytes); bytes += sizeof(int);
2132 NSMutableString *string = [NSMutableString stringWithUTF8String:bytes];
2134 // Replace unrecognized end-of-line sequences with \x0a (line feed).
2135 NSRange range = { 0, [string length] };
2136 unsigned n = [string replaceOccurrencesOfString:@"\x0d\x0a"
2137 withString:@"\x0a" options:0
2140 n = [string replaceOccurrencesOfString:@"\x0d" withString:@"\x0a"
2141 options:0 range:range];
2144 len = [string lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
2145 char_u *s = (char_u*)[string UTF8String];
2147 if (input_conv.vc_type != CONV_NONE)
2148 s = string_convert(&input_conv, s, &len);
2150 dnd_yank_drag_data(s, len);
2152 if (input_conv.vc_type != CONV_NONE)
2155 add_to_input_buf(dropkey, sizeof(dropkey));
2159 - (void)handleOdbEdit:(NSData *)data
2161 #ifdef FEAT_ODB_EDITOR
2162 const void *bytes = [data bytes];
2164 OSType serverID = *((OSType*)bytes); bytes += sizeof(OSType);
2166 char_u *path = NULL;
2167 int pathLen = *((int*)bytes); bytes += sizeof(int);
2169 path = (char_u*)bytes;
2172 path = CONVERT_FROM_UTF8(path);
2176 NSAppleEventDescriptor *token = nil;
2177 DescType tokenType = *((DescType*)bytes); bytes += sizeof(DescType);
2178 int descLen = *((int*)bytes); bytes += sizeof(int);
2180 token = [NSAppleEventDescriptor descriptorWithDescriptorType:tokenType
2186 unsigned i, numFiles = *((unsigned*)bytes); bytes += sizeof(unsigned);
2187 for (i = 0; i < numFiles; ++i) {
2188 int len = *((int*)bytes); bytes += sizeof(int);
2189 char_u *filename = (char_u*)bytes;
2191 filename = CONVERT_FROM_UTF8(filename);
2193 buf_T *buf = buflist_findname(filename);
2195 if (buf->b_odb_token) {
2196 [(NSAppleEventDescriptor*)(buf->b_odb_token) release];
2197 buf->b_odb_token = NULL;
2200 if (buf->b_odb_fname) {
2201 vim_free(buf->b_odb_fname);
2202 buf->b_odb_fname = NULL;
2205 buf->b_odb_server_id = serverID;
2208 buf->b_odb_token = [token retain];
2210 buf->b_odb_fname = vim_strsave(path);
2212 NSLog(@"WARNING: Could not find buffer '%s' for ODB editing.",
2217 CONVERT_FROM_UTF8_FREE(filename);
2222 CONVERT_FROM_UTF8_FREE(path);
2224 #endif // FEAT_ODB_EDITOR
2227 - (void)handleXcodeMod:(NSData *)data
2230 const void *bytes = [data bytes];
2231 DescType type = *((DescType*)bytes); bytes += sizeof(DescType);
2232 unsigned len = *((unsigned*)bytes); bytes += sizeof(unsigned);
2236 NSAppleEventDescriptor *replyEvent = [NSAppleEventDescriptor
2237 descriptorWithDescriptorType:type
2243 - (BOOL)checkForModifiedBuffers
2246 for (buf = firstbuf; buf != NULL; buf = buf->b_next) {
2247 if (bufIsChanged(buf)) {
2255 - (void)addInput:(NSString *)input
2257 char_u *s = (char_u*)[input UTF8String];
2260 s = CONVERT_FROM_UTF8(s);
2263 server_to_input_buf(s);
2266 CONVERT_FROM_UTF8_FREE(s);
2270 @end // MMBackend (Private)
2275 @implementation MMBackend (ClientServer)
2277 - (NSString *)connectionNameFromServerName:(NSString *)name
2279 NSString *bundleIdentifier = [[NSBundle mainBundle] bundleIdentifier];
2281 return [[NSString stringWithFormat:@"%@.%@", bundleIdentifier, name]
2285 - (NSConnection *)connectionForServerName:(NSString *)name
2287 // TODO: Try 'name%d' if 'name' fails.
2288 NSString *connName = [self connectionNameFromServerName:name];
2289 NSConnection *svrConn = [connectionNameDict objectForKey:connName];
2292 svrConn = [NSConnection connectionWithRegisteredName:connName
2294 // Try alternate server...
2295 if (!svrConn && alternateServerName) {
2296 //NSLog(@" trying to connect to alternate server: %@",
2297 // alternateServerName);
2298 connName = [self connectionNameFromServerName:alternateServerName];
2299 svrConn = [NSConnection connectionWithRegisteredName:connName
2303 // Try looking for alternate servers...
2305 //NSLog(@" looking for alternate servers...");
2306 NSString *alt = [self alternateServerNameForName:name];
2307 if (alt != alternateServerName) {
2308 //NSLog(@" found alternate server: %@", string);
2309 [alternateServerName release];
2310 alternateServerName = [alt copy];
2314 // Try alternate server again...
2315 if (!svrConn && alternateServerName) {
2316 //NSLog(@" trying to connect to alternate server: %@",
2317 // alternateServerName);
2318 connName = [self connectionNameFromServerName:alternateServerName];
2319 svrConn = [NSConnection connectionWithRegisteredName:connName
2324 [connectionNameDict setObject:svrConn forKey:connName];
2326 //NSLog(@"Adding %@ as connection observer for %@", self, svrConn);
2327 [[NSNotificationCenter defaultCenter] addObserver:self
2328 selector:@selector(serverConnectionDidDie:)
2329 name:NSConnectionDidDieNotification object:svrConn];
2336 - (NSConnection *)connectionForServerPort:(int)port
2339 NSEnumerator *e = [connectionNameDict objectEnumerator];
2341 while ((conn = [e nextObject])) {
2342 // HACK! Assume connection uses mach ports.
2343 if (port == [(NSMachPort*)[conn sendPort] machPort])
2350 - (void)serverConnectionDidDie:(NSNotification *)notification
2352 //NSLog(@"%s%@", _cmd, notification);
2354 NSConnection *svrConn = [notification object];
2356 //NSLog(@"Removing %@ as connection observer from %@", self, svrConn);
2357 [[NSNotificationCenter defaultCenter]
2359 name:NSConnectionDidDieNotification
2362 [connectionNameDict removeObjectsForKeys:
2363 [connectionNameDict allKeysForObject:svrConn]];
2365 // HACK! Assume connection uses mach ports.
2366 int port = [(NSMachPort*)[svrConn sendPort] machPort];
2367 NSNumber *key = [NSNumber numberWithInt:port];
2369 [clientProxyDict removeObjectForKey:key];
2370 [serverReplyDict removeObjectForKey:key];
2373 - (void)addClient:(NSDistantObject *)client
2375 NSConnection *conn = [client connectionForProxy];
2376 // HACK! Assume connection uses mach ports.
2377 int port = [(NSMachPort*)[conn sendPort] machPort];
2378 NSNumber *key = [NSNumber numberWithInt:port];
2380 if (![clientProxyDict objectForKey:key]) {
2381 [client setProtocolForProxy:@protocol(MMVimClientProtocol)];
2382 [clientProxyDict setObject:client forKey:key];
2385 // NOTE: 'clientWindow' is a global variable which is used by <client>
2386 clientWindow = port;
2389 - (NSString *)alternateServerNameForName:(NSString *)name
2391 if (!(name && [name length] > 0))
2394 // Only look for alternates if 'name' doesn't end in a digit.
2395 unichar lastChar = [name characterAtIndex:[name length]-1];
2396 if (lastChar >= '0' && lastChar <= '9')
2399 // Look for alternates among all current servers.
2400 NSArray *list = [self serverList];
2401 if (!(list && [list count] > 0))
2404 // Filter out servers starting with 'name' and ending with a number. The
2405 // (?i) pattern ensures that the match is case insensitive.
2406 NSString *pat = [NSString stringWithFormat:@"(?i)%@[0-9]+\\z", name];
2407 NSPredicate *pred = [NSPredicate predicateWithFormat:
2408 @"SELF MATCHES %@", pat];
2409 list = [list filteredArrayUsingPredicate:pred];
2410 if ([list count] > 0) {
2411 list = [list sortedArrayUsingSelector:@selector(serverNameCompare:)];
2412 return [list objectAtIndex:0];
2418 @end // MMBackend (ClientServer)
2423 @implementation NSString (MMServerNameCompare)
2424 - (NSComparisonResult)serverNameCompare:(NSString *)string
2426 return [self compare:string
2427 options:NSCaseInsensitiveSearch|NSNumericSearch];
2434 static int eventModifierFlagsToVimModMask(int modifierFlags)
2438 if (modifierFlags & NSShiftKeyMask)
2439 modMask |= MOD_MASK_SHIFT;
2440 if (modifierFlags & NSControlKeyMask)
2441 modMask |= MOD_MASK_CTRL;
2442 if (modifierFlags & NSAlternateKeyMask)
2443 modMask |= MOD_MASK_ALT;
2444 if (modifierFlags & NSCommandKeyMask)
2445 modMask |= MOD_MASK_CMD;
2450 static int vimModMaskToEventModifierFlags(int mods)
2454 if (mods & MOD_MASK_SHIFT)
2455 flags |= NSShiftKeyMask;
2456 if (mods & MOD_MASK_CTRL)
2457 flags |= NSControlKeyMask;
2458 if (mods & MOD_MASK_ALT)
2459 flags |= NSAlternateKeyMask;
2460 if (mods & MOD_MASK_CMD)
2461 flags |= NSCommandKeyMask;
2466 static int eventModifierFlagsToVimMouseModMask(int modifierFlags)
2470 if (modifierFlags & NSShiftKeyMask)
2471 modMask |= MOUSE_SHIFT;
2472 if (modifierFlags & NSControlKeyMask)
2473 modMask |= MOUSE_CTRL;
2474 if (modifierFlags & NSAlternateKeyMask)
2475 modMask |= MOUSE_ALT;
2480 static int eventButtonNumberToVimMouseButton(int buttonNumber)
2482 static int mouseButton[] = { MOUSE_LEFT, MOUSE_RIGHT, MOUSE_MIDDLE };
2484 return (buttonNumber >= 0 && buttonNumber < 3)
2485 ? mouseButton[buttonNumber] : -1;
2488 static int specialKeyToNSKey(int key)
2490 if (!IS_SPECIAL(key))
2497 { K_UP, NSUpArrowFunctionKey },
2498 { K_DOWN, NSDownArrowFunctionKey },
2499 { K_LEFT, NSLeftArrowFunctionKey },
2500 { K_RIGHT, NSRightArrowFunctionKey },
2501 { K_F1, NSF1FunctionKey },
2502 { K_F2, NSF2FunctionKey },
2503 { K_F3, NSF3FunctionKey },
2504 { K_F4, NSF4FunctionKey },
2505 { K_F5, NSF5FunctionKey },
2506 { K_F6, NSF6FunctionKey },
2507 { K_F7, NSF7FunctionKey },
2508 { K_F8, NSF8FunctionKey },
2509 { K_F9, NSF9FunctionKey },
2510 { K_F10, NSF10FunctionKey },
2511 { K_F11, NSF11FunctionKey },
2512 { K_F12, NSF12FunctionKey },
2513 { K_F13, NSF13FunctionKey },
2514 { K_F14, NSF14FunctionKey },
2515 { K_F15, NSF15FunctionKey },
2516 { K_F16, NSF16FunctionKey },
2517 { K_F17, NSF17FunctionKey },
2518 { K_F18, NSF18FunctionKey },
2519 { K_F19, NSF19FunctionKey },
2520 { K_F20, NSF20FunctionKey },
2521 { K_F21, NSF21FunctionKey },
2522 { K_F22, NSF22FunctionKey },
2523 { K_F23, NSF23FunctionKey },
2524 { K_F24, NSF24FunctionKey },
2525 { K_F25, NSF25FunctionKey },
2526 { K_F26, NSF26FunctionKey },
2527 { K_F27, NSF27FunctionKey },
2528 { K_F28, NSF28FunctionKey },
2529 { K_F29, NSF29FunctionKey },
2530 { K_F30, NSF30FunctionKey },
2531 { K_F31, NSF31FunctionKey },
2532 { K_F32, NSF32FunctionKey },
2533 { K_F33, NSF33FunctionKey },
2534 { K_F34, NSF34FunctionKey },
2535 { K_F35, NSF35FunctionKey },
2536 { K_DEL, NSBackspaceCharacter },
2537 { K_BS, NSDeleteCharacter },
2538 { K_HOME, NSHomeFunctionKey },
2539 { K_END, NSEndFunctionKey },
2540 { K_PAGEUP, NSPageUpFunctionKey },
2541 { K_PAGEDOWN, NSPageDownFunctionKey }
2545 for (i = 0; i < sizeof(sp2ns)/sizeof(sp2ns[0]); ++i) {
2546 if (sp2ns[i].special == key)
2547 return sp2ns[i].nskey;