- Now possible to have 'openFiles' events to open the files in tabs in the
[MacVim/jjgod.git] / MMBackend.m
blob99b9bfa49c588ce0472a9b35a40007657dfc94f6
1 /* vi:set ts=8 sts=4 sw=4 ft=objc:
2  *
3  * VIM - Vi IMproved            by Bram Moolenaar
4  *                              MacVim GUI port by Bjorn Winckler
5  *
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.
9  */
11 #import "MMBackend.h"
12 #import "vim.h"
16 // This constant controls how often the command queue may be flushed.  If it is
17 // too small the app might feel unresponsive; if it is too large there might be
18 // long periods without the screen updating (e.g. when sourcing a large session
19 // file).  (The unit is seconds.)
20 static float MMFlushTimeoutInterval = 0.1f;
23 // TODO: Move to separate file.
24 static int eventModifierFlagsToVimModMask(int modifierFlags);
25 static int vimModMaskToEventModifierFlags(int mods);
26 static int eventModifierFlagsToVimMouseModMask(int modifierFlags);
27 static int eventButtonNumberToVimMouseButton(int buttonNumber);
28 static int specialKeyToNSKey(int key);
31 @interface MMBackend (Private)
32 - (void)handleMessage:(int)msgid data:(NSData *)data;
33 + (NSDictionary *)specialKeys;
34 - (void)handleKeyDown:(NSString *)key modifiers:(int)mods;
35 - (void)queueMessage:(int)msgid data:(NSData *)data;
36 - (void)connectionDidDie:(NSNotification *)notification;
37 @end
41 @implementation MMBackend
43 + (MMBackend *)sharedInstance
45     static MMBackend *singleton = nil;
46     return singleton ? singleton : (singleton = [MMBackend new]);
49 - (id)init
51     if ((self = [super init])) {
52         queue = [[NSMutableArray alloc] init];
53         drawData = [[NSMutableData alloc] initWithCapacity:1024];
54         NSString *path = [[NSBundle mainBundle] pathForResource:@"Colors"
55                                                          ofType:@"plist"];
56         colorDict = [[NSDictionary dictionaryWithContentsOfFile:path] retain];
57     }
59     return self;
62 - (void)dealloc
64     //NSLog(@"%@ %s", [self className], _cmd);
66     [[NSNotificationCenter defaultCenter] removeObserver:self];
68     [queue release];
69     [drawData release];
70     [frontendProxy release];
71     [connection release];
72     [colorDict release];
74     [super dealloc];
77 - (void)setBackgroundColor:(int)color
79     backgroundColor = color;
82 - (void)setForegroundColor:(int)color
84     foregroundColor = color;
87 - (void)setSpecialColor:(int)color
89     specialColor = color;
92 - (void)setDefaultColorsBackground:(int)bg foreground:(int)fg
94     defaultBackgroundColor = bg;
95     defaultForegroundColor = fg;
97     NSMutableData *data = [NSMutableData data];
99     [data appendBytes:&bg length:sizeof(int)];
100     [data appendBytes:&fg length:sizeof(int)];
102     [self queueMessage:SetDefaultColorsMsgID data:data];
105 - (BOOL)checkin
107     NSBundle *mainBundle = [NSBundle mainBundle];
109     // NOTE!  If the name of the connection changes here it must also be
110     // updated in MMAppController.m.
111     NSString *name = [NSString stringWithFormat:@"%@-connection",
112              [mainBundle bundleIdentifier]];
113     connection = [NSConnection connectionWithRegisteredName:name host:nil];
114     if (!connection) {
115 #if 0
116         NSString *path = [mainBundle bundlePath];
117         if (![[NSWorkspace sharedWorkspace] launchApplication:path]) {
118             NSLog(@"WARNING: Failed to launch GUI with path %@", path);
119             return NO;
120         }
121 #else
122         // HACK!  It would be preferable to launch the GUI using NSWorkspace,
123         // however I have not managed to figure out how to pass arguments using
124         // NSWorkspace.
125         //
126         // NOTE!  Using NSTask to launch the GUI has the negative side-effect
127         // that the GUI won't be activated (or raised) so there is a hack in
128         // MMWindowController which always raises the app when a new window is
129         // opened.
130         NSMutableArray *args = [NSMutableArray arrayWithObjects:
131             [NSString stringWithFormat:@"-%@", MMNoWindowKey], @"yes", nil];
132         NSString *exeName = [[mainBundle infoDictionary]
133                 objectForKey:@"CFBundleExecutable"];
134         NSString *path = [mainBundle pathForAuxiliaryExecutable:exeName];
135         if (!path) {
136             NSLog(@"ERROR: Could not find MacVim executable in bundle");
137             return NO;
138         }
140         [NSTask launchedTaskWithLaunchPath:path arguments:args];
141 #endif
143         // HACK!  The NSWorkspaceDidLaunchApplicationNotification does not work
144         // for tasks like this, so poll the mach bootstrap server until it
145         // returns a valid connection.  Also set a time-out date so that we
146         // don't get stuck doing this forever.
147         NSDate *timeOutDate = [NSDate dateWithTimeIntervalSinceNow:15];
148         while (!connection &&
149                 NSOrderedDescending == [timeOutDate compare:[NSDate date]])
150         {
151             [[NSRunLoop currentRunLoop]
152                     runMode:NSDefaultRunLoopMode
153                  beforeDate:[NSDate dateWithTimeIntervalSinceNow:1]];
155             connection = [NSConnection connectionWithRegisteredName:name
156                                                                host:nil];
157         }
159         if (!connection) {
160             NSLog(@"WARNING: Timed-out waiting for GUI to launch.");
161             return NO;
162         }
163     }
165     id proxy = [connection rootProxy];
166     [proxy setProtocolForProxy:@protocol(MMAppProtocol)];
168     [[NSNotificationCenter defaultCenter] addObserver:self
169             selector:@selector(connectionDidDie:)
170                 name:NSConnectionDidDieNotification object:connection];
172     int pid = [[NSProcessInfo processInfo] processIdentifier];
174     @try {
175         frontendProxy = [(NSDistantObject*)[proxy connectBackend:self
176                                                              pid:pid] retain];
177     }
178     @catch (NSException *e) {
179         NSLog(@"Exception caught when trying to connect backend: \"%@\"", e);
180     }
182     if (frontendProxy) {
183         [frontendProxy setProtocolForProxy:@protocol(MMAppProtocol)];
184     }
186     return connection && frontendProxy;
189 - (BOOL)openVimWindow
191     [self queueMessage:OpenVimWindowMsgID data:nil];
192     return YES;
195 - (void)clearAll
197     int type = ClearAllDrawType;
199     // Any draw commands in queue are effectively obsolete since this clearAll
200     // will negate any effect they have, therefore we may as well clear the
201     // draw queue.
202     [drawData setLength:0];
204     [drawData appendBytes:&type length:sizeof(int)];
206     [drawData appendBytes:&defaultBackgroundColor length:sizeof(int)];
209 - (void)clearBlockFromRow:(int)row1 column:(int)col1
210                     toRow:(int)row2 column:(int)col2
212     int type = ClearBlockDrawType;
214     [drawData appendBytes:&type length:sizeof(int)];
216     [drawData appendBytes:&defaultBackgroundColor length:sizeof(int)];
217     [drawData appendBytes:&row1 length:sizeof(int)];
218     [drawData appendBytes:&col1 length:sizeof(int)];
219     [drawData appendBytes:&row2 length:sizeof(int)];
220     [drawData appendBytes:&col2 length:sizeof(int)];
223 - (void)deleteLinesFromRow:(int)row count:(int)count
224               scrollBottom:(int)bottom left:(int)left right:(int)right
226     int type = DeleteLinesDrawType;
228     [drawData appendBytes:&type length:sizeof(int)];
230     [drawData appendBytes:&defaultBackgroundColor length:sizeof(int)];
231     [drawData appendBytes:&row length:sizeof(int)];
232     [drawData appendBytes:&count length:sizeof(int)];
233     [drawData appendBytes:&bottom length:sizeof(int)];
234     [drawData appendBytes:&left length:sizeof(int)];
235     [drawData appendBytes:&right length:sizeof(int)];
238 - (void)replaceString:(char*)s length:(int)len row:(int)row column:(int)col
239                 flags:(int)flags
241     if (len <= 0) return;
243     int type = ReplaceStringDrawType;
245     [drawData appendBytes:&type length:sizeof(int)];
247     [drawData appendBytes:&backgroundColor length:sizeof(int)];
248     [drawData appendBytes:&foregroundColor length:sizeof(int)];
249     [drawData appendBytes:&specialColor length:sizeof(int)];
250     [drawData appendBytes:&row length:sizeof(int)];
251     [drawData appendBytes:&col length:sizeof(int)];
252     [drawData appendBytes:&flags length:sizeof(int)];
253     [drawData appendBytes:&len length:sizeof(int)];
254     [drawData appendBytes:s length:len];
257 - (void)insertLinesFromRow:(int)row count:(int)count
258               scrollBottom:(int)bottom left:(int)left right:(int)right
260     int type = InsertLinesDrawType;
262     [drawData appendBytes:&type length:sizeof(int)];
264     [drawData appendBytes:&defaultBackgroundColor length:sizeof(int)];
265     [drawData appendBytes:&row length:sizeof(int)];
266     [drawData appendBytes:&count length:sizeof(int)];
267     [drawData appendBytes:&bottom length:sizeof(int)];
268     [drawData appendBytes:&left length:sizeof(int)];
269     [drawData appendBytes:&right length:sizeof(int)];
272 - (void)flushQueue:(BOOL)force
274     // NOTE! This method gets called a lot; if we were to flush every time it
275     // was called MacVim would feel unresponsive.  So there is a time out which
276     // ensures that the queue isn't flushed too often.
277     if (!force && lastFlushDate && -[lastFlushDate timeIntervalSinceNow]
278             < MMFlushTimeoutInterval)
279         return;
281     if ([drawData length] > 0) {
282         [self queueMessage:BatchDrawMsgID data:[drawData copy]];
283         [drawData setLength:0];
284     }
286     if ([queue count] > 0) {
287         // TODO: Come up with a better way to handle the insertion point.
288         [self updateInsertionPoint];
290         @try {
291             [frontendProxy processCommandQueue:queue];
292         }
293         @catch (NSException *e) {
294             NSLog(@"Exception caught when processing command queue: \"%@\"", e);
295         }
297         [queue removeAllObjects];
299         [lastFlushDate release];
300         lastFlushDate = [[NSDate date] retain];
301     }
304 - (BOOL)waitForInput:(int)milliseconds
306     NSDate *date = milliseconds > 0 ?
307             [NSDate dateWithTimeIntervalSinceNow:.001*milliseconds] : 
308             [NSDate distantFuture];
310     [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:date];
312     // I know of no way to figure out if the run loop exited because input was
313     // found or because of a time out, so I need to manually indicate when
314     // input was received in processInput:data: and then reset it every time
315     // here.
316     BOOL yn = inputReceived;
317     inputReceived = NO;
319     return yn;
322 - (void)exit
324     // By invalidating the NSConnection the MMWindowController immediately
325     // finds out that the connection is down and as a result
326     // [MMWindowController connectionDidDie:] is invoked.
327     //NSLog(@"%@ %s", [self className], _cmd);
328     [[NSNotificationCenter defaultCenter] removeObserver:self];
329     [connection invalidate];
332 - (void)selectTab:(int)index
334     //NSLog(@"%s%d", _cmd, index);
336     index -= 1;
337     NSData *data = [NSData dataWithBytes:&index length:sizeof(int)];
338     [self queueMessage:SelectTabMsgID data:data];
341 - (void)updateTabBar
343     //NSLog(@"%s", _cmd);
345     NSMutableData *data = [NSMutableData data];
347     int idx = tabpage_index(curtab) - 1;
348     [data appendBytes:&idx length:sizeof(int)];
350     tabpage_T *tp;
351     for (tp = first_tabpage; tp != NULL; tp = tp->tp_next) {
352         // This function puts the label of the tab in the global 'NameBuff'.
353         get_tabline_label(tp, FALSE);
354         int len = strlen((char*)NameBuff);
355         if (len <= 0) continue;
357         // Count the number of windows in the tabpage.
358         //win_T *wp = tp->tp_firstwin;
359         //int wincount;
360         //for (wincount = 0; wp != NULL; wp = wp->w_next, ++wincount);
362         //[data appendBytes:&wincount length:sizeof(int)];
363         [data appendBytes:&len length:sizeof(int)];
364         [data appendBytes:NameBuff length:len];
365     }
367     [self queueMessage:UpdateTabBarMsgID data:data];
370 - (BOOL)tabBarVisible
372     return tabBarVisible;
375 - (void)showTabBar:(BOOL)enable
377     tabBarVisible = enable;
379     int msgid = enable ? ShowTabBarMsgID : HideTabBarMsgID;
380     [self queueMessage:msgid data:nil];
383 - (void)setRows:(int)rows columns:(int)cols
385     //NSLog(@"[VimTask] setRows:%d columns:%d", rows, cols);
387     int dim[] = { rows, cols };
388     NSData *data = [NSData dataWithBytes:&dim length:2*sizeof(int)];
390     [self queueMessage:SetTextDimensionsMsgID data:data];
393 - (void)setVimWindowTitle:(char *)title
395     NSMutableData *data = [NSMutableData data];
396     int len = strlen(title);
397     if (len <= 0) return;
399     [data appendBytes:&len length:sizeof(int)];
400     [data appendBytes:title length:len];
402     [self queueMessage:SetVimWindowTitleMsgID data:data];
405 - (char *)browseForFileInDirectory:(char *)dir title:(char *)title
406                             saving:(int)saving
408     //NSLog(@"browseForFileInDirectory:%s title:%s saving:%d", dir, title,
409     //        saving);
411     char_u *s = NULL;
412     NSString *ds = dir
413             ? [NSString stringWithCString:dir encoding:NSUTF8StringEncoding]
414             : nil;
415     NSString *ts = title
416             ? [NSString stringWithCString:title encoding:NSUTF8StringEncoding]
417             : nil;
418     @try {
419         [frontendProxy showSavePanelForDirectory:ds title:ts saving:saving];
421         // Wait until a reply is sent from MMVimController.
422         [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
423                                  beforeDate:[NSDate distantFuture]];
425         if (dialogReturn && [dialogReturn isKindOfClass:[NSString class]]) {
426             s = vim_strsave((char_u*)[dialogReturn UTF8String]);
427         }
429         [dialogReturn release];  dialogReturn = nil;
430     }
431     @catch (NSException *e) {
432         NSLog(@"Exception caught when showing save panel: \"%@\"", e);
433     }
435     return (char *)s;
438 - (oneway void)setDialogReturn:(in bycopy id)obj
440     // NOTE: This is called by
441     //   - [MMVimController panelDidEnd:::], and
442     //   - [MMVimController alertDidEnd:::],
443     // to indicate that a save/open panel or alert has finished.
445     if (obj != dialogReturn) {
446         [dialogReturn release];
447         dialogReturn = [obj retain];
448     }
451 - (int)presentDialogWithType:(int)type title:(char *)title message:(char *)msg
452                      buttons:(char *)btns textField:(char *)txtfield
454     int retval = 0;
455     NSString *message = nil, *text = nil, *textFieldString = nil;
456     NSArray *buttons = nil;
457     int style = NSInformationalAlertStyle;
459     if (VIM_WARNING == type) style = NSWarningAlertStyle;
460     else if (VIM_ERROR == type) style = NSCriticalAlertStyle;
462     if (btns) {
463         NSString *btnString = [NSString stringWithUTF8String:btns];
464         buttons = [btnString componentsSeparatedByString:@"\n"];
465     }
466     if (title)
467         message = [NSString stringWithUTF8String:title];
468     if (msg) {
469         text = [NSString stringWithUTF8String:msg];
470         if (!message) {
471             // HACK! If there is a '\n\n' or '\n' sequence in the message, then
472             // make the part up to there into the title.  We only do this
473             // because Vim has lots of dialogs without a title and they look
474             // ugly that way.
475             // TODO: Fix the actual dialog texts.
476             NSRange eolRange = [text rangeOfString:@"\n\n"];
477             if (NSNotFound == eolRange.location)
478                 eolRange = [text rangeOfString:@"\n"];
479             if (NSNotFound != eolRange.location) {
480                 message = [text substringToIndex:eolRange.location];
481                 text = [text substringFromIndex:NSMaxRange(eolRange)];
482             }
483         }
484     }
485     if (txtfield)
486         textFieldString = [NSString stringWithUTF8String:txtfield];
488     @try {
489         [frontendProxy presentDialogWithStyle:style message:message
490                               informativeText:text buttonTitles:buttons
491                               textFieldString:textFieldString];
493         // Wait until a reply is sent from MMVimController.
494         [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
495                                  beforeDate:[NSDate distantFuture]];
497         if (dialogReturn && [dialogReturn isKindOfClass:[NSArray class]]
498                 && [dialogReturn count]) {
499             retval = [[dialogReturn objectAtIndex:0] intValue];
500             if (txtfield && [dialogReturn count] > 1) {
501                 NSString *retString = [dialogReturn objectAtIndex:1];
502                 vim_strncpy((char_u*)txtfield, (char_u*)[retString UTF8String],
503                         IOSIZE - 1);
504             }
505         }
507         [dialogReturn release]; dialogReturn = nil;
508     }
509     @catch (NSException *e) {
510         NSLog(@"Exception caught while showing alert dialog: \"%@\"", e);
511     }
513     return retval;
516 - (void)updateInsertionPoint
518     NSMutableData *data = [NSMutableData data];
520     int state = get_shape_idx(FALSE);
521     state = (state == SHAPE_IDX_I) || (state == SHAPE_IDX_CI);
523     [data appendBytes:&defaultForegroundColor length:sizeof(int)];
524     [data appendBytes:&gui.row length:sizeof(int)];
525     [data appendBytes:&gui.col length:sizeof(int)];
526     [data appendBytes:&state length:sizeof(int)];
528     [self queueMessage:UpdateInsertionPointMsgID data:data];
531 - (void)addMenuWithTag:(int)tag parent:(int)parentTag name:(char *)name
532                atIndex:(int)index
534     //NSLog(@"addMenuWithTag:%d parent:%d name:%s atIndex:%d", tag, parentTag,
535     //        name, index);
537     int namelen = name ? strlen(name) : 0;
538     NSMutableData *data = [NSMutableData data];
540     [data appendBytes:&tag length:sizeof(int)];
541     [data appendBytes:&parentTag length:sizeof(int)];
542     [data appendBytes:&namelen length:sizeof(int)];
543     if (namelen > 0) [data appendBytes:name length:namelen];
544     [data appendBytes:&index length:sizeof(int)];
546     [self queueMessage:AddMenuMsgID data:data];
549 - (void)addMenuItemWithTag:(int)tag parent:(int)parentTag name:(char *)name
550                        tip:(char *)tip icon:(char *)icon
551              keyEquivalent:(int)key modifiers:(int)mods
552                     action:(NSString *)action atIndex:(int)index
554     //NSLog(@"addMenuItemWithTag:%d parent:%d name:%s tip:%s atIndex:%d", tag,
555     //        parentTag, name, tip, index);
557     int namelen = name ? strlen(name) : 0;
558     int tiplen = tip ? strlen(tip) : 0;
559     int iconlen = icon ? strlen(icon) : 0;
560     int eventFlags = vimModMaskToEventModifierFlags(mods);
561     int actionlen = [action lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
562     NSMutableData *data = [NSMutableData data];
564     key = specialKeyToNSKey(key);
566     [data appendBytes:&tag length:sizeof(int)];
567     [data appendBytes:&parentTag length:sizeof(int)];
568     [data appendBytes:&namelen length:sizeof(int)];
569     if (namelen > 0) [data appendBytes:name length:namelen];
570     [data appendBytes:&tiplen length:sizeof(int)];
571     if (tiplen > 0) [data appendBytes:tip length:tiplen];
572     [data appendBytes:&iconlen length:sizeof(int)];
573     if (iconlen > 0) [data appendBytes:icon length:iconlen];
574     [data appendBytes:&actionlen length:sizeof(int)];
575     if (actionlen > 0) [data appendBytes:[action UTF8String] length:actionlen];
576     [data appendBytes:&index length:sizeof(int)];
577     [data appendBytes:&key length:sizeof(int)];
578     [data appendBytes:&eventFlags length:sizeof(int)];
580     [self queueMessage:AddMenuItemMsgID data:data];
583 - (void)removeMenuItemWithTag:(int)tag
585     NSMutableData *data = [NSMutableData data];
586     [data appendBytes:&tag length:sizeof(int)];
588     [self queueMessage:RemoveMenuItemMsgID data:data];
591 - (void)enableMenuItemWithTag:(int)tag state:(int)enabled
593     NSMutableData *data = [NSMutableData data];
595     [data appendBytes:&tag length:sizeof(int)];
596     [data appendBytes:&enabled length:sizeof(int)];
598     [self queueMessage:EnableMenuItemMsgID data:data];
601 - (void)showPopupMenuWithName:(char *)name atMouseLocation:(BOOL)mouse
603     int len = strlen(name);
604     int row = -1, col = -1;
606     if (len <= 0) return;
608     if (!mouse && curwin) {
609         row = curwin->w_wrow;
610         col = curwin->w_wcol;
611     }
613     NSMutableData *data = [NSMutableData data];
615     [data appendBytes:&row length:sizeof(int)];
616     [data appendBytes:&col length:sizeof(int)];
617     [data appendBytes:&len length:sizeof(int)];
618     [data appendBytes:name length:len];
620     [self queueMessage:ShowPopupMenuMsgID data:data];
623 - (void)showToolbar:(int)enable flags:(int)flags
625     NSMutableData *data = [NSMutableData data];
627     [data appendBytes:&enable length:sizeof(int)];
628     [data appendBytes:&flags length:sizeof(int)];
630     [self queueMessage:ShowToolbarMsgID data:data];
633 - (void)createScrollbarWithIdentifier:(long)ident type:(int)type
635     NSMutableData *data = [NSMutableData data];
637     [data appendBytes:&ident length:sizeof(long)];
638     [data appendBytes:&type length:sizeof(int)];
640     [self queueMessage:CreateScrollbarMsgID data:data];
643 - (void)destroyScrollbarWithIdentifier:(long)ident
645     NSMutableData *data = [NSMutableData data];
646     [data appendBytes:&ident length:sizeof(long)];
648     [self queueMessage:DestroyScrollbarMsgID data:data];
651 - (void)showScrollbarWithIdentifier:(long)ident state:(int)visible
653     NSMutableData *data = [NSMutableData data];
655     [data appendBytes:&ident length:sizeof(long)];
656     [data appendBytes:&visible length:sizeof(int)];
658     [self queueMessage:ShowScrollbarMsgID data:data];
661 - (void)setScrollbarPosition:(int)pos length:(int)len identifier:(long)ident
663     NSMutableData *data = [NSMutableData data];
665     [data appendBytes:&ident length:sizeof(long)];
666     [data appendBytes:&pos length:sizeof(int)];
667     [data appendBytes:&len length:sizeof(int)];
669     [self queueMessage:SetScrollbarPositionMsgID data:data];
672 - (void)setScrollbarThumbValue:(long)val size:(long)size max:(long)max
673                     identifier:(long)ident
675     float fval = max-size+1 > 0 ? (float)val/(max-size+1) : 0;
676     float prop = (float)size/(max+1);
677     if (fval < 0) fval = 0;
678     else if (fval > 1.0f) fval = 1.0f;
679     if (prop < 0) prop = 0;
680     else if (prop > 1.0f) prop = 1.0f;
682     NSMutableData *data = [NSMutableData data];
684     [data appendBytes:&ident length:sizeof(long)];
685     [data appendBytes:&fval length:sizeof(float)];
686     [data appendBytes:&prop length:sizeof(float)];
688     [self queueMessage:SetScrollbarThumbMsgID data:data];
691 - (BOOL)setFontWithName:(char *)name
693     NSString *fontName;
694     float size = 0.0f;
695     BOOL parseFailed = NO;
697     if (name) {
698         fontName = [[[NSString alloc] initWithCString:name
699                 encoding:NSUTF8StringEncoding] autorelease];
700         NSArray *components = [fontName componentsSeparatedByString:@":"];
701         if ([components count] == 2) {
702             NSString *sizeString = [components lastObject];
703             if ([sizeString length] > 0
704                     && [sizeString characterAtIndex:0] == 'h') {
705                 sizeString = [sizeString substringFromIndex:1];
706                 if ([sizeString length] > 0) {
707                     size = [sizeString floatValue];
708                     fontName = [components objectAtIndex:0];
709                 }
710             } else {
711                 parseFailed = YES;
712             }
713         } else if ([components count] > 2) {
714             parseFailed = YES;
715         }
716     } else {
717         fontName = [[NSFont userFixedPitchFontOfSize:0] displayName];
718     }
720     if (!parseFailed && [fontName length] > 0) {
721         if (size < 6 || size > 100) {
722             // Font size 0.0 tells NSFont to use the 'user default size'.
723             size = 0.0f;
724         }
726         NSFont *font = [NSFont fontWithName:fontName size:size];
727         if (font) {
728             //NSLog(@"Setting font '%@' of size %.2f", fontName, size);
729             int len = [fontName
730                     lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
731             if (len > 0) {
732                 NSMutableData *data = [NSMutableData data];
734                 [data appendBytes:&size length:sizeof(float)];
735                 [data appendBytes:&len length:sizeof(int)];
736                 [data appendBytes:[fontName UTF8String] length:len];
738                 [self queueMessage:SetFontMsgID data:data];
739                 return YES;
740             }
741         }
742     }
744     NSLog(@"WARNING: Cannot set font with name '%@' of size %.2f",
745             fontName, size);
746     return NO;
749 - (void)executeActionWithName:(NSString *)name
751     int len = [name lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
753     if (len > 0) {
754         NSMutableData *data = [NSMutableData data];
756         [data appendBytes:&len length:sizeof(int)];
757         [data appendBytes:[name UTF8String] length:len];
759         [self queueMessage:ExecuteActionMsgID data:data];
760     }
763 - (void)setMouseShape:(int)shape
765     NSMutableData *data = [NSMutableData data];
766     [data appendBytes:&shape length:sizeof(int)];
767     [self queueMessage:SetMouseShapeMsgID data:data];
770 - (int)lookupColorWithKey:(NSString *)key
772     if (!(key && [key length] > 0))
773         return INVALCOLOR;
775     // First of all try to lookup key in the color dictionary; note that all
776     // keys in this dictionary are lowercase with no whitespace.
778     NSString *stripKey = [[[[key lowercaseString]
779         stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]
780             componentsSeparatedByString:@" "]
781                componentsJoinedByString:@""];
783     if (stripKey && [stripKey length] > 0) {
784         id obj = [colorDict objectForKey:stripKey];
785         if (obj) return [obj intValue];
787         // The key was not in the dictionary; is it perhaps of the form
788         // #rrggbb?
790         if ([stripKey length] > 1 && [stripKey characterAtIndex:0] == '#') {
791             NSScanner *scanner = [NSScanner scannerWithString:stripKey];
792             [scanner setScanLocation:1];
793             unsigned hex = 0;
794             if ([scanner scanHexInt:&hex]) {
795                 return (int)hex;
796             }
797         }
798     }
800     NSLog(@"WARNING: No color with key %@ found.", stripKey);
801     return INVALCOLOR;
804 - (oneway void)processInput:(int)msgid data:(in NSData *)data
806     [lastFlushDate release];
807     lastFlushDate = [[NSDate date] retain];
809     // HACK! A focus message might get lost, but whenever we get here the GUI
810     // is in focus.
811     if (!gui.in_focus && GotFocusMsgID != msgid && LostFocusMsgID != msgid)
812         gui_focus_change(TRUE);
814     [self handleMessage:msgid data:data];
815     inputReceived = YES;
818 - (oneway void)processInputAndData:(in NSArray *)messages
820     unsigned i, count = [messages count];
821     if (count % 2) {
822         NSLog(@"WARNING: [messages count] is odd in %s", _cmd);
823         return;
824     }
826     [lastFlushDate release];
827     lastFlushDate = [[NSDate date] retain];
829     for (i = 0; i < count; i += 2) {
830         int msgid = [[messages objectAtIndex:i] intValue];
831         id data = [messages objectAtIndex:i+1];
832         if ([data isEqual:[NSNull null]])
833             data = nil;
835         [self handleMessage:msgid data:data];
836     }
838     // HACK! A focus message might get lost, but whenever we get here the GUI
839     // is in focus.
840     if (!gui.in_focus)
841         gui_focus_change(TRUE);
843     inputReceived = YES;
846 - (BOOL)checkForModifiedBuffers
848     buf_T *buf;
849     for (buf = firstbuf; buf != NULL; buf = buf->b_next) {
850         if (bufIsChanged(buf)) {
851             return YES;
852         }
853     }
855     return NO;
858 - (BOOL)starRegisterToPasteboard:(byref NSPasteboard *)pboard
860     if (VIsual_active && (State & NORMAL) && clip_star.available) {
861         // If there is no pasteboard, return YES to indicate that there is text
862         // to copy.
863         if (!pboard)
864             return YES;
866         clip_copy_selection();
868         // Get the text to put on the pasteboard.
869         long_u len = 0; char_u *str = 0;
870         int type = clip_convert_selection(&str, &len, &clip_star);
871         if (type < 0)
872             return NO;
873         
874         NSString *string = [[NSString alloc]
875             initWithBytes:str length:len encoding:NSUTF8StringEncoding];
877         NSArray *types = [NSArray arrayWithObject:NSStringPboardType];
878         [pboard declareTypes:types owner:nil];
879         BOOL ok = [pboard setString:string forType:NSStringPboardType];
880     
881         [string release];
882         vim_free(str);
884         return ok;
885     }
887     return NO;
890 @end // MMBackend
894 @implementation MMBackend (Private)
896 - (void)handleMessage:(int)msgid data:(NSData *)data
898     if (KillTaskMsgID == msgid) {
899         //NSLog(@"VimTask received kill message; exiting now.");
900         // Set this flag here so that exit does not send TaskExitedMsgID back
901         // to MMVimController.
902         receivedKillTaskMsg = YES;
903         getout(0);
904     } else if (InsertTextMsgID == msgid) {
905         if (!data) return;
906         NSString *key = [[NSString alloc] initWithData:data
907                                               encoding:NSUTF8StringEncoding];
908         char_u *str = (char_u*)[key UTF8String];
909         int i, len = [key lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
911 #if MM_ENABLE_CONV
912         char_u *conv_str = NULL;
913         if (input_conv.vc_type != CONV_NONE) {
914             conv_str = string_convert(&input_conv, str, &len);
915             if (conv_str)
916                 str = conv_str;
917         }
918 #endif
920         for (i = 0; i < len; ++i) {
921             add_to_input_buf(str+i, 1);
922             if (CSI == str[i]) {
923                 // NOTE: If the converted string contains the byte CSI, then it
924                 // must be followed by the bytes KS_EXTRA, KE_CSI or things
925                 // won't work.
926                 static char_u extra[2] = { KS_EXTRA, KE_CSI };
927                 add_to_input_buf(extra, 2);
928             }
929         }
931 #if MM_ENABLE_CONV
932         if (conv_str)
933             vim_free(conv_str);
934 #endif
935         [key release];
936     } else if (KeyDownMsgID == msgid || CmdKeyMsgID == msgid) {
937         if (!data) return;
938         const void *bytes = [data bytes];
939         int mods = *((int*)bytes);  bytes += sizeof(int);
940         int len = *((int*)bytes);  bytes += sizeof(int);
941         NSString *key = [[NSString alloc] initWithBytes:bytes length:len
942                                               encoding:NSUTF8StringEncoding];
943         mods = eventModifierFlagsToVimModMask(mods);
945         [self handleKeyDown:key modifiers:mods];
947         [key release];
948     } else if (SelectTabMsgID == msgid) {
949         if (!data) return;
950         const void *bytes = [data bytes];
951         int idx = *((int*)bytes) + 1;
952         //NSLog(@"Selecting tab %d", idx);
953         send_tabline_event(idx);
954     } else if (CloseTabMsgID == msgid) {
955         if (!data) return;
956         const void *bytes = [data bytes];
957         int idx = *((int*)bytes) + 1;
958         //NSLog(@"Closing tab %d", idx);
959         send_tabline_menu_event(idx, TABLINE_MENU_CLOSE);
960     } else if (AddNewTabMsgID == msgid) {
961         //NSLog(@"Adding new tab");
962         send_tabline_menu_event(0, TABLINE_MENU_NEW);
963     } else if (DraggedTabMsgID == msgid) {
964         if (!data) return;
965         const void *bytes = [data bytes];
966         // NOTE! The destination index is 0 based, so do not add 1 to make it 1
967         // based.
968         int idx = *((int*)bytes);
970         tabpage_move(idx);
971     } else if (ScrollWheelMsgID == msgid) {
972         if (!data) return;
973         const void *bytes = [data bytes];
975         int row = *((int*)bytes);  bytes += sizeof(int);
976         int col = *((int*)bytes);  bytes += sizeof(int);
977         int flags = *((int*)bytes);  bytes += sizeof(int);
978         float dy = *((float*)bytes);  bytes += sizeof(float);
980         int button = MOUSE_5;
981         if (dy > 0) button = MOUSE_4;
983         flags = eventModifierFlagsToVimMouseModMask(flags);
985         gui_send_mouse_event(button, col, row, NO, flags);
986     } else if (MouseDownMsgID == msgid) {
987         if (!data) return;
988         const void *bytes = [data bytes];
990         int row = *((int*)bytes);  bytes += sizeof(int);
991         int col = *((int*)bytes);  bytes += sizeof(int);
992         int button = *((int*)bytes);  bytes += sizeof(int);
993         int flags = *((int*)bytes);  bytes += sizeof(int);
994         int count = *((int*)bytes);  bytes += sizeof(int);
996         button = eventButtonNumberToVimMouseButton(button);
997         flags = eventModifierFlagsToVimMouseModMask(flags);
999         gui_send_mouse_event(button, col, row, 0 != count, flags);
1000     } else if (MouseUpMsgID == msgid) {
1001         if (!data) return;
1002         const void *bytes = [data bytes];
1004         int row = *((int*)bytes);  bytes += sizeof(int);
1005         int col = *((int*)bytes);  bytes += sizeof(int);
1006         int flags = *((int*)bytes);  bytes += sizeof(int);
1008         flags = eventModifierFlagsToVimMouseModMask(flags);
1010         gui_send_mouse_event(MOUSE_RELEASE, col, row, NO, flags);
1011     } else if (MouseDraggedMsgID == msgid) {
1012         if (!data) return;
1013         const void *bytes = [data bytes];
1015         int row = *((int*)bytes);  bytes += sizeof(int);
1016         int col = *((int*)bytes);  bytes += sizeof(int);
1017         int flags = *((int*)bytes);  bytes += sizeof(int);
1019         flags = eventModifierFlagsToVimMouseModMask(flags);
1021         gui_send_mouse_event(MOUSE_DRAG, col, row, NO, flags);
1022     } else if (SetTextDimensionsMsgID == msgid) {
1023         if (!data) return;
1024         const void *bytes = [data bytes];
1025         int rows = *((int*)bytes);  bytes += sizeof(int);
1026         int cols = *((int*)bytes);  bytes += sizeof(int);
1028         // NOTE! Vim doesn't call gui_mch_set_shellsize() after
1029         // gui_resize_shell(), so we have to manually set the rows and columns
1030         // here.  (MacVim doesn't change the rows and columns to avoid
1031         // inconsistent states between Vim and MacVim.)
1032         [self setRows:rows columns:cols];
1034         //NSLog(@"[VimTask] Resizing shell to %dx%d.", cols, rows);
1035         gui_resize_shell(cols, rows);
1036     } else if (ExecuteMenuMsgID == msgid) {
1037         if (!data) return;
1038         const void *bytes = [data bytes];
1039         int tag = *((int*)bytes);  bytes += sizeof(int);
1041         vimmenu_T *menu = (vimmenu_T*)tag;
1042         // TODO!  Make sure 'menu' is a valid menu pointer!
1043         if (menu) {
1044             gui_menu_cb(menu);
1045         }
1046     } else if (ToggleToolbarMsgID == msgid) {
1047         char_u go[sizeof(GO_ALL)+2];
1048         char_u *p;
1049         int len;
1051         STRCPY(go, p_go);
1052         p = vim_strchr(go, GO_TOOLBAR);
1053         len = STRLEN(go);
1055         if (p != NULL) {
1056             char_u *end = go + len;
1057             while (p < end) {
1058                 p[0] = p[1];
1059                 ++p;
1060             }
1061         } else {
1062             go[len] = GO_TOOLBAR;
1063             go[len+1] = NUL;
1064         }
1066         set_option_value((char_u*)"guioptions", 0, go, 0);
1068         // Force screen redraw (does it have to be this complicated?).
1069         redraw_all_later(CLEAR);
1070         update_screen(NOT_VALID);
1071         setcursor();
1072         out_flush();
1073         gui_update_cursor(FALSE, FALSE);
1074         gui_mch_flush();
1075     } else if (ScrollbarEventMsgID == msgid) {
1076         if (!data) return;
1077         const void *bytes = [data bytes];
1078         long ident = *((long*)bytes);  bytes += sizeof(long);
1079         int hitPart = *((int*)bytes);  bytes += sizeof(int);
1080         float fval = *((float*)bytes);  bytes += sizeof(float);
1081         scrollbar_T *sb = gui_find_scrollbar(ident);
1083         if (sb) {
1084             scrollbar_T *sb_info = sb->wp ? &sb->wp->w_scrollbars[0] : sb;
1085             long value = sb_info->value;
1086             long size = sb_info->size;
1087             long max = sb_info->max;
1088             BOOL isStillDragging = NO;
1089             BOOL updateKnob = YES;
1091             switch (hitPart) {
1092             case NSScrollerDecrementPage:
1093                 value -= (size > 2 ? size - 2 : 1);
1094                 break;
1095             case NSScrollerIncrementPage:
1096                 value += (size > 2 ? size - 2 : 1);
1097                 break;
1098             case NSScrollerDecrementLine:
1099                 --value;
1100                 break;
1101             case NSScrollerIncrementLine:
1102                 ++value;
1103                 break;
1104             case NSScrollerKnob:
1105                 isStillDragging = YES;
1106                 // fall through ...
1107             case NSScrollerKnobSlot:
1108                 value = (long)(fval * (max - size + 1));
1109                 // fall through ...
1110             default:
1111                 updateKnob = NO;
1112                 break;
1113             }
1115             //NSLog(@"value %d -> %d", sb_info->value, value);
1116             gui_drag_scrollbar(sb, value, isStillDragging);
1118             if (updateKnob) {
1119                 // Dragging the knob or option+clicking automatically updates
1120                 // the knob position (on the actual NSScroller), so we only
1121                 // need to set the knob position in the other cases.
1122                 if (sb->wp) {
1123                     // Update both the left&right vertical scrollbars.
1124                     long identLeft = sb->wp->w_scrollbars[SBAR_LEFT].ident;
1125                     long identRight = sb->wp->w_scrollbars[SBAR_RIGHT].ident;
1126                     [self setScrollbarThumbValue:value size:size max:max
1127                                       identifier:identLeft];
1128                     [self setScrollbarThumbValue:value size:size max:max
1129                                       identifier:identRight];
1130                 } else {
1131                     // Update the horizontal scrollbar.
1132                     [self setScrollbarThumbValue:value size:size max:max
1133                                       identifier:ident];
1134                 }
1135             }
1136         }
1137     } else if (SetFontMsgID == msgid) {
1138         if (!data) return;
1139         const void *bytes = [data bytes];
1140         float pointSize = *((float*)bytes);  bytes += sizeof(float);
1141         //unsigned len = *((unsigned*)bytes);  bytes += sizeof(unsigned);
1142         bytes += sizeof(unsigned);  // len not used
1144         NSMutableString *name = [NSMutableString stringWithUTF8String:bytes];
1145         [name appendString:[NSString stringWithFormat:@":h%.2f", pointSize]];
1147         set_option_value((char_u*)"gfn", 0, (char_u*)[name UTF8String], 0);
1149         // Force screen redraw (does it have to be this complicated?).
1150         redraw_all_later(CLEAR);
1151         update_screen(NOT_VALID);
1152         setcursor();
1153         out_flush();
1154         gui_update_cursor(FALSE, FALSE);
1155         gui_mch_flush();
1156     } else if (VimShouldCloseMsgID == msgid) {
1157         gui_shell_closed();
1158     } else if (DropFilesMsgID == msgid) {
1159 #ifdef FEAT_DND
1160         const void *bytes = [data bytes];
1161         int n = *((int*)bytes);  bytes += sizeof(int);
1163 #if 0
1164         int row = *((int*)bytes);  bytes += sizeof(int);
1165         int col = *((int*)bytes);  bytes += sizeof(int);
1167         char_u **fnames = (char_u **)alloc(n * sizeof(char_u *));
1168         if (fnames) {
1169             const void *end = [data bytes] + [data length];
1170             int i = 0;
1171             while (bytes < end && i < n) {
1172                 int len = *((int*)bytes);  bytes += sizeof(int);
1173                 fnames[i++] = vim_strnsave((char_u*)bytes, len);
1174                 bytes += len;
1175             }
1177             // NOTE!  This function will free 'fnames'.
1178             gui_handle_drop(col, row, 0, fnames, i < n ? i : n);
1179         }
1180 #else
1181         // HACK!  I'm not sure how to get Vim to open a list of files in tabs,
1182         // so instead I create a ':tab drop' command with all the files to open
1183         // and execute it.
1184         NSMutableString *cmd = (n > 1)
1185                 ? [NSMutableString stringWithString:@":tab drop"]
1186                 : [NSMutableString stringWithString:@":drop"];
1188         const void *end = [data bytes] + [data length];
1189         int i;
1190         for (i = 0; i < n && bytes < end; ++i) {
1191             int len = *((int*)bytes);  bytes += sizeof(int);
1192             NSMutableString *file =
1193                     [NSMutableString stringWithUTF8String:bytes];
1194             [file replaceOccurrencesOfString:@" "
1195                                   withString:@"\\ "
1196                                      options:0
1197                                        range:NSMakeRange(0, [file length])];
1198             bytes += len;
1200             [cmd appendString:@" "];
1201             [cmd appendString:file];
1202         }
1204         // By going to the last tabpage we ensure that the new tabs will appear
1205         // last (if this call is left out, the taborder becomes messy).
1206         goto_tabpage(9999);
1208         do_cmdline_cmd((char_u*)[cmd UTF8String]);
1210         // Force screen redraw (does it have to be this complicated?).
1211         // (This code was taken from the end of gui_handle_drop().)
1212         update_screen(NOT_VALID);
1213         setcursor();
1214         out_flush();
1215         gui_update_cursor(FALSE, FALSE);
1216         gui_mch_flush();
1217 #endif
1218 #endif // FEAT_DND
1219     } else if (DropStringMsgID == msgid) {
1220 #ifdef FEAT_DND
1221         char_u  dropkey[3] = { CSI, KS_EXTRA, (char_u)KE_DROP };
1222         const void *bytes = [data bytes];
1223         int len = *((int*)bytes);  bytes += sizeof(int);
1224         NSMutableString *string = [NSMutableString stringWithUTF8String:bytes];
1226         // Replace unrecognized end-of-line sequences with \x0a (line feed).
1227         NSRange range = { 0, [string length] };
1228         unsigned n = [string replaceOccurrencesOfString:@"\x0d\x0a"
1229                                              withString:@"\x0a" options:0
1230                                                   range:range];
1231         if (0 == n) {
1232             n = [string replaceOccurrencesOfString:@"\x0d" withString:@"\x0a"
1233                                            options:0 range:range];
1234         }
1236         len = [string lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
1237         dnd_yank_drag_data((char_u*)[string UTF8String], len);
1238         add_to_input_buf(dropkey, sizeof(dropkey));
1239 #endif // FEAT_DND
1240     } else if (GotFocusMsgID == msgid) {
1241         if (!gui.in_focus)
1242             gui_focus_change(YES);
1243     } else if (LostFocusMsgID == msgid) {
1244         if (gui.in_focus)
1245             gui_focus_change(NO);
1246     } else if (MouseMovedMsgID == msgid) {
1247         const void *bytes = [data bytes];
1248         int row = *((int*)bytes);  bytes += sizeof(int);
1249         int col = *((int*)bytes);  bytes += sizeof(int);
1251         gui_mouse_moved(col, row);
1252     } else if (SetMouseShapeMsgID == msgid) {
1253         const void *bytes = [data bytes];
1254         int shape = *((int*)bytes);  bytes += sizeof(int);
1255         update_mouseshape(shape);
1256     } else {
1257         NSLog(@"WARNING: Unknown message received (msgid=%d)", msgid);
1258     }
1261 + (NSDictionary *)specialKeys
1263     static NSDictionary *specialKeys = nil;
1265     if (!specialKeys) {
1266         NSBundle *mainBundle = [NSBundle mainBundle];
1267         NSString *path = [mainBundle pathForResource:@"SpecialKeys"
1268                                               ofType:@"plist"];
1269         specialKeys = [[NSDictionary alloc] initWithContentsOfFile:path];
1270     }
1272     return specialKeys;
1275 - (void)handleKeyDown:(NSString *)key modifiers:(int)mods
1277     char_u special[3];
1278     char_u modChars[3];
1279     char_u *chars = 0;
1280     int length = 0;
1282     // Special keys (arrow keys, function keys, etc.) are stored in a plist so
1283     // that new keys can easily be added.
1284     NSString *specialString = [[MMBackend specialKeys]
1285             objectForKey:key];
1286     if (specialString && [specialString length] > 1) {
1287         //NSLog(@"special key: %@", specialString);
1288         int ikey = TO_SPECIAL([specialString characterAtIndex:0],
1289                 [specialString characterAtIndex:1]);
1291         ikey = simplify_key(ikey, &mods);
1292         if (ikey == CSI)
1293             ikey = K_CSI;
1295         special[0] = CSI;
1296         special[1] = K_SECOND(ikey);
1297         special[2] = K_THIRD(ikey);
1299         chars = special;
1300         length = 3;
1301     } else if ([key length] > 0) {
1302         chars = (char_u*)[key UTF8String];
1303         length = [key lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
1304         unichar c = [key characterAtIndex:0];
1306         //NSLog(@"non-special: %@ (hex=%x, mods=%d)", key,
1307         //        [key characterAtIndex:0], mods);
1309         if (length == 1 && ((c == Ctrl_C && ctrl_c_interrupts)
1310                 || (c == intr_char && intr_char != Ctrl_C))) {
1311             trash_input_buf();
1312             got_int = TRUE;
1313         }
1315         // HACK!  In most circumstances the Ctrl and Shift modifiers should be
1316         // cleared since they are already added to the key by the AppKit.
1317         // Unfortunately, the only way to deal with when to clear the modifiers
1318         // or not seems to be to have hard-wired rules like this.
1319         if ( !((' ' == c) || (0xa0 == c) || (mods & MOD_MASK_CMD)) ) {
1320             mods &= ~MOD_MASK_SHIFT;
1321             mods &= ~MOD_MASK_CTRL;
1322             //NSLog(@"clear shift ctrl");
1323         }
1325         // HACK!  All Option+key presses go via 'insert text' messages, except
1326         // for <M-Space>.  If the Alt flag is not cleared for <M-Space> it does
1327         // not work to map to it.
1328         if (0xa0 == c && !(mods & MOD_MASK_CMD)) {
1329             //NSLog(@"clear alt");
1330             mods &= ~MOD_MASK_ALT;
1331         }
1332     }
1334     if (chars && length > 0) {
1335         if (mods) {
1336             //NSLog(@"adding mods: %d", mods);
1337             modChars[0] = CSI;
1338             modChars[1] = KS_MODIFIER;
1339             modChars[2] = mods;
1340             add_to_input_buf(modChars, 3);
1341         }
1343         //NSLog(@"add to input buf: 0x%x", chars[0]);
1344         // TODO: Check for CSI bytes?
1345         add_to_input_buf(chars, length);
1346     }
1349 - (void)queueMessage:(int)msgid data:(NSData *)data
1351     [queue addObject:[NSData dataWithBytes:&msgid length:sizeof(int)]];
1352     if (data)
1353         [queue addObject:data];
1354     else
1355         [queue addObject:[NSData data]];
1358 - (void)connectionDidDie:(NSNotification *)notification
1360     // If the main connection to MacVim is lost this means that MacVim was
1361     // either quit (by the user chosing Quit on the MacVim menu), or it has
1362     // crashed.  In either case our only option is to quit now.
1363     // TODO: Write backup file?
1365     //NSLog(@"A Vim process lots its connection to MacVim; quitting.");
1366     getout(0);
1369 @end // MMBackend (Private)
1374 static int eventModifierFlagsToVimModMask(int modifierFlags)
1376     int modMask = 0;
1378     if (modifierFlags & NSShiftKeyMask)
1379         modMask |= MOD_MASK_SHIFT;
1380     if (modifierFlags & NSControlKeyMask)
1381         modMask |= MOD_MASK_CTRL;
1382     if (modifierFlags & NSAlternateKeyMask)
1383         modMask |= MOD_MASK_ALT;
1384     if (modifierFlags & NSCommandKeyMask)
1385         modMask |= MOD_MASK_CMD;
1387     return modMask;
1390 static int vimModMaskToEventModifierFlags(int mods)
1392     int flags = 0;
1394     if (mods & MOD_MASK_SHIFT)
1395         flags |= NSShiftKeyMask;
1396     if (mods & MOD_MASK_CTRL)
1397         flags |= NSControlKeyMask;
1398     if (mods & MOD_MASK_ALT)
1399         flags |= NSAlternateKeyMask;
1400     if (mods & MOD_MASK_CMD)
1401         flags |= NSCommandKeyMask;
1403     return flags;
1406 static int eventModifierFlagsToVimMouseModMask(int modifierFlags)
1408     int modMask = 0;
1410     if (modifierFlags & NSShiftKeyMask)
1411         modMask |= MOUSE_SHIFT;
1412     if (modifierFlags & NSControlKeyMask)
1413         modMask |= MOUSE_CTRL;
1414     if (modifierFlags & NSAlternateKeyMask)
1415         modMask |= MOUSE_ALT;
1417     return modMask;
1420 static int eventButtonNumberToVimMouseButton(int buttonNumber)
1422     static int mouseButton[] = { MOUSE_LEFT, MOUSE_RIGHT, MOUSE_MIDDLE,
1423             MOUSE_X1, MOUSE_X2 };
1425     return mouseButton[buttonNumber < 5 ? buttonNumber : 0];
1428 static int specialKeyToNSKey(int key)
1430     if (!IS_SPECIAL(key))
1431         return key;
1433     static struct {
1434         int special;
1435         int nskey;
1436     } sp2ns[] = {
1437         { K_UP, NSUpArrowFunctionKey },
1438         { K_DOWN, NSDownArrowFunctionKey },
1439         { K_LEFT, NSLeftArrowFunctionKey },
1440         { K_RIGHT, NSRightArrowFunctionKey },
1441         { K_F1, NSF1FunctionKey },
1442         { K_F2, NSF2FunctionKey },
1443         { K_F3, NSF3FunctionKey },
1444         { K_F4, NSF4FunctionKey },
1445         { K_F5, NSF5FunctionKey },
1446         { K_F6, NSF6FunctionKey },
1447         { K_F7, NSF7FunctionKey },
1448         { K_F8, NSF8FunctionKey },
1449         { K_F9, NSF9FunctionKey },
1450         { K_F10, NSF10FunctionKey },
1451         { K_F11, NSF11FunctionKey },
1452         { K_F12, NSF12FunctionKey },
1453         { K_F13, NSF13FunctionKey },
1454         { K_F14, NSF14FunctionKey },
1455         { K_F15, NSF15FunctionKey },
1456         { K_F16, NSF16FunctionKey },
1457         { K_F17, NSF17FunctionKey },
1458         { K_F18, NSF18FunctionKey },
1459         { K_F19, NSF19FunctionKey },
1460         { K_F20, NSF20FunctionKey },
1461         { K_F21, NSF21FunctionKey },
1462         { K_F22, NSF22FunctionKey },
1463         { K_F23, NSF23FunctionKey },
1464         { K_F24, NSF24FunctionKey },
1465         { K_F25, NSF25FunctionKey },
1466         { K_F26, NSF26FunctionKey },
1467         { K_F27, NSF27FunctionKey },
1468         { K_F28, NSF28FunctionKey },
1469         { K_F29, NSF29FunctionKey },
1470         { K_F30, NSF30FunctionKey },
1471         { K_F31, NSF31FunctionKey },
1472         { K_F32, NSF32FunctionKey },
1473         { K_F33, NSF33FunctionKey },
1474         { K_F34, NSF34FunctionKey },
1475         { K_F35, NSF35FunctionKey },
1476         { K_DEL, NSBackspaceCharacter },
1477         { K_BS, NSDeleteCharacter },
1478         { K_HOME, NSHomeFunctionKey },
1479         { K_END, NSEndFunctionKey },
1480         { K_PAGEUP, NSPageUpFunctionKey },
1481         { K_PAGEDOWN, NSPageDownFunctionKey }
1482     };
1484     int i;
1485     for (i = 0; i < sizeof(sp2ns)/sizeof(sp2ns[0]); ++i) {
1486         if (sp2ns[i].special == key)
1487             return sp2ns[i].nskey;
1488     }
1490     return 0;