Use 'behave mswin' instead of setting 'keymodel' and 'selectmode'.
[MacVim/jjgod.git] / MMBackend.m
blobb44be40e6c9e2ecdd1bffa430896efd53b42d5a8
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);
30 enum {
31     MMBlinkStateNone = 0,
32     MMBlinkStateOn,
33     MMBlinkStateOff
37 @interface MMBackend (Private)
38 - (void)handleMessage:(int)msgid data:(NSData *)data;
39 + (NSDictionary *)specialKeys;
40 - (void)handleKeyDown:(NSString *)key modifiers:(int)mods;
41 - (void)queueMessage:(int)msgid data:(NSData *)data;
42 - (void)connectionDidDie:(NSNotification *)notification;
43 - (void)blinkTimerFired:(NSTimer *)timer;
44 @end
48 @implementation MMBackend
50 + (MMBackend *)sharedInstance
52     static MMBackend *singleton = nil;
53     return singleton ? singleton : (singleton = [MMBackend new]);
56 - (id)init
58     if ((self = [super init])) {
59         queue = [[NSMutableArray alloc] init];
60         drawData = [[NSMutableData alloc] initWithCapacity:1024];
61         NSString *path = [[NSBundle mainBundle] pathForResource:@"Colors"
62                                                          ofType:@"plist"];
63         colorDict = [[NSDictionary dictionaryWithContentsOfFile:path] retain];
64     }
66     return self;
69 - (void)dealloc
71     //NSLog(@"%@ %s", [self className], _cmd);
73     [[NSNotificationCenter defaultCenter] removeObserver:self];
75     [blinkTimer release];  blinkTimer = nil;
76     [queue release];  queue = nil;
77     [drawData release];  drawData = nil;
78     [frontendProxy release];  frontendProxy = nil;
79     [connection release];  connection = nil;
80     [colorDict release];  colorDict = nil;
82     [super dealloc];
85 - (void)setBackgroundColor:(int)color
87     backgroundColor = color;
90 - (void)setForegroundColor:(int)color
92     foregroundColor = color;
95 - (void)setSpecialColor:(int)color
97     specialColor = color;
100 - (void)setDefaultColorsBackground:(int)bg foreground:(int)fg
102     defaultBackgroundColor = bg;
103     defaultForegroundColor = fg;
105     NSMutableData *data = [NSMutableData data];
107     [data appendBytes:&bg length:sizeof(int)];
108     [data appendBytes:&fg length:sizeof(int)];
110     [self queueMessage:SetDefaultColorsMsgID data:data];
113 - (BOOL)checkin
115     NSBundle *mainBundle = [NSBundle mainBundle];
117     // NOTE!  If the name of the connection changes here it must also be
118     // updated in MMAppController.m.
119     NSString *name = [NSString stringWithFormat:@"%@-connection",
120              [mainBundle bundleIdentifier]];
121     connection = [NSConnection connectionWithRegisteredName:name host:nil];
122     if (!connection) {
123 #if 0
124         NSString *path = [mainBundle bundlePath];
125         if (![[NSWorkspace sharedWorkspace] launchApplication:path]) {
126             NSLog(@"WARNING: Failed to launch GUI with path %@", path);
127             return NO;
128         }
129 #else
130         // HACK!  It would be preferable to launch the GUI using NSWorkspace,
131         // however I have not managed to figure out how to pass arguments using
132         // NSWorkspace.
133         //
134         // NOTE!  Using NSTask to launch the GUI has the negative side-effect
135         // that the GUI won't be activated (or raised) so there is a hack in
136         // MMWindowController which always raises the app when a new window is
137         // opened.
138         NSMutableArray *args = [NSMutableArray arrayWithObjects:
139             [NSString stringWithFormat:@"-%@", MMNoWindowKey], @"yes", nil];
140         NSString *exeName = [[mainBundle infoDictionary]
141                 objectForKey:@"CFBundleExecutable"];
142         NSString *path = [mainBundle pathForAuxiliaryExecutable:exeName];
143         if (!path) {
144             NSLog(@"ERROR: Could not find MacVim executable in bundle");
145             return NO;
146         }
148         [NSTask launchedTaskWithLaunchPath:path arguments:args];
149 #endif
151         // HACK!  The NSWorkspaceDidLaunchApplicationNotification does not work
152         // for tasks like this, so poll the mach bootstrap server until it
153         // returns a valid connection.  Also set a time-out date so that we
154         // don't get stuck doing this forever.
155         NSDate *timeOutDate = [NSDate dateWithTimeIntervalSinceNow:15];
156         while (!connection &&
157                 NSOrderedDescending == [timeOutDate compare:[NSDate date]])
158         {
159             [[NSRunLoop currentRunLoop]
160                     runMode:NSDefaultRunLoopMode
161                  beforeDate:[NSDate dateWithTimeIntervalSinceNow:1]];
163             connection = [NSConnection connectionWithRegisteredName:name
164                                                                host:nil];
165         }
167         if (!connection) {
168             NSLog(@"WARNING: Timed-out waiting for GUI to launch.");
169             return NO;
170         }
171     }
173     id proxy = [connection rootProxy];
174     [proxy setProtocolForProxy:@protocol(MMAppProtocol)];
176     [[NSNotificationCenter defaultCenter] addObserver:self
177             selector:@selector(connectionDidDie:)
178                 name:NSConnectionDidDieNotification object:connection];
180     int pid = [[NSProcessInfo processInfo] processIdentifier];
182     @try {
183         frontendProxy = [(NSDistantObject*)[proxy connectBackend:self
184                                                              pid:pid] retain];
185     }
186     @catch (NSException *e) {
187         NSLog(@"Exception caught when trying to connect backend: \"%@\"", e);
188     }
190     if (frontendProxy) {
191         [frontendProxy setProtocolForProxy:@protocol(MMAppProtocol)];
192     }
194     return connection && frontendProxy;
197 - (BOOL)openVimWindow
199     [self queueMessage:OpenVimWindowMsgID data:nil];
200     return YES;
203 - (void)clearAll
205     int type = ClearAllDrawType;
207     // Any draw commands in queue are effectively obsolete since this clearAll
208     // will negate any effect they have, therefore we may as well clear the
209     // draw queue.
210     [drawData setLength:0];
212     [drawData appendBytes:&type length:sizeof(int)];
214     [drawData appendBytes:&defaultBackgroundColor length:sizeof(int)];
217 - (void)clearBlockFromRow:(int)row1 column:(int)col1
218                     toRow:(int)row2 column:(int)col2
220     int type = ClearBlockDrawType;
222     [drawData appendBytes:&type length:sizeof(int)];
224     [drawData appendBytes:&defaultBackgroundColor length:sizeof(int)];
225     [drawData appendBytes:&row1 length:sizeof(int)];
226     [drawData appendBytes:&col1 length:sizeof(int)];
227     [drawData appendBytes:&row2 length:sizeof(int)];
228     [drawData appendBytes:&col2 length:sizeof(int)];
231 - (void)deleteLinesFromRow:(int)row count:(int)count
232               scrollBottom:(int)bottom left:(int)left right:(int)right
234     int type = DeleteLinesDrawType;
236     [drawData appendBytes:&type length:sizeof(int)];
238     [drawData appendBytes:&defaultBackgroundColor length:sizeof(int)];
239     [drawData appendBytes:&row length:sizeof(int)];
240     [drawData appendBytes:&count length:sizeof(int)];
241     [drawData appendBytes:&bottom length:sizeof(int)];
242     [drawData appendBytes:&left length:sizeof(int)];
243     [drawData appendBytes:&right length:sizeof(int)];
246 - (void)replaceString:(char*)s length:(int)len row:(int)row column:(int)col
247                 flags:(int)flags
249     if (len <= 0) return;
251     int type = ReplaceStringDrawType;
253     [drawData appendBytes:&type length:sizeof(int)];
255     [drawData appendBytes:&backgroundColor length:sizeof(int)];
256     [drawData appendBytes:&foregroundColor length:sizeof(int)];
257     [drawData appendBytes:&specialColor length:sizeof(int)];
258     [drawData appendBytes:&row length:sizeof(int)];
259     [drawData appendBytes:&col length:sizeof(int)];
260     [drawData appendBytes:&flags length:sizeof(int)];
261     [drawData appendBytes:&len length:sizeof(int)];
262     [drawData appendBytes:s length:len];
265 - (void)insertLinesFromRow:(int)row count:(int)count
266               scrollBottom:(int)bottom left:(int)left right:(int)right
268     int type = InsertLinesDrawType;
270     [drawData appendBytes:&type length:sizeof(int)];
272     [drawData appendBytes:&defaultBackgroundColor length:sizeof(int)];
273     [drawData appendBytes:&row length:sizeof(int)];
274     [drawData appendBytes:&count length:sizeof(int)];
275     [drawData appendBytes:&bottom length:sizeof(int)];
276     [drawData appendBytes:&left length:sizeof(int)];
277     [drawData appendBytes:&right length:sizeof(int)];
280 - (void)drawCursorAtRow:(int)row column:(int)col shape:(int)shape
281                fraction:(int)percent color:(int)color
283     int type = DrawCursorDrawType;
285     [drawData appendBytes:&type length:sizeof(int)];
287     [drawData appendBytes:&color length:sizeof(int)];
288     [drawData appendBytes:&row length:sizeof(int)];
289     [drawData appendBytes:&col length:sizeof(int)];
290     [drawData appendBytes:&shape length:sizeof(int)];
291     [drawData appendBytes:&percent length:sizeof(int)];
294 - (void)flushQueue:(BOOL)force
296     // NOTE! This method gets called a lot; if we were to flush every time it
297     // was called MacVim would feel unresponsive.  So there is a time out which
298     // ensures that the queue isn't flushed too often.
299     if (!force && lastFlushDate && -[lastFlushDate timeIntervalSinceNow]
300             < MMFlushTimeoutInterval)
301         return;
303     if ([drawData length] > 0) {
304         [self queueMessage:BatchDrawMsgID data:[drawData copy]];
305         [drawData setLength:0];
306     }
308     if ([queue count] > 0) {
309         @try {
310             [frontendProxy processCommandQueue:queue];
311         }
312         @catch (NSException *e) {
313             NSLog(@"Exception caught when processing command queue: \"%@\"", e);
314         }
316         [queue removeAllObjects];
318         [lastFlushDate release];
319         lastFlushDate = [[NSDate date] retain];
320     }
323 - (BOOL)waitForInput:(int)milliseconds
325     NSDate *date = milliseconds > 0 ?
326             [NSDate dateWithTimeIntervalSinceNow:.001*milliseconds] : 
327             [NSDate distantFuture];
329     [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:date];
331     // I know of no way to figure out if the run loop exited because input was
332     // found or because of a time out, so I need to manually indicate when
333     // input was received in processInput:data: and then reset it every time
334     // here.
335     BOOL yn = inputReceived;
336     inputReceived = NO;
338     return yn;
341 - (void)exit
343     // By invalidating the NSConnection the MMWindowController immediately
344     // finds out that the connection is down and as a result
345     // [MMWindowController connectionDidDie:] is invoked.
346     //NSLog(@"%@ %s", [self className], _cmd);
347     [[NSNotificationCenter defaultCenter] removeObserver:self];
348     [connection invalidate];
351 - (void)selectTab:(int)index
353     //NSLog(@"%s%d", _cmd, index);
355     index -= 1;
356     NSData *data = [NSData dataWithBytes:&index length:sizeof(int)];
357     [self queueMessage:SelectTabMsgID data:data];
360 - (void)updateTabBar
362     //NSLog(@"%s", _cmd);
364     NSMutableData *data = [NSMutableData data];
366     int idx = tabpage_index(curtab) - 1;
367     [data appendBytes:&idx length:sizeof(int)];
369     tabpage_T *tp;
370     for (tp = first_tabpage; tp != NULL; tp = tp->tp_next) {
371         // This function puts the label of the tab in the global 'NameBuff'.
372         get_tabline_label(tp, FALSE);
373         int len = strlen((char*)NameBuff);
374         if (len <= 0) continue;
376         // Count the number of windows in the tabpage.
377         //win_T *wp = tp->tp_firstwin;
378         //int wincount;
379         //for (wincount = 0; wp != NULL; wp = wp->w_next, ++wincount);
381         //[data appendBytes:&wincount length:sizeof(int)];
382         [data appendBytes:&len length:sizeof(int)];
383         [data appendBytes:NameBuff length:len];
384     }
386     [self queueMessage:UpdateTabBarMsgID data:data];
389 - (BOOL)tabBarVisible
391     return tabBarVisible;
394 - (void)showTabBar:(BOOL)enable
396     tabBarVisible = enable;
398     int msgid = enable ? ShowTabBarMsgID : HideTabBarMsgID;
399     [self queueMessage:msgid data:nil];
402 - (void)setRows:(int)rows columns:(int)cols
404     //NSLog(@"[VimTask] setRows:%d columns:%d", rows, cols);
406     int dim[] = { rows, cols };
407     NSData *data = [NSData dataWithBytes:&dim length:2*sizeof(int)];
409     [self queueMessage:SetTextDimensionsMsgID data:data];
412 - (void)setVimWindowTitle:(char *)title
414     NSMutableData *data = [NSMutableData data];
415     int len = strlen(title);
416     if (len <= 0) return;
418     [data appendBytes:&len length:sizeof(int)];
419     [data appendBytes:title length:len];
421     [self queueMessage:SetVimWindowTitleMsgID data:data];
424 - (char *)browseForFileInDirectory:(char *)dir title:(char *)title
425                             saving:(int)saving
427     //NSLog(@"browseForFileInDirectory:%s title:%s saving:%d", dir, title,
428     //        saving);
430     char_u *s = NULL;
431     NSString *ds = dir
432             ? [NSString stringWithCString:dir encoding:NSUTF8StringEncoding]
433             : nil;
434     NSString *ts = title
435             ? [NSString stringWithCString:title encoding:NSUTF8StringEncoding]
436             : nil;
437     @try {
438         [frontendProxy showSavePanelForDirectory:ds title:ts saving:saving];
440         // Wait until a reply is sent from MMVimController.
441         [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
442                                  beforeDate:[NSDate distantFuture]];
444         if (dialogReturn && [dialogReturn isKindOfClass:[NSString class]]) {
445             s = vim_strsave((char_u*)[dialogReturn UTF8String]);
446         }
448         [dialogReturn release];  dialogReturn = nil;
449     }
450     @catch (NSException *e) {
451         NSLog(@"Exception caught when showing save panel: \"%@\"", e);
452     }
454     return (char *)s;
457 - (oneway void)setDialogReturn:(in bycopy id)obj
459     // NOTE: This is called by
460     //   - [MMVimController panelDidEnd:::], and
461     //   - [MMVimController alertDidEnd:::],
462     // to indicate that a save/open panel or alert has finished.
464     if (obj != dialogReturn) {
465         [dialogReturn release];
466         dialogReturn = [obj retain];
467     }
470 - (int)presentDialogWithType:(int)type title:(char *)title message:(char *)msg
471                      buttons:(char *)btns textField:(char *)txtfield
473     int retval = 0;
474     NSString *message = nil, *text = nil, *textFieldString = nil;
475     NSArray *buttons = nil;
476     int style = NSInformationalAlertStyle;
478     if (VIM_WARNING == type) style = NSWarningAlertStyle;
479     else if (VIM_ERROR == type) style = NSCriticalAlertStyle;
481     if (btns) {
482         NSString *btnString = [NSString stringWithUTF8String:btns];
483         buttons = [btnString componentsSeparatedByString:@"\n"];
484     }
485     if (title)
486         message = [NSString stringWithUTF8String:title];
487     if (msg) {
488         text = [NSString stringWithUTF8String:msg];
489         if (!message) {
490             // HACK! If there is a '\n\n' or '\n' sequence in the message, then
491             // make the part up to there into the title.  We only do this
492             // because Vim has lots of dialogs without a title and they look
493             // ugly that way.
494             // TODO: Fix the actual dialog texts.
495             NSRange eolRange = [text rangeOfString:@"\n\n"];
496             if (NSNotFound == eolRange.location)
497                 eolRange = [text rangeOfString:@"\n"];
498             if (NSNotFound != eolRange.location) {
499                 message = [text substringToIndex:eolRange.location];
500                 text = [text substringFromIndex:NSMaxRange(eolRange)];
501             }
502         }
503     }
504     if (txtfield)
505         textFieldString = [NSString stringWithUTF8String:txtfield];
507     @try {
508         [frontendProxy presentDialogWithStyle:style message:message
509                               informativeText:text buttonTitles:buttons
510                               textFieldString:textFieldString];
512         // Wait until a reply is sent from MMVimController.
513         [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
514                                  beforeDate:[NSDate distantFuture]];
516         if (dialogReturn && [dialogReturn isKindOfClass:[NSArray class]]
517                 && [dialogReturn count]) {
518             retval = [[dialogReturn objectAtIndex:0] intValue];
519             if (txtfield && [dialogReturn count] > 1) {
520                 NSString *retString = [dialogReturn objectAtIndex:1];
521                 vim_strncpy((char_u*)txtfield, (char_u*)[retString UTF8String],
522                         IOSIZE - 1);
523             }
524         }
526         [dialogReturn release]; dialogReturn = nil;
527     }
528     @catch (NSException *e) {
529         NSLog(@"Exception caught while showing alert dialog: \"%@\"", e);
530     }
532     return retval;
535 - (void)addMenuWithTag:(int)tag parent:(int)parentTag name:(char *)name
536                atIndex:(int)index
538     //NSLog(@"addMenuWithTag:%d parent:%d name:%s atIndex:%d", tag, parentTag,
539     //        name, index);
541     int namelen = name ? strlen(name) : 0;
542     NSMutableData *data = [NSMutableData data];
544     [data appendBytes:&tag length:sizeof(int)];
545     [data appendBytes:&parentTag length:sizeof(int)];
546     [data appendBytes:&namelen length:sizeof(int)];
547     if (namelen > 0) [data appendBytes:name length:namelen];
548     [data appendBytes:&index length:sizeof(int)];
550     [self queueMessage:AddMenuMsgID data:data];
553 - (void)addMenuItemWithTag:(int)tag parent:(int)parentTag name:(char *)name
554                        tip:(char *)tip icon:(char *)icon
555              keyEquivalent:(int)key modifiers:(int)mods
556                     action:(NSString *)action atIndex:(int)index
558     //NSLog(@"addMenuItemWithTag:%d parent:%d name:%s tip:%s atIndex:%d", tag,
559     //        parentTag, name, tip, index);
561     int namelen = name ? strlen(name) : 0;
562     int tiplen = tip ? strlen(tip) : 0;
563     int iconlen = icon ? strlen(icon) : 0;
564     int eventFlags = vimModMaskToEventModifierFlags(mods);
565     int actionlen = [action lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
566     NSMutableData *data = [NSMutableData data];
568     key = specialKeyToNSKey(key);
570     [data appendBytes:&tag length:sizeof(int)];
571     [data appendBytes:&parentTag length:sizeof(int)];
572     [data appendBytes:&namelen length:sizeof(int)];
573     if (namelen > 0) [data appendBytes:name length:namelen];
574     [data appendBytes:&tiplen length:sizeof(int)];
575     if (tiplen > 0) [data appendBytes:tip length:tiplen];
576     [data appendBytes:&iconlen length:sizeof(int)];
577     if (iconlen > 0) [data appendBytes:icon length:iconlen];
578     [data appendBytes:&actionlen length:sizeof(int)];
579     if (actionlen > 0) [data appendBytes:[action UTF8String] length:actionlen];
580     [data appendBytes:&index length:sizeof(int)];
581     [data appendBytes:&key length:sizeof(int)];
582     [data appendBytes:&eventFlags length:sizeof(int)];
584     [self queueMessage:AddMenuItemMsgID data:data];
587 - (void)removeMenuItemWithTag:(int)tag
589     NSMutableData *data = [NSMutableData data];
590     [data appendBytes:&tag length:sizeof(int)];
592     [self queueMessage:RemoveMenuItemMsgID data:data];
595 - (void)enableMenuItemWithTag:(int)tag state:(int)enabled
597     NSMutableData *data = [NSMutableData data];
599     [data appendBytes:&tag length:sizeof(int)];
600     [data appendBytes:&enabled length:sizeof(int)];
602     [self queueMessage:EnableMenuItemMsgID data:data];
605 - (void)showPopupMenuWithName:(char *)name atMouseLocation:(BOOL)mouse
607     int len = strlen(name);
608     int row = -1, col = -1;
610     if (len <= 0) return;
612     if (!mouse && curwin) {
613         row = curwin->w_wrow;
614         col = curwin->w_wcol;
615     }
617     NSMutableData *data = [NSMutableData data];
619     [data appendBytes:&row length:sizeof(int)];
620     [data appendBytes:&col length:sizeof(int)];
621     [data appendBytes:&len length:sizeof(int)];
622     [data appendBytes:name length:len];
624     [self queueMessage:ShowPopupMenuMsgID data:data];
627 - (void)showToolbar:(int)enable flags:(int)flags
629     NSMutableData *data = [NSMutableData data];
631     [data appendBytes:&enable length:sizeof(int)];
632     [data appendBytes:&flags length:sizeof(int)];
634     [self queueMessage:ShowToolbarMsgID data:data];
637 - (void)createScrollbarWithIdentifier:(long)ident type:(int)type
639     NSMutableData *data = [NSMutableData data];
641     [data appendBytes:&ident length:sizeof(long)];
642     [data appendBytes:&type length:sizeof(int)];
644     [self queueMessage:CreateScrollbarMsgID data:data];
647 - (void)destroyScrollbarWithIdentifier:(long)ident
649     NSMutableData *data = [NSMutableData data];
650     [data appendBytes:&ident length:sizeof(long)];
652     [self queueMessage:DestroyScrollbarMsgID data:data];
655 - (void)showScrollbarWithIdentifier:(long)ident state:(int)visible
657     NSMutableData *data = [NSMutableData data];
659     [data appendBytes:&ident length:sizeof(long)];
660     [data appendBytes:&visible length:sizeof(int)];
662     [self queueMessage:ShowScrollbarMsgID data:data];
665 - (void)setScrollbarPosition:(int)pos length:(int)len identifier:(long)ident
667     NSMutableData *data = [NSMutableData data];
669     [data appendBytes:&ident length:sizeof(long)];
670     [data appendBytes:&pos length:sizeof(int)];
671     [data appendBytes:&len length:sizeof(int)];
673     [self queueMessage:SetScrollbarPositionMsgID data:data];
676 - (void)setScrollbarThumbValue:(long)val size:(long)size max:(long)max
677                     identifier:(long)ident
679     float fval = max-size+1 > 0 ? (float)val/(max-size+1) : 0;
680     float prop = (float)size/(max+1);
681     if (fval < 0) fval = 0;
682     else if (fval > 1.0f) fval = 1.0f;
683     if (prop < 0) prop = 0;
684     else if (prop > 1.0f) prop = 1.0f;
686     NSMutableData *data = [NSMutableData data];
688     [data appendBytes:&ident length:sizeof(long)];
689     [data appendBytes:&fval length:sizeof(float)];
690     [data appendBytes:&prop length:sizeof(float)];
692     [self queueMessage:SetScrollbarThumbMsgID data:data];
695 - (BOOL)setFontWithName:(char *)name
697     NSString *fontName;
698     float size = 0.0f;
699     BOOL parseFailed = NO;
701     if (name) {
702         fontName = [[[NSString alloc] initWithCString:name
703                 encoding:NSUTF8StringEncoding] autorelease];
704         NSArray *components = [fontName componentsSeparatedByString:@":"];
705         if ([components count] == 2) {
706             NSString *sizeString = [components lastObject];
707             if ([sizeString length] > 0
708                     && [sizeString characterAtIndex:0] == 'h') {
709                 sizeString = [sizeString substringFromIndex:1];
710                 if ([sizeString length] > 0) {
711                     size = [sizeString floatValue];
712                     fontName = [components objectAtIndex:0];
713                 }
714             } else {
715                 parseFailed = YES;
716             }
717         } else if ([components count] > 2) {
718             parseFailed = YES;
719         }
720     } else {
721         fontName = [[NSFont userFixedPitchFontOfSize:0] displayName];
722     }
724     if (!parseFailed && [fontName length] > 0) {
725         if (size < 6 || size > 100) {
726             // Font size 0.0 tells NSFont to use the 'user default size'.
727             size = 0.0f;
728         }
730         NSFont *font = [NSFont fontWithName:fontName size:size];
731         if (font) {
732             //NSLog(@"Setting font '%@' of size %.2f", fontName, size);
733             int len = [fontName
734                     lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
735             if (len > 0) {
736                 NSMutableData *data = [NSMutableData data];
738                 [data appendBytes:&size length:sizeof(float)];
739                 [data appendBytes:&len length:sizeof(int)];
740                 [data appendBytes:[fontName UTF8String] length:len];
742                 [self queueMessage:SetFontMsgID data:data];
743                 return YES;
744             }
745         }
746     }
748     NSLog(@"WARNING: Cannot set font with name '%@' of size %.2f",
749             fontName, size);
750     return NO;
753 - (void)executeActionWithName:(NSString *)name
755     int len = [name lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
757     if (len > 0) {
758         NSMutableData *data = [NSMutableData data];
760         [data appendBytes:&len length:sizeof(int)];
761         [data appendBytes:[name UTF8String] length:len];
763         [self queueMessage:ExecuteActionMsgID data:data];
764     }
767 - (void)setMouseShape:(int)shape
769     NSMutableData *data = [NSMutableData data];
770     [data appendBytes:&shape length:sizeof(int)];
771     [self queueMessage:SetMouseShapeMsgID data:data];
774 - (void)setBlinkWait:(int)wait on:(int)on off:(int)off
776     // Vim specifies times in milliseconds, whereas Cocoa wants them in
777     // seconds.
778     blinkWaitInterval = .001f*wait;
779     blinkOnInterval = .001f*on;
780     blinkOffInterval = .001f*off;
783 - (void)startBlink
785     if (blinkTimer) {
786         [blinkTimer invalidate];
787         [blinkTimer release];
788         blinkTimer = nil;
789     }
791     if (blinkWaitInterval > 0 && blinkOnInterval > 0 && blinkOffInterval > 0
792             && gui.in_focus) {
793         blinkState = MMBlinkStateOn;
794         blinkTimer =
795             [[NSTimer scheduledTimerWithTimeInterval:blinkWaitInterval
796                                               target:self
797                                             selector:@selector(blinkTimerFired:)
798                                             userInfo:nil repeats:NO] retain];
799         gui_update_cursor(TRUE, FALSE);
800         [self flushQueue:YES];
801     }
804 - (void)stopBlink
806     if (MMBlinkStateOff == blinkState) {
807         gui_update_cursor(TRUE, FALSE);
808         [self flushQueue:YES];
809     }
811     blinkState = MMBlinkStateNone;
814 - (int)lookupColorWithKey:(NSString *)key
816     if (!(key && [key length] > 0))
817         return INVALCOLOR;
819     // First of all try to lookup key in the color dictionary; note that all
820     // keys in this dictionary are lowercase with no whitespace.
822     NSString *stripKey = [[[[key lowercaseString]
823         stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]
824             componentsSeparatedByString:@" "]
825                componentsJoinedByString:@""];
827     if (stripKey && [stripKey length] > 0) {
828         id obj = [colorDict objectForKey:stripKey];
829         if (obj) return [obj intValue];
831         // The key was not in the dictionary; is it perhaps of the form
832         // #rrggbb?
834         if ([stripKey length] > 1 && [stripKey characterAtIndex:0] == '#') {
835             NSScanner *scanner = [NSScanner scannerWithString:stripKey];
836             [scanner setScanLocation:1];
837             unsigned hex = 0;
838             if ([scanner scanHexInt:&hex]) {
839                 return (int)hex;
840             }
841         }
842     }
844     NSLog(@"WARNING: No color with key %@ found.", stripKey);
845     return INVALCOLOR;
848 - (oneway void)processInput:(int)msgid data:(in NSData *)data
850     [lastFlushDate release];
851     lastFlushDate = [[NSDate date] retain];
853     // HACK! A focus message might get lost, but whenever we get here the GUI
854     // is in focus.
855     if (!gui.in_focus && GotFocusMsgID != msgid && LostFocusMsgID != msgid)
856         gui_focus_change(TRUE);
858     [self handleMessage:msgid data:data];
859     inputReceived = YES;
862 - (oneway void)processInputAndData:(in NSArray *)messages
864     unsigned i, count = [messages count];
865     if (count % 2) {
866         NSLog(@"WARNING: [messages count] is odd in %s", _cmd);
867         return;
868     }
870     [lastFlushDate release];
871     lastFlushDate = [[NSDate date] retain];
873     for (i = 0; i < count; i += 2) {
874         int msgid = [[messages objectAtIndex:i] intValue];
875         id data = [messages objectAtIndex:i+1];
876         if ([data isEqual:[NSNull null]])
877             data = nil;
879         [self handleMessage:msgid data:data];
880     }
882     // HACK! A focus message might get lost, but whenever we get here the GUI
883     // is in focus.
884     if (!gui.in_focus)
885         gui_focus_change(TRUE);
887     inputReceived = YES;
890 - (BOOL)checkForModifiedBuffers
892     buf_T *buf;
893     for (buf = firstbuf; buf != NULL; buf = buf->b_next) {
894         if (bufIsChanged(buf)) {
895             return YES;
896         }
897     }
899     return NO;
902 - (BOOL)starRegisterToPasteboard:(byref NSPasteboard *)pboard
904     if (VIsual_active && (State & NORMAL) && clip_star.available) {
905         // If there is no pasteboard, return YES to indicate that there is text
906         // to copy.
907         if (!pboard)
908             return YES;
910         clip_copy_selection();
912         // Get the text to put on the pasteboard.
913         long_u len = 0; char_u *str = 0;
914         int type = clip_convert_selection(&str, &len, &clip_star);
915         if (type < 0)
916             return NO;
917         
918         NSString *string = [[NSString alloc]
919             initWithBytes:str length:len encoding:NSUTF8StringEncoding];
921         NSArray *types = [NSArray arrayWithObject:NSStringPboardType];
922         [pboard declareTypes:types owner:nil];
923         BOOL ok = [pboard setString:string forType:NSStringPboardType];
924     
925         [string release];
926         vim_free(str);
928         return ok;
929     }
931     return NO;
934 @end // MMBackend
938 @implementation MMBackend (Private)
940 - (void)handleMessage:(int)msgid data:(NSData *)data
942     if (InsertTextMsgID == msgid) {
943         if (!data) return;
944         NSString *key = [[NSString alloc] initWithData:data
945                                               encoding:NSUTF8StringEncoding];
946         char_u *str = (char_u*)[key UTF8String];
947         int i, len = [key lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
949 #if MM_ENABLE_CONV
950         char_u *conv_str = NULL;
951         if (input_conv.vc_type != CONV_NONE) {
952             conv_str = string_convert(&input_conv, str, &len);
953             if (conv_str)
954                 str = conv_str;
955         }
956 #endif
958         for (i = 0; i < len; ++i) {
959             add_to_input_buf(str+i, 1);
960             if (CSI == str[i]) {
961                 // NOTE: If the converted string contains the byte CSI, then it
962                 // must be followed by the bytes KS_EXTRA, KE_CSI or things
963                 // won't work.
964                 static char_u extra[2] = { KS_EXTRA, KE_CSI };
965                 add_to_input_buf(extra, 2);
966             }
967         }
969 #if MM_ENABLE_CONV
970         if (conv_str)
971             vim_free(conv_str);
972 #endif
973         [key release];
974     } else if (KeyDownMsgID == msgid || CmdKeyMsgID == msgid) {
975         if (!data) return;
976         const void *bytes = [data bytes];
977         int mods = *((int*)bytes);  bytes += sizeof(int);
978         int len = *((int*)bytes);  bytes += sizeof(int);
979         NSString *key = [[NSString alloc] initWithBytes:bytes length:len
980                                               encoding:NSUTF8StringEncoding];
981         mods = eventModifierFlagsToVimModMask(mods);
983         [self handleKeyDown:key modifiers:mods];
985         [key release];
986     } else if (SelectTabMsgID == msgid) {
987         if (!data) return;
988         const void *bytes = [data bytes];
989         int idx = *((int*)bytes) + 1;
990         //NSLog(@"Selecting tab %d", idx);
991         send_tabline_event(idx);
992     } else if (CloseTabMsgID == msgid) {
993         if (!data) return;
994         const void *bytes = [data bytes];
995         int idx = *((int*)bytes) + 1;
996         //NSLog(@"Closing tab %d", idx);
997         send_tabline_menu_event(idx, TABLINE_MENU_CLOSE);
998     } else if (AddNewTabMsgID == msgid) {
999         //NSLog(@"Adding new tab");
1000         send_tabline_menu_event(0, TABLINE_MENU_NEW);
1001     } else if (DraggedTabMsgID == msgid) {
1002         if (!data) return;
1003         const void *bytes = [data bytes];
1004         // NOTE! The destination index is 0 based, so do not add 1 to make it 1
1005         // based.
1006         int idx = *((int*)bytes);
1008         tabpage_move(idx);
1009     } else if (ScrollWheelMsgID == msgid) {
1010         if (!data) return;
1011         const void *bytes = [data bytes];
1013         int row = *((int*)bytes);  bytes += sizeof(int);
1014         int col = *((int*)bytes);  bytes += sizeof(int);
1015         int flags = *((int*)bytes);  bytes += sizeof(int);
1016         float dy = *((float*)bytes);  bytes += sizeof(float);
1018         int button = MOUSE_5;
1019         if (dy > 0) button = MOUSE_4;
1021         flags = eventModifierFlagsToVimMouseModMask(flags);
1023         gui_send_mouse_event(button, col, row, NO, flags);
1024     } else if (MouseDownMsgID == msgid) {
1025         if (!data) return;
1026         const void *bytes = [data bytes];
1028         int row = *((int*)bytes);  bytes += sizeof(int);
1029         int col = *((int*)bytes);  bytes += sizeof(int);
1030         int button = *((int*)bytes);  bytes += sizeof(int);
1031         int flags = *((int*)bytes);  bytes += sizeof(int);
1032         int count = *((int*)bytes);  bytes += sizeof(int);
1034         button = eventButtonNumberToVimMouseButton(button);
1035         flags = eventModifierFlagsToVimMouseModMask(flags);
1037         gui_send_mouse_event(button, col, row, 0 != count, flags);
1038     } else if (MouseUpMsgID == msgid) {
1039         if (!data) return;
1040         const void *bytes = [data bytes];
1042         int row = *((int*)bytes);  bytes += sizeof(int);
1043         int col = *((int*)bytes);  bytes += sizeof(int);
1044         int flags = *((int*)bytes);  bytes += sizeof(int);
1046         flags = eventModifierFlagsToVimMouseModMask(flags);
1048         gui_send_mouse_event(MOUSE_RELEASE, col, row, NO, flags);
1049     } else if (MouseDraggedMsgID == msgid) {
1050         if (!data) return;
1051         const void *bytes = [data bytes];
1053         int row = *((int*)bytes);  bytes += sizeof(int);
1054         int col = *((int*)bytes);  bytes += sizeof(int);
1055         int flags = *((int*)bytes);  bytes += sizeof(int);
1057         flags = eventModifierFlagsToVimMouseModMask(flags);
1059         gui_send_mouse_event(MOUSE_DRAG, col, row, NO, flags);
1060     } else if (SetTextDimensionsMsgID == msgid) {
1061         if (!data) return;
1062         const void *bytes = [data bytes];
1063         int rows = *((int*)bytes);  bytes += sizeof(int);
1064         int cols = *((int*)bytes);  bytes += sizeof(int);
1066         // NOTE! Vim doesn't call gui_mch_set_shellsize() after
1067         // gui_resize_shell(), so we have to manually set the rows and columns
1068         // here.  (MacVim doesn't change the rows and columns to avoid
1069         // inconsistent states between Vim and MacVim.)
1070         [self setRows:rows columns:cols];
1072         //NSLog(@"[VimTask] Resizing shell to %dx%d.", cols, rows);
1073         gui_resize_shell(cols, rows);
1074     } else if (ExecuteMenuMsgID == msgid) {
1075         if (!data) return;
1076         const void *bytes = [data bytes];
1077         int tag = *((int*)bytes);  bytes += sizeof(int);
1079         vimmenu_T *menu = (vimmenu_T*)tag;
1080         // TODO!  Make sure 'menu' is a valid menu pointer!
1081         if (menu) {
1082             gui_menu_cb(menu);
1083         }
1084     } else if (ToggleToolbarMsgID == msgid) {
1085         char_u go[sizeof(GO_ALL)+2];
1086         char_u *p;
1087         int len;
1089         STRCPY(go, p_go);
1090         p = vim_strchr(go, GO_TOOLBAR);
1091         len = STRLEN(go);
1093         if (p != NULL) {
1094             char_u *end = go + len;
1095             while (p < end) {
1096                 p[0] = p[1];
1097                 ++p;
1098             }
1099         } else {
1100             go[len] = GO_TOOLBAR;
1101             go[len+1] = NUL;
1102         }
1104         set_option_value((char_u*)"guioptions", 0, go, 0);
1106         // Force screen redraw (does it have to be this complicated?).
1107         redraw_all_later(CLEAR);
1108         update_screen(NOT_VALID);
1109         setcursor();
1110         out_flush();
1111         gui_update_cursor(FALSE, FALSE);
1112         gui_mch_flush();
1113     } else if (ScrollbarEventMsgID == msgid) {
1114         if (!data) return;
1115         const void *bytes = [data bytes];
1116         long ident = *((long*)bytes);  bytes += sizeof(long);
1117         int hitPart = *((int*)bytes);  bytes += sizeof(int);
1118         float fval = *((float*)bytes);  bytes += sizeof(float);
1119         scrollbar_T *sb = gui_find_scrollbar(ident);
1121         if (sb) {
1122             scrollbar_T *sb_info = sb->wp ? &sb->wp->w_scrollbars[0] : sb;
1123             long value = sb_info->value;
1124             long size = sb_info->size;
1125             long max = sb_info->max;
1126             BOOL isStillDragging = NO;
1127             BOOL updateKnob = YES;
1129             switch (hitPart) {
1130             case NSScrollerDecrementPage:
1131                 value -= (size > 2 ? size - 2 : 1);
1132                 break;
1133             case NSScrollerIncrementPage:
1134                 value += (size > 2 ? size - 2 : 1);
1135                 break;
1136             case NSScrollerDecrementLine:
1137                 --value;
1138                 break;
1139             case NSScrollerIncrementLine:
1140                 ++value;
1141                 break;
1142             case NSScrollerKnob:
1143                 isStillDragging = YES;
1144                 // fall through ...
1145             case NSScrollerKnobSlot:
1146                 value = (long)(fval * (max - size + 1));
1147                 // fall through ...
1148             default:
1149                 updateKnob = NO;
1150                 break;
1151             }
1153             //NSLog(@"value %d -> %d", sb_info->value, value);
1154             gui_drag_scrollbar(sb, value, isStillDragging);
1156             if (updateKnob) {
1157                 // Dragging the knob or option+clicking automatically updates
1158                 // the knob position (on the actual NSScroller), so we only
1159                 // need to set the knob position in the other cases.
1160                 if (sb->wp) {
1161                     // Update both the left&right vertical scrollbars.
1162                     long identLeft = sb->wp->w_scrollbars[SBAR_LEFT].ident;
1163                     long identRight = sb->wp->w_scrollbars[SBAR_RIGHT].ident;
1164                     [self setScrollbarThumbValue:value size:size max:max
1165                                       identifier:identLeft];
1166                     [self setScrollbarThumbValue:value size:size max:max
1167                                       identifier:identRight];
1168                 } else {
1169                     // Update the horizontal scrollbar.
1170                     [self setScrollbarThumbValue:value size:size max:max
1171                                       identifier:ident];
1172                 }
1173             }
1174         }
1175     } else if (SetFontMsgID == msgid) {
1176         if (!data) return;
1177         const void *bytes = [data bytes];
1178         float pointSize = *((float*)bytes);  bytes += sizeof(float);
1179         //unsigned len = *((unsigned*)bytes);  bytes += sizeof(unsigned);
1180         bytes += sizeof(unsigned);  // len not used
1182         NSMutableString *name = [NSMutableString stringWithUTF8String:bytes];
1183         [name appendString:[NSString stringWithFormat:@":h%.2f", pointSize]];
1185         set_option_value((char_u*)"gfn", 0, (char_u*)[name UTF8String], 0);
1187         // Force screen redraw (does it have to be this complicated?).
1188         redraw_all_later(CLEAR);
1189         update_screen(NOT_VALID);
1190         setcursor();
1191         out_flush();
1192         gui_update_cursor(FALSE, FALSE);
1193         gui_mch_flush();
1194     } else if (VimShouldCloseMsgID == msgid) {
1195         gui_shell_closed();
1196     } else if (DropFilesMsgID == msgid) {
1197 #ifdef FEAT_DND
1198         const void *bytes = [data bytes];
1199         int n = *((int*)bytes);  bytes += sizeof(int);
1201 #if 0
1202         int row = *((int*)bytes);  bytes += sizeof(int);
1203         int col = *((int*)bytes);  bytes += sizeof(int);
1205         char_u **fnames = (char_u **)alloc(n * sizeof(char_u *));
1206         if (fnames) {
1207             const void *end = [data bytes] + [data length];
1208             int i = 0;
1209             while (bytes < end && i < n) {
1210                 int len = *((int*)bytes);  bytes += sizeof(int);
1211                 fnames[i++] = vim_strnsave((char_u*)bytes, len);
1212                 bytes += len;
1213             }
1215             // NOTE!  This function will free 'fnames'.
1216             gui_handle_drop(col, row, 0, fnames, i < n ? i : n);
1217         }
1218 #else
1219         // HACK!  I'm not sure how to get Vim to open a list of files in tabs,
1220         // so instead I create a ':tab drop' command with all the files to open
1221         // and execute it.
1222         NSMutableString *cmd = (n > 1)
1223                 ? [NSMutableString stringWithString:@":tab drop"]
1224                 : [NSMutableString stringWithString:@":drop"];
1226         const void *end = [data bytes] + [data length];
1227         int i;
1228         for (i = 0; i < n && bytes < end; ++i) {
1229             int len = *((int*)bytes);  bytes += sizeof(int);
1230             NSMutableString *file =
1231                     [NSMutableString stringWithUTF8String:bytes];
1232             [file replaceOccurrencesOfString:@" "
1233                                   withString:@"\\ "
1234                                      options:0
1235                                        range:NSMakeRange(0, [file length])];
1236             bytes += len;
1238             [cmd appendString:@" "];
1239             [cmd appendString:file];
1240         }
1242         // By going to the last tabpage we ensure that the new tabs will appear
1243         // last (if this call is left out, the taborder becomes messy).
1244         goto_tabpage(9999);
1246         do_cmdline_cmd((char_u*)[cmd UTF8String]);
1248         // Force screen redraw (does it have to be this complicated?).
1249         // (This code was taken from the end of gui_handle_drop().)
1250         update_screen(NOT_VALID);
1251         setcursor();
1252         out_flush();
1253         gui_update_cursor(FALSE, FALSE);
1254         gui_mch_flush();
1255 #endif
1256 #endif // FEAT_DND
1257     } else if (DropStringMsgID == msgid) {
1258 #ifdef FEAT_DND
1259         char_u  dropkey[3] = { CSI, KS_EXTRA, (char_u)KE_DROP };
1260         const void *bytes = [data bytes];
1261         int len = *((int*)bytes);  bytes += sizeof(int);
1262         NSMutableString *string = [NSMutableString stringWithUTF8String:bytes];
1264         // Replace unrecognized end-of-line sequences with \x0a (line feed).
1265         NSRange range = { 0, [string length] };
1266         unsigned n = [string replaceOccurrencesOfString:@"\x0d\x0a"
1267                                              withString:@"\x0a" options:0
1268                                                   range:range];
1269         if (0 == n) {
1270             n = [string replaceOccurrencesOfString:@"\x0d" withString:@"\x0a"
1271                                            options:0 range:range];
1272         }
1274         len = [string lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
1275         dnd_yank_drag_data((char_u*)[string UTF8String], len);
1276         add_to_input_buf(dropkey, sizeof(dropkey));
1277 #endif // FEAT_DND
1278     } else if (GotFocusMsgID == msgid) {
1279         if (!gui.in_focus)
1280             gui_focus_change(YES);
1281     } else if (LostFocusMsgID == msgid) {
1282         if (gui.in_focus)
1283             gui_focus_change(NO);
1284     } else if (MouseMovedMsgID == msgid) {
1285         const void *bytes = [data bytes];
1286         int row = *((int*)bytes);  bytes += sizeof(int);
1287         int col = *((int*)bytes);  bytes += sizeof(int);
1289         gui_mouse_moved(col, row);
1290     } else if (SetMouseShapeMsgID == msgid) {
1291         const void *bytes = [data bytes];
1292         int shape = *((int*)bytes);  bytes += sizeof(int);
1293         update_mouseshape(shape);
1294     } else {
1295         NSLog(@"WARNING: Unknown message received (msgid=%d)", msgid);
1296     }
1299 + (NSDictionary *)specialKeys
1301     static NSDictionary *specialKeys = nil;
1303     if (!specialKeys) {
1304         NSBundle *mainBundle = [NSBundle mainBundle];
1305         NSString *path = [mainBundle pathForResource:@"SpecialKeys"
1306                                               ofType:@"plist"];
1307         specialKeys = [[NSDictionary alloc] initWithContentsOfFile:path];
1308     }
1310     return specialKeys;
1313 - (void)handleKeyDown:(NSString *)key modifiers:(int)mods
1315     char_u special[3];
1316     char_u modChars[3];
1317     char_u *chars = 0;
1318     int length = 0;
1320     // Special keys (arrow keys, function keys, etc.) are stored in a plist so
1321     // that new keys can easily be added.
1322     NSString *specialString = [[MMBackend specialKeys]
1323             objectForKey:key];
1324     if (specialString && [specialString length] > 1) {
1325         //NSLog(@"special key: %@", specialString);
1326         int ikey = TO_SPECIAL([specialString characterAtIndex:0],
1327                 [specialString characterAtIndex:1]);
1329         ikey = simplify_key(ikey, &mods);
1330         if (ikey == CSI)
1331             ikey = K_CSI;
1333         special[0] = CSI;
1334         special[1] = K_SECOND(ikey);
1335         special[2] = K_THIRD(ikey);
1337         chars = special;
1338         length = 3;
1339     } else if ([key length] > 0) {
1340         chars = (char_u*)[key UTF8String];
1341         length = [key lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
1342         unichar c = [key characterAtIndex:0];
1344         //NSLog(@"non-special: %@ (hex=%x, mods=%d)", key,
1345         //        [key characterAtIndex:0], mods);
1347         if (length == 1 && ((c == Ctrl_C && ctrl_c_interrupts)
1348                 || (c == intr_char && intr_char != Ctrl_C))) {
1349             trash_input_buf();
1350             got_int = TRUE;
1351         }
1353         // HACK!  In most circumstances the Ctrl and Shift modifiers should be
1354         // cleared since they are already added to the key by the AppKit.
1355         // Unfortunately, the only way to deal with when to clear the modifiers
1356         // or not seems to be to have hard-wired rules like this.
1357         if ( !((' ' == c) || (0xa0 == c) || (mods & MOD_MASK_CMD)) ) {
1358             mods &= ~MOD_MASK_SHIFT;
1359             mods &= ~MOD_MASK_CTRL;
1360             //NSLog(@"clear shift ctrl");
1361         }
1363         // HACK!  All Option+key presses go via 'insert text' messages, except
1364         // for <M-Space>.  If the Alt flag is not cleared for <M-Space> it does
1365         // not work to map to it.
1366         if (0xa0 == c && !(mods & MOD_MASK_CMD)) {
1367             //NSLog(@"clear alt");
1368             mods &= ~MOD_MASK_ALT;
1369         }
1370     }
1372     if (chars && length > 0) {
1373         if (mods) {
1374             //NSLog(@"adding mods: %d", mods);
1375             modChars[0] = CSI;
1376             modChars[1] = KS_MODIFIER;
1377             modChars[2] = mods;
1378             add_to_input_buf(modChars, 3);
1379         }
1381         //NSLog(@"add to input buf: 0x%x", chars[0]);
1382         // TODO: Check for CSI bytes?
1383         add_to_input_buf(chars, length);
1384     }
1387 - (void)queueMessage:(int)msgid data:(NSData *)data
1389     [queue addObject:[NSData dataWithBytes:&msgid length:sizeof(int)]];
1390     if (data)
1391         [queue addObject:data];
1392     else
1393         [queue addObject:[NSData data]];
1396 - (void)connectionDidDie:(NSNotification *)notification
1398     // If the main connection to MacVim is lost this means that MacVim was
1399     // either quit (by the user chosing Quit on the MacVim menu), or it has
1400     // crashed.  In either case our only option is to quit now.
1401     // TODO: Write backup file?
1403     //NSLog(@"A Vim process lots its connection to MacVim; quitting.");
1404     getout(0);
1407 - (void)blinkTimerFired:(NSTimer *)timer
1409     NSTimeInterval timeInterval = 0;
1411     [blinkTimer release];
1412     blinkTimer = nil;
1414     if (MMBlinkStateOn == blinkState) {
1415         gui_undraw_cursor();
1416         blinkState = MMBlinkStateOff;
1417         timeInterval = blinkOffInterval;
1418     } else if (MMBlinkStateOff == blinkState) {
1419         gui_update_cursor(TRUE, FALSE);
1420         blinkState = MMBlinkStateOn;
1421         timeInterval = blinkOnInterval;
1422     }
1424     if (timeInterval > 0) {
1425         blinkTimer = 
1426             [[NSTimer scheduledTimerWithTimeInterval:timeInterval target:self
1427                                             selector:@selector(blinkTimerFired:)
1428                                             userInfo:nil repeats:NO] retain];
1429         [self flushQueue:YES];
1430     }
1433 @end // MMBackend (Private)
1438 static int eventModifierFlagsToVimModMask(int modifierFlags)
1440     int modMask = 0;
1442     if (modifierFlags & NSShiftKeyMask)
1443         modMask |= MOD_MASK_SHIFT;
1444     if (modifierFlags & NSControlKeyMask)
1445         modMask |= MOD_MASK_CTRL;
1446     if (modifierFlags & NSAlternateKeyMask)
1447         modMask |= MOD_MASK_ALT;
1448     if (modifierFlags & NSCommandKeyMask)
1449         modMask |= MOD_MASK_CMD;
1451     return modMask;
1454 static int vimModMaskToEventModifierFlags(int mods)
1456     int flags = 0;
1458     if (mods & MOD_MASK_SHIFT)
1459         flags |= NSShiftKeyMask;
1460     if (mods & MOD_MASK_CTRL)
1461         flags |= NSControlKeyMask;
1462     if (mods & MOD_MASK_ALT)
1463         flags |= NSAlternateKeyMask;
1464     if (mods & MOD_MASK_CMD)
1465         flags |= NSCommandKeyMask;
1467     return flags;
1470 static int eventModifierFlagsToVimMouseModMask(int modifierFlags)
1472     int modMask = 0;
1474     if (modifierFlags & NSShiftKeyMask)
1475         modMask |= MOUSE_SHIFT;
1476     if (modifierFlags & NSControlKeyMask)
1477         modMask |= MOUSE_CTRL;
1478     if (modifierFlags & NSAlternateKeyMask)
1479         modMask |= MOUSE_ALT;
1481     return modMask;
1484 static int eventButtonNumberToVimMouseButton(int buttonNumber)
1486     static int mouseButton[] = { MOUSE_LEFT, MOUSE_RIGHT, MOUSE_MIDDLE,
1487             MOUSE_X1, MOUSE_X2 };
1489     return mouseButton[buttonNumber < 5 ? buttonNumber : 0];
1492 static int specialKeyToNSKey(int key)
1494     if (!IS_SPECIAL(key))
1495         return key;
1497     static struct {
1498         int special;
1499         int nskey;
1500     } sp2ns[] = {
1501         { K_UP, NSUpArrowFunctionKey },
1502         { K_DOWN, NSDownArrowFunctionKey },
1503         { K_LEFT, NSLeftArrowFunctionKey },
1504         { K_RIGHT, NSRightArrowFunctionKey },
1505         { K_F1, NSF1FunctionKey },
1506         { K_F2, NSF2FunctionKey },
1507         { K_F3, NSF3FunctionKey },
1508         { K_F4, NSF4FunctionKey },
1509         { K_F5, NSF5FunctionKey },
1510         { K_F6, NSF6FunctionKey },
1511         { K_F7, NSF7FunctionKey },
1512         { K_F8, NSF8FunctionKey },
1513         { K_F9, NSF9FunctionKey },
1514         { K_F10, NSF10FunctionKey },
1515         { K_F11, NSF11FunctionKey },
1516         { K_F12, NSF12FunctionKey },
1517         { K_F13, NSF13FunctionKey },
1518         { K_F14, NSF14FunctionKey },
1519         { K_F15, NSF15FunctionKey },
1520         { K_F16, NSF16FunctionKey },
1521         { K_F17, NSF17FunctionKey },
1522         { K_F18, NSF18FunctionKey },
1523         { K_F19, NSF19FunctionKey },
1524         { K_F20, NSF20FunctionKey },
1525         { K_F21, NSF21FunctionKey },
1526         { K_F22, NSF22FunctionKey },
1527         { K_F23, NSF23FunctionKey },
1528         { K_F24, NSF24FunctionKey },
1529         { K_F25, NSF25FunctionKey },
1530         { K_F26, NSF26FunctionKey },
1531         { K_F27, NSF27FunctionKey },
1532         { K_F28, NSF28FunctionKey },
1533         { K_F29, NSF29FunctionKey },
1534         { K_F30, NSF30FunctionKey },
1535         { K_F31, NSF31FunctionKey },
1536         { K_F32, NSF32FunctionKey },
1537         { K_F33, NSF33FunctionKey },
1538         { K_F34, NSF34FunctionKey },
1539         { K_F35, NSF35FunctionKey },
1540         { K_DEL, NSBackspaceCharacter },
1541         { K_BS, NSDeleteCharacter },
1542         { K_HOME, NSHomeFunctionKey },
1543         { K_END, NSEndFunctionKey },
1544         { K_PAGEUP, NSPageUpFunctionKey },
1545         { K_PAGEDOWN, NSPageDownFunctionKey }
1546     };
1548     int i;
1549     for (i = 0; i < sizeof(sp2ns)/sizeof(sp2ns[0]); ++i) {
1550         if (sp2ns[i].special == key)
1551             return sp2ns[i].nskey;
1552     }
1554     return 0;