- Added possibility to look up system colors
[MacVim/jjgod.git] / MMBackend.m
blob4dfdbc9cbdb41473687cefe298b1078aaf49a8e1
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];
62         NSString *path = [[NSBundle mainBundle] pathForResource:@"Colors"
63                                                          ofType:@"plist"];
64         if (path) {
65             colorDict = [[NSDictionary dictionaryWithContentsOfFile:path]
66                 retain];
67         } else {
68             NSLog(@"WARNING: Could not locate Colors.plist.");
69         }
71         path = [[NSBundle mainBundle] pathForResource:@"SystemColors"
72                                                ofType:@"plist"];
73         if (path) {
74             sysColorDict = [[NSDictionary dictionaryWithContentsOfFile:path]
75                 retain];
76         } else {
77             NSLog(@"WARNING: Could not locate SystemColors.plist.");
78         }
79     }
81     return self;
84 - (void)dealloc
86     //NSLog(@"%@ %s", [self className], _cmd);
88     [[NSNotificationCenter defaultCenter] removeObserver:self];
90     [blinkTimer release];  blinkTimer = nil;
91     [queue release];  queue = nil;
92     [drawData release];  drawData = nil;
93     [frontendProxy release];  frontendProxy = nil;
94     [connection release];  connection = nil;
95     [sysColorDict release];  sysColorDict = nil;
96     [colorDict release];  colorDict = nil;
98     [super dealloc];
101 - (void)setBackgroundColor:(int)color
103     backgroundColor = color;
106 - (void)setForegroundColor:(int)color
108     foregroundColor = color;
111 - (void)setSpecialColor:(int)color
113     specialColor = color;
116 - (void)setDefaultColorsBackground:(int)bg foreground:(int)fg
118     defaultBackgroundColor = bg;
119     defaultForegroundColor = fg;
121     NSMutableData *data = [NSMutableData data];
123     [data appendBytes:&bg length:sizeof(int)];
124     [data appendBytes:&fg length:sizeof(int)];
126     [self queueMessage:SetDefaultColorsMsgID data:data];
129 - (BOOL)checkin
131     NSBundle *mainBundle = [NSBundle mainBundle];
133     // NOTE!  If the name of the connection changes here it must also be
134     // updated in MMAppController.m.
135     NSString *name = [NSString stringWithFormat:@"%@-connection",
136              [mainBundle bundleIdentifier]];
137     connection = [NSConnection connectionWithRegisteredName:name host:nil];
138     if (!connection) {
139 #if 0
140         NSString *path = [mainBundle bundlePath];
141         if (![[NSWorkspace sharedWorkspace] launchApplication:path]) {
142             NSLog(@"WARNING: Failed to launch GUI with path %@", path);
143             return NO;
144         }
145 #else
146         // HACK!  It would be preferable to launch the GUI using NSWorkspace,
147         // however I have not managed to figure out how to pass arguments using
148         // NSWorkspace.
149         //
150         // NOTE!  Using NSTask to launch the GUI has the negative side-effect
151         // that the GUI won't be activated (or raised) so there is a hack in
152         // MMWindowController which always raises the app when a new window is
153         // opened.
154         NSMutableArray *args = [NSMutableArray arrayWithObjects:
155             [NSString stringWithFormat:@"-%@", MMNoWindowKey], @"yes", nil];
156         NSString *exeName = [[mainBundle infoDictionary]
157                 objectForKey:@"CFBundleExecutable"];
158         NSString *path = [mainBundle pathForAuxiliaryExecutable:exeName];
159         if (!path) {
160             NSLog(@"ERROR: Could not find MacVim executable in bundle");
161             return NO;
162         }
164         [NSTask launchedTaskWithLaunchPath:path arguments:args];
165 #endif
167         // HACK!  The NSWorkspaceDidLaunchApplicationNotification does not work
168         // for tasks like this, so poll the mach bootstrap server until it
169         // returns a valid connection.  Also set a time-out date so that we
170         // don't get stuck doing this forever.
171         NSDate *timeOutDate = [NSDate dateWithTimeIntervalSinceNow:15];
172         while (!connection &&
173                 NSOrderedDescending == [timeOutDate compare:[NSDate date]])
174         {
175             [[NSRunLoop currentRunLoop]
176                     runMode:NSDefaultRunLoopMode
177                  beforeDate:[NSDate dateWithTimeIntervalSinceNow:1]];
179             connection = [NSConnection connectionWithRegisteredName:name
180                                                                host:nil];
181         }
183         if (!connection) {
184             NSLog(@"WARNING: Timed-out waiting for GUI to launch.");
185             return NO;
186         }
187     }
189     id proxy = [connection rootProxy];
190     [proxy setProtocolForProxy:@protocol(MMAppProtocol)];
192     [[NSNotificationCenter defaultCenter] addObserver:self
193             selector:@selector(connectionDidDie:)
194                 name:NSConnectionDidDieNotification object:connection];
196     int pid = [[NSProcessInfo processInfo] processIdentifier];
198     @try {
199         frontendProxy = [(NSDistantObject*)[proxy connectBackend:self
200                                                              pid:pid] retain];
201     }
202     @catch (NSException *e) {
203         NSLog(@"Exception caught when trying to connect backend: \"%@\"", e);
204     }
206     if (frontendProxy) {
207         [frontendProxy setProtocolForProxy:@protocol(MMAppProtocol)];
208     }
210     return connection && frontendProxy;
213 - (BOOL)openVimWindow
215     [self queueMessage:OpenVimWindowMsgID data:nil];
216     return YES;
219 - (void)clearAll
221     int type = ClearAllDrawType;
223     // Any draw commands in queue are effectively obsolete since this clearAll
224     // will negate any effect they have, therefore we may as well clear the
225     // draw queue.
226     [drawData setLength:0];
228     [drawData appendBytes:&type length:sizeof(int)];
230     [drawData appendBytes:&defaultBackgroundColor length:sizeof(int)];
233 - (void)clearBlockFromRow:(int)row1 column:(int)col1
234                     toRow:(int)row2 column:(int)col2
236     int type = ClearBlockDrawType;
238     [drawData appendBytes:&type length:sizeof(int)];
240     [drawData appendBytes:&defaultBackgroundColor length:sizeof(int)];
241     [drawData appendBytes:&row1 length:sizeof(int)];
242     [drawData appendBytes:&col1 length:sizeof(int)];
243     [drawData appendBytes:&row2 length:sizeof(int)];
244     [drawData appendBytes:&col2 length:sizeof(int)];
247 - (void)deleteLinesFromRow:(int)row count:(int)count
248               scrollBottom:(int)bottom left:(int)left right:(int)right
250     int type = DeleteLinesDrawType;
252     [drawData appendBytes:&type length:sizeof(int)];
254     [drawData appendBytes:&defaultBackgroundColor length:sizeof(int)];
255     [drawData appendBytes:&row length:sizeof(int)];
256     [drawData appendBytes:&count length:sizeof(int)];
257     [drawData appendBytes:&bottom length:sizeof(int)];
258     [drawData appendBytes:&left length:sizeof(int)];
259     [drawData appendBytes:&right length:sizeof(int)];
262 - (void)replaceString:(char*)s length:(int)len row:(int)row column:(int)col
263                 flags:(int)flags
265     if (len <= 0) return;
267     int type = ReplaceStringDrawType;
269     [drawData appendBytes:&type length:sizeof(int)];
271     [drawData appendBytes:&backgroundColor length:sizeof(int)];
272     [drawData appendBytes:&foregroundColor length:sizeof(int)];
273     [drawData appendBytes:&specialColor length:sizeof(int)];
274     [drawData appendBytes:&row length:sizeof(int)];
275     [drawData appendBytes:&col length:sizeof(int)];
276     [drawData appendBytes:&flags length:sizeof(int)];
277     [drawData appendBytes:&len length:sizeof(int)];
278     [drawData appendBytes:s length:len];
281 - (void)insertLinesFromRow:(int)row count:(int)count
282               scrollBottom:(int)bottom left:(int)left right:(int)right
284     int type = InsertLinesDrawType;
286     [drawData appendBytes:&type length:sizeof(int)];
288     [drawData appendBytes:&defaultBackgroundColor length:sizeof(int)];
289     [drawData appendBytes:&row length:sizeof(int)];
290     [drawData appendBytes:&count length:sizeof(int)];
291     [drawData appendBytes:&bottom length:sizeof(int)];
292     [drawData appendBytes:&left length:sizeof(int)];
293     [drawData appendBytes:&right length:sizeof(int)];
296 - (void)drawCursorAtRow:(int)row column:(int)col shape:(int)shape
297                fraction:(int)percent color:(int)color
299     int type = DrawCursorDrawType;
301     [drawData appendBytes:&type length:sizeof(int)];
303     [drawData appendBytes:&color length:sizeof(int)];
304     [drawData appendBytes:&row length:sizeof(int)];
305     [drawData appendBytes:&col length:sizeof(int)];
306     [drawData appendBytes:&shape length:sizeof(int)];
307     [drawData appendBytes:&percent length:sizeof(int)];
310 - (void)flushQueue:(BOOL)force
312     // NOTE! This method gets called a lot; if we were to flush every time it
313     // was called MacVim would feel unresponsive.  So there is a time out which
314     // ensures that the queue isn't flushed too often.
315     if (!force && lastFlushDate && -[lastFlushDate timeIntervalSinceNow]
316             < MMFlushTimeoutInterval)
317         return;
319     if ([drawData length] > 0) {
320         [self queueMessage:BatchDrawMsgID data:[drawData copy]];
321         [drawData setLength:0];
322     }
324     if ([queue count] > 0) {
325         @try {
326             [frontendProxy processCommandQueue:queue];
327         }
328         @catch (NSException *e) {
329             NSLog(@"Exception caught when processing command queue: \"%@\"", e);
330         }
332         [queue removeAllObjects];
334         [lastFlushDate release];
335         lastFlushDate = [[NSDate date] retain];
336     }
339 - (BOOL)waitForInput:(int)milliseconds
341     NSDate *date = milliseconds > 0 ?
342             [NSDate dateWithTimeIntervalSinceNow:.001*milliseconds] : 
343             [NSDate distantFuture];
345     [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:date];
347     // I know of no way to figure out if the run loop exited because input was
348     // found or because of a time out, so I need to manually indicate when
349     // input was received in processInput:data: and then reset it every time
350     // here.
351     BOOL yn = inputReceived;
352     inputReceived = NO;
354     return yn;
357 - (void)exit
359     // By invalidating the NSConnection the MMWindowController immediately
360     // finds out that the connection is down and as a result
361     // [MMWindowController connectionDidDie:] is invoked.
362     //NSLog(@"%@ %s", [self className], _cmd);
363     [[NSNotificationCenter defaultCenter] removeObserver:self];
364     [connection invalidate];
367 - (void)selectTab:(int)index
369     //NSLog(@"%s%d", _cmd, index);
371     index -= 1;
372     NSData *data = [NSData dataWithBytes:&index length:sizeof(int)];
373     [self queueMessage:SelectTabMsgID data:data];
376 - (void)updateTabBar
378     //NSLog(@"%s", _cmd);
380     NSMutableData *data = [NSMutableData data];
382     int idx = tabpage_index(curtab) - 1;
383     [data appendBytes:&idx length:sizeof(int)];
385     tabpage_T *tp;
386     for (tp = first_tabpage; tp != NULL; tp = tp->tp_next) {
387         // This function puts the label of the tab in the global 'NameBuff'.
388         get_tabline_label(tp, FALSE);
389         int len = strlen((char*)NameBuff);
390         if (len <= 0) continue;
392         // Count the number of windows in the tabpage.
393         //win_T *wp = tp->tp_firstwin;
394         //int wincount;
395         //for (wincount = 0; wp != NULL; wp = wp->w_next, ++wincount);
397         //[data appendBytes:&wincount length:sizeof(int)];
398         [data appendBytes:&len length:sizeof(int)];
399         [data appendBytes:NameBuff length:len];
400     }
402     [self queueMessage:UpdateTabBarMsgID data:data];
405 - (BOOL)tabBarVisible
407     return tabBarVisible;
410 - (void)showTabBar:(BOOL)enable
412     tabBarVisible = enable;
414     int msgid = enable ? ShowTabBarMsgID : HideTabBarMsgID;
415     [self queueMessage:msgid data:nil];
418 - (void)setRows:(int)rows columns:(int)cols
420     //NSLog(@"[VimTask] setRows:%d columns:%d", rows, cols);
422     int dim[] = { rows, cols };
423     NSData *data = [NSData dataWithBytes:&dim length:2*sizeof(int)];
425     [self queueMessage:SetTextDimensionsMsgID data:data];
428 - (void)setVimWindowTitle:(char *)title
430     NSMutableData *data = [NSMutableData data];
431     int len = strlen(title);
432     if (len <= 0) return;
434     [data appendBytes:&len length:sizeof(int)];
435     [data appendBytes:title length:len];
437     [self queueMessage:SetVimWindowTitleMsgID data:data];
440 - (char *)browseForFileInDirectory:(char *)dir title:(char *)title
441                             saving:(int)saving
443     //NSLog(@"browseForFileInDirectory:%s title:%s saving:%d", dir, title,
444     //        saving);
446     char_u *s = NULL;
447     NSString *ds = dir
448             ? [NSString stringWithCString:dir encoding:NSUTF8StringEncoding]
449             : nil;
450     NSString *ts = title
451             ? [NSString stringWithCString:title encoding:NSUTF8StringEncoding]
452             : nil;
453     @try {
454         [frontendProxy showSavePanelForDirectory:ds title:ts saving:saving];
456         // Wait until a reply is sent from MMVimController.
457         [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
458                                  beforeDate:[NSDate distantFuture]];
460         if (dialogReturn && [dialogReturn isKindOfClass:[NSString class]]) {
461             s = vim_strsave((char_u*)[dialogReturn UTF8String]);
462         }
464         [dialogReturn release];  dialogReturn = nil;
465     }
466     @catch (NSException *e) {
467         NSLog(@"Exception caught when showing save panel: \"%@\"", e);
468     }
470     return (char *)s;
473 - (oneway void)setDialogReturn:(in bycopy id)obj
475     // NOTE: This is called by
476     //   - [MMVimController panelDidEnd:::], and
477     //   - [MMVimController alertDidEnd:::],
478     // to indicate that a save/open panel or alert has finished.
480     if (obj != dialogReturn) {
481         [dialogReturn release];
482         dialogReturn = [obj retain];
483     }
486 - (int)presentDialogWithType:(int)type title:(char *)title message:(char *)msg
487                      buttons:(char *)btns textField:(char *)txtfield
489     int retval = 0;
490     NSString *message = nil, *text = nil, *textFieldString = nil;
491     NSArray *buttons = nil;
492     int style = NSInformationalAlertStyle;
494     if (VIM_WARNING == type) style = NSWarningAlertStyle;
495     else if (VIM_ERROR == type) style = NSCriticalAlertStyle;
497     if (btns) {
498         NSString *btnString = [NSString stringWithUTF8String:btns];
499         buttons = [btnString componentsSeparatedByString:@"\n"];
500     }
501     if (title)
502         message = [NSString stringWithUTF8String:title];
503     if (msg) {
504         text = [NSString stringWithUTF8String:msg];
505         if (!message) {
506             // HACK! If there is a '\n\n' or '\n' sequence in the message, then
507             // make the part up to there into the title.  We only do this
508             // because Vim has lots of dialogs without a title and they look
509             // ugly that way.
510             // TODO: Fix the actual dialog texts.
511             NSRange eolRange = [text rangeOfString:@"\n\n"];
512             if (NSNotFound == eolRange.location)
513                 eolRange = [text rangeOfString:@"\n"];
514             if (NSNotFound != eolRange.location) {
515                 message = [text substringToIndex:eolRange.location];
516                 text = [text substringFromIndex:NSMaxRange(eolRange)];
517             }
518         }
519     }
520     if (txtfield)
521         textFieldString = [NSString stringWithUTF8String:txtfield];
523     @try {
524         [frontendProxy presentDialogWithStyle:style message:message
525                               informativeText:text buttonTitles:buttons
526                               textFieldString:textFieldString];
528         // Wait until a reply is sent from MMVimController.
529         [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
530                                  beforeDate:[NSDate distantFuture]];
532         if (dialogReturn && [dialogReturn isKindOfClass:[NSArray class]]
533                 && [dialogReturn count]) {
534             retval = [[dialogReturn objectAtIndex:0] intValue];
535             if (txtfield && [dialogReturn count] > 1) {
536                 NSString *retString = [dialogReturn objectAtIndex:1];
537                 vim_strncpy((char_u*)txtfield, (char_u*)[retString UTF8String],
538                         IOSIZE - 1);
539             }
540         }
542         [dialogReturn release]; dialogReturn = nil;
543     }
544     @catch (NSException *e) {
545         NSLog(@"Exception caught while showing alert dialog: \"%@\"", e);
546     }
548     return retval;
551 - (void)addMenuWithTag:(int)tag parent:(int)parentTag name:(char *)name
552                atIndex:(int)index
554     //NSLog(@"addMenuWithTag:%d parent:%d name:%s atIndex:%d", tag, parentTag,
555     //        name, index);
557     int namelen = name ? strlen(name) : 0;
558     NSMutableData *data = [NSMutableData data];
560     [data appendBytes:&tag length:sizeof(int)];
561     [data appendBytes:&parentTag length:sizeof(int)];
562     [data appendBytes:&namelen length:sizeof(int)];
563     if (namelen > 0) [data appendBytes:name length:namelen];
564     [data appendBytes:&index length:sizeof(int)];
566     [self queueMessage:AddMenuMsgID data:data];
569 - (void)addMenuItemWithTag:(int)tag parent:(int)parentTag name:(char *)name
570                        tip:(char *)tip icon:(char *)icon
571              keyEquivalent:(int)key modifiers:(int)mods
572                     action:(NSString *)action atIndex:(int)index
574     //NSLog(@"addMenuItemWithTag:%d parent:%d name:%s tip:%s atIndex:%d", tag,
575     //        parentTag, name, tip, index);
577     int namelen = name ? strlen(name) : 0;
578     int tiplen = tip ? strlen(tip) : 0;
579     int iconlen = icon ? strlen(icon) : 0;
580     int eventFlags = vimModMaskToEventModifierFlags(mods);
581     int actionlen = [action lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
582     NSMutableData *data = [NSMutableData data];
584     key = specialKeyToNSKey(key);
586     [data appendBytes:&tag length:sizeof(int)];
587     [data appendBytes:&parentTag length:sizeof(int)];
588     [data appendBytes:&namelen length:sizeof(int)];
589     if (namelen > 0) [data appendBytes:name length:namelen];
590     [data appendBytes:&tiplen length:sizeof(int)];
591     if (tiplen > 0) [data appendBytes:tip length:tiplen];
592     [data appendBytes:&iconlen length:sizeof(int)];
593     if (iconlen > 0) [data appendBytes:icon length:iconlen];
594     [data appendBytes:&actionlen length:sizeof(int)];
595     if (actionlen > 0) [data appendBytes:[action UTF8String] length:actionlen];
596     [data appendBytes:&index length:sizeof(int)];
597     [data appendBytes:&key length:sizeof(int)];
598     [data appendBytes:&eventFlags length:sizeof(int)];
600     [self queueMessage:AddMenuItemMsgID data:data];
603 - (void)removeMenuItemWithTag:(int)tag
605     NSMutableData *data = [NSMutableData data];
606     [data appendBytes:&tag length:sizeof(int)];
608     [self queueMessage:RemoveMenuItemMsgID data:data];
611 - (void)enableMenuItemWithTag:(int)tag state:(int)enabled
613     NSMutableData *data = [NSMutableData data];
615     [data appendBytes:&tag length:sizeof(int)];
616     [data appendBytes:&enabled length:sizeof(int)];
618     [self queueMessage:EnableMenuItemMsgID data:data];
621 - (void)showPopupMenuWithName:(char *)name atMouseLocation:(BOOL)mouse
623     int len = strlen(name);
624     int row = -1, col = -1;
626     if (len <= 0) return;
628     if (!mouse && curwin) {
629         row = curwin->w_wrow;
630         col = curwin->w_wcol;
631     }
633     NSMutableData *data = [NSMutableData data];
635     [data appendBytes:&row length:sizeof(int)];
636     [data appendBytes:&col length:sizeof(int)];
637     [data appendBytes:&len length:sizeof(int)];
638     [data appendBytes:name length:len];
640     [self queueMessage:ShowPopupMenuMsgID data:data];
643 - (void)showToolbar:(int)enable flags:(int)flags
645     NSMutableData *data = [NSMutableData data];
647     [data appendBytes:&enable length:sizeof(int)];
648     [data appendBytes:&flags length:sizeof(int)];
650     [self queueMessage:ShowToolbarMsgID data:data];
653 - (void)createScrollbarWithIdentifier:(long)ident type:(int)type
655     NSMutableData *data = [NSMutableData data];
657     [data appendBytes:&ident length:sizeof(long)];
658     [data appendBytes:&type length:sizeof(int)];
660     [self queueMessage:CreateScrollbarMsgID data:data];
663 - (void)destroyScrollbarWithIdentifier:(long)ident
665     NSMutableData *data = [NSMutableData data];
666     [data appendBytes:&ident length:sizeof(long)];
668     [self queueMessage:DestroyScrollbarMsgID data:data];
671 - (void)showScrollbarWithIdentifier:(long)ident state:(int)visible
673     NSMutableData *data = [NSMutableData data];
675     [data appendBytes:&ident length:sizeof(long)];
676     [data appendBytes:&visible length:sizeof(int)];
678     [self queueMessage:ShowScrollbarMsgID data:data];
681 - (void)setScrollbarPosition:(int)pos length:(int)len identifier:(long)ident
683     NSMutableData *data = [NSMutableData data];
685     [data appendBytes:&ident length:sizeof(long)];
686     [data appendBytes:&pos length:sizeof(int)];
687     [data appendBytes:&len length:sizeof(int)];
689     [self queueMessage:SetScrollbarPositionMsgID data:data];
692 - (void)setScrollbarThumbValue:(long)val size:(long)size max:(long)max
693                     identifier:(long)ident
695     float fval = max-size+1 > 0 ? (float)val/(max-size+1) : 0;
696     float prop = (float)size/(max+1);
697     if (fval < 0) fval = 0;
698     else if (fval > 1.0f) fval = 1.0f;
699     if (prop < 0) prop = 0;
700     else if (prop > 1.0f) prop = 1.0f;
702     NSMutableData *data = [NSMutableData data];
704     [data appendBytes:&ident length:sizeof(long)];
705     [data appendBytes:&fval length:sizeof(float)];
706     [data appendBytes:&prop length:sizeof(float)];
708     [self queueMessage:SetScrollbarThumbMsgID data:data];
711 - (BOOL)setFontWithName:(char *)name
713     NSString *fontName;
714     float size = 0.0f;
715     BOOL parseFailed = NO;
717     if (name) {
718         fontName = [[[NSString alloc] initWithCString:name
719                 encoding:NSUTF8StringEncoding] autorelease];
720         NSArray *components = [fontName componentsSeparatedByString:@":"];
721         if ([components count] == 2) {
722             NSString *sizeString = [components lastObject];
723             if ([sizeString length] > 0
724                     && [sizeString characterAtIndex:0] == 'h') {
725                 sizeString = [sizeString substringFromIndex:1];
726                 if ([sizeString length] > 0) {
727                     size = [sizeString floatValue];
728                     fontName = [components objectAtIndex:0];
729                 }
730             } else {
731                 parseFailed = YES;
732             }
733         } else if ([components count] > 2) {
734             parseFailed = YES;
735         }
736     } else {
737         fontName = [[NSFont userFixedPitchFontOfSize:0] displayName];
738     }
740     if (!parseFailed && [fontName length] > 0) {
741         if (size < 6 || size > 100) {
742             // Font size 0.0 tells NSFont to use the 'user default size'.
743             size = 0.0f;
744         }
746         NSFont *font = [NSFont fontWithName:fontName size:size];
747         if (font) {
748             //NSLog(@"Setting font '%@' of size %.2f", fontName, size);
749             int len = [fontName
750                     lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
751             if (len > 0) {
752                 NSMutableData *data = [NSMutableData data];
754                 [data appendBytes:&size length:sizeof(float)];
755                 [data appendBytes:&len length:sizeof(int)];
756                 [data appendBytes:[fontName UTF8String] length:len];
758                 [self queueMessage:SetFontMsgID data:data];
759                 return YES;
760             }
761         }
762     }
764     NSLog(@"WARNING: Cannot set font with name '%@' of size %.2f",
765             fontName, size);
766     return NO;
769 - (void)executeActionWithName:(NSString *)name
771     int len = [name lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
773     if (len > 0) {
774         NSMutableData *data = [NSMutableData data];
776         [data appendBytes:&len length:sizeof(int)];
777         [data appendBytes:[name UTF8String] length:len];
779         [self queueMessage:ExecuteActionMsgID data:data];
780     }
783 - (void)setMouseShape:(int)shape
785     NSMutableData *data = [NSMutableData data];
786     [data appendBytes:&shape length:sizeof(int)];
787     [self queueMessage:SetMouseShapeMsgID data:data];
790 - (void)setBlinkWait:(int)wait on:(int)on off:(int)off
792     // Vim specifies times in milliseconds, whereas Cocoa wants them in
793     // seconds.
794     blinkWaitInterval = .001f*wait;
795     blinkOnInterval = .001f*on;
796     blinkOffInterval = .001f*off;
799 - (void)startBlink
801     if (blinkTimer) {
802         [blinkTimer invalidate];
803         [blinkTimer release];
804         blinkTimer = nil;
805     }
807     if (blinkWaitInterval > 0 && blinkOnInterval > 0 && blinkOffInterval > 0
808             && gui.in_focus) {
809         blinkState = MMBlinkStateOn;
810         blinkTimer =
811             [[NSTimer scheduledTimerWithTimeInterval:blinkWaitInterval
812                                               target:self
813                                             selector:@selector(blinkTimerFired:)
814                                             userInfo:nil repeats:NO] retain];
815         gui_update_cursor(TRUE, FALSE);
816         [self flushQueue:YES];
817     }
820 - (void)stopBlink
822     if (MMBlinkStateOff == blinkState) {
823         gui_update_cursor(TRUE, FALSE);
824         [self flushQueue:YES];
825     }
827     blinkState = MMBlinkStateNone;
830 - (int)lookupColorWithKey:(NSString *)key
832     if (!(key && [key length] > 0))
833         return INVALCOLOR;
835     NSString *stripKey = [[[[key lowercaseString]
836         stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]
837             componentsSeparatedByString:@" "]
838                componentsJoinedByString:@""];
840     if (stripKey && [stripKey length] > 0) {
841         // First of all try to lookup key in the color dictionary; note that
842         // all keys in this dictionary are lowercase with no whitespace.
843         id obj = [colorDict objectForKey:stripKey];
844         if (obj) return [obj intValue];
846         // The key was not in the dictionary; is it perhaps of the form
847         // #rrggbb?
848         if ([stripKey length] > 1 && [stripKey characterAtIndex:0] == '#') {
849             NSScanner *scanner = [NSScanner scannerWithString:stripKey];
850             [scanner setScanLocation:1];
851             unsigned hex = 0;
852             if ([scanner scanHexInt:&hex]) {
853                 return (int)hex;
854             }
855         }
857         // As a last resort, check if it is one of the system defined colors.
858         // The keys in this dictionary are also lowercase with no whitespace.
859         obj = [sysColorDict objectForKey:stripKey];
860         if (obj) {
861             NSColor *col = [NSColor performSelector:NSSelectorFromString(obj)];
862             if (col) {
863                 float r, g, b, a;
864                 col = [col colorUsingColorSpaceName:NSCalibratedRGBColorSpace];
865                 [col getRed:&r green:&g blue:&b alpha:&a];
866                 return ((int)(r*255) << 16) + ((int)(g*255) << 8)
867                     + (int)(b*255);
868             }
869         }
870     }
872     NSLog(@"WARNING: No color with key %@ found.", stripKey);
873     return INVALCOLOR;
876 - (oneway void)processInput:(int)msgid data:(in NSData *)data
878     [lastFlushDate release];
879     lastFlushDate = [[NSDate date] retain];
881     // HACK! A focus message might get lost, but whenever we get here the GUI
882     // is in focus.
883     if (!gui.in_focus && GotFocusMsgID != msgid && LostFocusMsgID != msgid)
884         gui_focus_change(TRUE);
886     [self handleMessage:msgid data:data];
887     inputReceived = YES;
890 - (oneway void)processInputAndData:(in NSArray *)messages
892     unsigned i, count = [messages count];
893     if (count % 2) {
894         NSLog(@"WARNING: [messages count] is odd in %s", _cmd);
895         return;
896     }
898     [lastFlushDate release];
899     lastFlushDate = [[NSDate date] retain];
901     for (i = 0; i < count; i += 2) {
902         int msgid = [[messages objectAtIndex:i] intValue];
903         id data = [messages objectAtIndex:i+1];
904         if ([data isEqual:[NSNull null]])
905             data = nil;
907         [self handleMessage:msgid data:data];
908     }
910     // HACK! A focus message might get lost, but whenever we get here the GUI
911     // is in focus.
912     if (!gui.in_focus)
913         gui_focus_change(TRUE);
915     inputReceived = YES;
918 - (BOOL)checkForModifiedBuffers
920     buf_T *buf;
921     for (buf = firstbuf; buf != NULL; buf = buf->b_next) {
922         if (bufIsChanged(buf)) {
923             return YES;
924         }
925     }
927     return NO;
930 - (BOOL)starRegisterToPasteboard:(byref NSPasteboard *)pboard
932     if (VIsual_active && (State & NORMAL) && clip_star.available) {
933         // If there is no pasteboard, return YES to indicate that there is text
934         // to copy.
935         if (!pboard)
936             return YES;
938         clip_copy_selection();
940         // Get the text to put on the pasteboard.
941         long_u len = 0; char_u *str = 0;
942         int type = clip_convert_selection(&str, &len, &clip_star);
943         if (type < 0)
944             return NO;
945         
946         NSString *string = [[NSString alloc]
947             initWithBytes:str length:len encoding:NSUTF8StringEncoding];
949         NSArray *types = [NSArray arrayWithObject:NSStringPboardType];
950         [pboard declareTypes:types owner:nil];
951         BOOL ok = [pboard setString:string forType:NSStringPboardType];
952     
953         [string release];
954         vim_free(str);
956         return ok;
957     }
959     return NO;
962 @end // MMBackend
966 @implementation MMBackend (Private)
968 - (void)handleMessage:(int)msgid data:(NSData *)data
970     if (InsertTextMsgID == msgid) {
971         if (!data) return;
972         NSString *key = [[NSString alloc] initWithData:data
973                                               encoding:NSUTF8StringEncoding];
974         char_u *str = (char_u*)[key UTF8String];
975         int i, len = [key lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
977 #if MM_ENABLE_CONV
978         char_u *conv_str = NULL;
979         if (input_conv.vc_type != CONV_NONE) {
980             conv_str = string_convert(&input_conv, str, &len);
981             if (conv_str)
982                 str = conv_str;
983         }
984 #endif
986         for (i = 0; i < len; ++i) {
987             add_to_input_buf(str+i, 1);
988             if (CSI == str[i]) {
989                 // NOTE: If the converted string contains the byte CSI, then it
990                 // must be followed by the bytes KS_EXTRA, KE_CSI or things
991                 // won't work.
992                 static char_u extra[2] = { KS_EXTRA, KE_CSI };
993                 add_to_input_buf(extra, 2);
994             }
995         }
997 #if MM_ENABLE_CONV
998         if (conv_str)
999             vim_free(conv_str);
1000 #endif
1001         [key release];
1002     } else if (KeyDownMsgID == msgid || CmdKeyMsgID == msgid) {
1003         if (!data) return;
1004         const void *bytes = [data bytes];
1005         int mods = *((int*)bytes);  bytes += sizeof(int);
1006         int len = *((int*)bytes);  bytes += sizeof(int);
1007         NSString *key = [[NSString alloc] initWithBytes:bytes length:len
1008                                               encoding:NSUTF8StringEncoding];
1009         mods = eventModifierFlagsToVimModMask(mods);
1011         [self handleKeyDown:key modifiers:mods];
1013         [key release];
1014     } else if (SelectTabMsgID == msgid) {
1015         if (!data) return;
1016         const void *bytes = [data bytes];
1017         int idx = *((int*)bytes) + 1;
1018         //NSLog(@"Selecting tab %d", idx);
1019         send_tabline_event(idx);
1020     } else if (CloseTabMsgID == msgid) {
1021         if (!data) return;
1022         const void *bytes = [data bytes];
1023         int idx = *((int*)bytes) + 1;
1024         //NSLog(@"Closing tab %d", idx);
1025         send_tabline_menu_event(idx, TABLINE_MENU_CLOSE);
1026     } else if (AddNewTabMsgID == msgid) {
1027         //NSLog(@"Adding new tab");
1028         send_tabline_menu_event(0, TABLINE_MENU_NEW);
1029     } else if (DraggedTabMsgID == msgid) {
1030         if (!data) return;
1031         const void *bytes = [data bytes];
1032         // NOTE! The destination index is 0 based, so do not add 1 to make it 1
1033         // based.
1034         int idx = *((int*)bytes);
1036         tabpage_move(idx);
1037     } else if (ScrollWheelMsgID == msgid) {
1038         if (!data) return;
1039         const void *bytes = [data bytes];
1041         int row = *((int*)bytes);  bytes += sizeof(int);
1042         int col = *((int*)bytes);  bytes += sizeof(int);
1043         int flags = *((int*)bytes);  bytes += sizeof(int);
1044         float dy = *((float*)bytes);  bytes += sizeof(float);
1046         int button = MOUSE_5;
1047         if (dy > 0) button = MOUSE_4;
1049         flags = eventModifierFlagsToVimMouseModMask(flags);
1051         gui_send_mouse_event(button, col, row, NO, flags);
1052     } else if (MouseDownMsgID == msgid) {
1053         if (!data) return;
1054         const void *bytes = [data bytes];
1056         int row = *((int*)bytes);  bytes += sizeof(int);
1057         int col = *((int*)bytes);  bytes += sizeof(int);
1058         int button = *((int*)bytes);  bytes += sizeof(int);
1059         int flags = *((int*)bytes);  bytes += sizeof(int);
1060         int count = *((int*)bytes);  bytes += sizeof(int);
1062         button = eventButtonNumberToVimMouseButton(button);
1063         flags = eventModifierFlagsToVimMouseModMask(flags);
1065         gui_send_mouse_event(button, col, row, 0 != count, flags);
1066     } else if (MouseUpMsgID == msgid) {
1067         if (!data) return;
1068         const void *bytes = [data bytes];
1070         int row = *((int*)bytes);  bytes += sizeof(int);
1071         int col = *((int*)bytes);  bytes += sizeof(int);
1072         int flags = *((int*)bytes);  bytes += sizeof(int);
1074         flags = eventModifierFlagsToVimMouseModMask(flags);
1076         gui_send_mouse_event(MOUSE_RELEASE, col, row, NO, flags);
1077     } else if (MouseDraggedMsgID == msgid) {
1078         if (!data) return;
1079         const void *bytes = [data bytes];
1081         int row = *((int*)bytes);  bytes += sizeof(int);
1082         int col = *((int*)bytes);  bytes += sizeof(int);
1083         int flags = *((int*)bytes);  bytes += sizeof(int);
1085         flags = eventModifierFlagsToVimMouseModMask(flags);
1087         gui_send_mouse_event(MOUSE_DRAG, col, row, NO, flags);
1088     } else if (SetTextDimensionsMsgID == msgid) {
1089         if (!data) return;
1090         const void *bytes = [data bytes];
1091         int rows = *((int*)bytes);  bytes += sizeof(int);
1092         int cols = *((int*)bytes);  bytes += sizeof(int);
1094         // NOTE! Vim doesn't call gui_mch_set_shellsize() after
1095         // gui_resize_shell(), so we have to manually set the rows and columns
1096         // here.  (MacVim doesn't change the rows and columns to avoid
1097         // inconsistent states between Vim and MacVim.)
1098         [self setRows:rows columns:cols];
1100         //NSLog(@"[VimTask] Resizing shell to %dx%d.", cols, rows);
1101         gui_resize_shell(cols, rows);
1102     } else if (ExecuteMenuMsgID == msgid) {
1103         if (!data) return;
1104         const void *bytes = [data bytes];
1105         int tag = *((int*)bytes);  bytes += sizeof(int);
1107         vimmenu_T *menu = (vimmenu_T*)tag;
1108         // TODO!  Make sure 'menu' is a valid menu pointer!
1109         if (menu) {
1110             gui_menu_cb(menu);
1111         }
1112     } else if (ToggleToolbarMsgID == msgid) {
1113         char_u go[sizeof(GO_ALL)+2];
1114         char_u *p;
1115         int len;
1117         STRCPY(go, p_go);
1118         p = vim_strchr(go, GO_TOOLBAR);
1119         len = STRLEN(go);
1121         if (p != NULL) {
1122             char_u *end = go + len;
1123             while (p < end) {
1124                 p[0] = p[1];
1125                 ++p;
1126             }
1127         } else {
1128             go[len] = GO_TOOLBAR;
1129             go[len+1] = NUL;
1130         }
1132         set_option_value((char_u*)"guioptions", 0, go, 0);
1134         // Force screen redraw (does it have to be this complicated?).
1135         redraw_all_later(CLEAR);
1136         update_screen(NOT_VALID);
1137         setcursor();
1138         out_flush();
1139         gui_update_cursor(FALSE, FALSE);
1140         gui_mch_flush();
1141     } else if (ScrollbarEventMsgID == msgid) {
1142         if (!data) return;
1143         const void *bytes = [data bytes];
1144         long ident = *((long*)bytes);  bytes += sizeof(long);
1145         int hitPart = *((int*)bytes);  bytes += sizeof(int);
1146         float fval = *((float*)bytes);  bytes += sizeof(float);
1147         scrollbar_T *sb = gui_find_scrollbar(ident);
1149         if (sb) {
1150             scrollbar_T *sb_info = sb->wp ? &sb->wp->w_scrollbars[0] : sb;
1151             long value = sb_info->value;
1152             long size = sb_info->size;
1153             long max = sb_info->max;
1154             BOOL isStillDragging = NO;
1155             BOOL updateKnob = YES;
1157             switch (hitPart) {
1158             case NSScrollerDecrementPage:
1159                 value -= (size > 2 ? size - 2 : 1);
1160                 break;
1161             case NSScrollerIncrementPage:
1162                 value += (size > 2 ? size - 2 : 1);
1163                 break;
1164             case NSScrollerDecrementLine:
1165                 --value;
1166                 break;
1167             case NSScrollerIncrementLine:
1168                 ++value;
1169                 break;
1170             case NSScrollerKnob:
1171                 isStillDragging = YES;
1172                 // fall through ...
1173             case NSScrollerKnobSlot:
1174                 value = (long)(fval * (max - size + 1));
1175                 // fall through ...
1176             default:
1177                 updateKnob = NO;
1178                 break;
1179             }
1181             //NSLog(@"value %d -> %d", sb_info->value, value);
1182             gui_drag_scrollbar(sb, value, isStillDragging);
1184             if (updateKnob) {
1185                 // Dragging the knob or option+clicking automatically updates
1186                 // the knob position (on the actual NSScroller), so we only
1187                 // need to set the knob position in the other cases.
1188                 if (sb->wp) {
1189                     // Update both the left&right vertical scrollbars.
1190                     long identLeft = sb->wp->w_scrollbars[SBAR_LEFT].ident;
1191                     long identRight = sb->wp->w_scrollbars[SBAR_RIGHT].ident;
1192                     [self setScrollbarThumbValue:value size:size max:max
1193                                       identifier:identLeft];
1194                     [self setScrollbarThumbValue:value size:size max:max
1195                                       identifier:identRight];
1196                 } else {
1197                     // Update the horizontal scrollbar.
1198                     [self setScrollbarThumbValue:value size:size max:max
1199                                       identifier:ident];
1200                 }
1201             }
1202         }
1203     } else if (SetFontMsgID == msgid) {
1204         if (!data) return;
1205         const void *bytes = [data bytes];
1206         float pointSize = *((float*)bytes);  bytes += sizeof(float);
1207         //unsigned len = *((unsigned*)bytes);  bytes += sizeof(unsigned);
1208         bytes += sizeof(unsigned);  // len not used
1210         NSMutableString *name = [NSMutableString stringWithUTF8String:bytes];
1211         [name appendString:[NSString stringWithFormat:@":h%.2f", pointSize]];
1213         set_option_value((char_u*)"gfn", 0, (char_u*)[name UTF8String], 0);
1215         // Force screen redraw (does it have to be this complicated?).
1216         redraw_all_later(CLEAR);
1217         update_screen(NOT_VALID);
1218         setcursor();
1219         out_flush();
1220         gui_update_cursor(FALSE, FALSE);
1221         gui_mch_flush();
1222     } else if (VimShouldCloseMsgID == msgid) {
1223         gui_shell_closed();
1224     } else if (DropFilesMsgID == msgid) {
1225 #ifdef FEAT_DND
1226         const void *bytes = [data bytes];
1227         int n = *((int*)bytes);  bytes += sizeof(int);
1229 #if 0
1230         int row = *((int*)bytes);  bytes += sizeof(int);
1231         int col = *((int*)bytes);  bytes += sizeof(int);
1233         char_u **fnames = (char_u **)alloc(n * sizeof(char_u *));
1234         if (fnames) {
1235             const void *end = [data bytes] + [data length];
1236             int i = 0;
1237             while (bytes < end && i < n) {
1238                 int len = *((int*)bytes);  bytes += sizeof(int);
1239                 fnames[i++] = vim_strnsave((char_u*)bytes, len);
1240                 bytes += len;
1241             }
1243             // NOTE!  This function will free 'fnames'.
1244             gui_handle_drop(col, row, 0, fnames, i < n ? i : n);
1245         }
1246 #else
1247         // HACK!  I'm not sure how to get Vim to open a list of files in tabs,
1248         // so instead I create a ':tab drop' command with all the files to open
1249         // and execute it.
1250         NSMutableString *cmd = (n > 1)
1251                 ? [NSMutableString stringWithString:@":tab drop"]
1252                 : [NSMutableString stringWithString:@":drop"];
1254         const void *end = [data bytes] + [data length];
1255         int i;
1256         for (i = 0; i < n && bytes < end; ++i) {
1257             int len = *((int*)bytes);  bytes += sizeof(int);
1258             NSMutableString *file =
1259                     [NSMutableString stringWithUTF8String:bytes];
1260             [file replaceOccurrencesOfString:@" "
1261                                   withString:@"\\ "
1262                                      options:0
1263                                        range:NSMakeRange(0, [file length])];
1264             bytes += len;
1266             [cmd appendString:@" "];
1267             [cmd appendString:file];
1268         }
1270         // By going to the last tabpage we ensure that the new tabs will appear
1271         // last (if this call is left out, the taborder becomes messy).
1272         goto_tabpage(9999);
1274         do_cmdline_cmd((char_u*)[cmd UTF8String]);
1276         // Force screen redraw (does it have to be this complicated?).
1277         // (This code was taken from the end of gui_handle_drop().)
1278         update_screen(NOT_VALID);
1279         setcursor();
1280         out_flush();
1281         gui_update_cursor(FALSE, FALSE);
1282         gui_mch_flush();
1283 #endif
1284 #endif // FEAT_DND
1285     } else if (DropStringMsgID == msgid) {
1286 #ifdef FEAT_DND
1287         char_u  dropkey[3] = { CSI, KS_EXTRA, (char_u)KE_DROP };
1288         const void *bytes = [data bytes];
1289         int len = *((int*)bytes);  bytes += sizeof(int);
1290         NSMutableString *string = [NSMutableString stringWithUTF8String:bytes];
1292         // Replace unrecognized end-of-line sequences with \x0a (line feed).
1293         NSRange range = { 0, [string length] };
1294         unsigned n = [string replaceOccurrencesOfString:@"\x0d\x0a"
1295                                              withString:@"\x0a" options:0
1296                                                   range:range];
1297         if (0 == n) {
1298             n = [string replaceOccurrencesOfString:@"\x0d" withString:@"\x0a"
1299                                            options:0 range:range];
1300         }
1302         len = [string lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
1303         dnd_yank_drag_data((char_u*)[string UTF8String], len);
1304         add_to_input_buf(dropkey, sizeof(dropkey));
1305 #endif // FEAT_DND
1306     } else if (GotFocusMsgID == msgid) {
1307         if (!gui.in_focus)
1308             gui_focus_change(YES);
1309     } else if (LostFocusMsgID == msgid) {
1310         if (gui.in_focus)
1311             gui_focus_change(NO);
1312     } else if (MouseMovedMsgID == msgid) {
1313         const void *bytes = [data bytes];
1314         int row = *((int*)bytes);  bytes += sizeof(int);
1315         int col = *((int*)bytes);  bytes += sizeof(int);
1317         gui_mouse_moved(col, row);
1318     } else if (SetMouseShapeMsgID == msgid) {
1319         const void *bytes = [data bytes];
1320         int shape = *((int*)bytes);  bytes += sizeof(int);
1321         update_mouseshape(shape);
1322     } else {
1323         NSLog(@"WARNING: Unknown message received (msgid=%d)", msgid);
1324     }
1327 + (NSDictionary *)specialKeys
1329     static NSDictionary *specialKeys = nil;
1331     if (!specialKeys) {
1332         NSBundle *mainBundle = [NSBundle mainBundle];
1333         NSString *path = [mainBundle pathForResource:@"SpecialKeys"
1334                                               ofType:@"plist"];
1335         specialKeys = [[NSDictionary alloc] initWithContentsOfFile:path];
1336     }
1338     return specialKeys;
1341 - (void)handleKeyDown:(NSString *)key modifiers:(int)mods
1343     char_u special[3];
1344     char_u modChars[3];
1345     char_u *chars = 0;
1346     int length = 0;
1348     // Special keys (arrow keys, function keys, etc.) are stored in a plist so
1349     // that new keys can easily be added.
1350     NSString *specialString = [[MMBackend specialKeys]
1351             objectForKey:key];
1352     if (specialString && [specialString length] > 1) {
1353         //NSLog(@"special key: %@", specialString);
1354         int ikey = TO_SPECIAL([specialString characterAtIndex:0],
1355                 [specialString characterAtIndex:1]);
1357         ikey = simplify_key(ikey, &mods);
1358         if (ikey == CSI)
1359             ikey = K_CSI;
1361         special[0] = CSI;
1362         special[1] = K_SECOND(ikey);
1363         special[2] = K_THIRD(ikey);
1365         chars = special;
1366         length = 3;
1367     } else if ([key length] > 0) {
1368         chars = (char_u*)[key UTF8String];
1369         length = [key lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
1370         unichar c = [key characterAtIndex:0];
1372         //NSLog(@"non-special: %@ (hex=%x, mods=%d)", key,
1373         //        [key characterAtIndex:0], mods);
1375         if (length == 1 && ((c == Ctrl_C && ctrl_c_interrupts)
1376                 || (c == intr_char && intr_char != Ctrl_C))) {
1377             trash_input_buf();
1378             got_int = TRUE;
1379         }
1381         // HACK!  In most circumstances the Ctrl and Shift modifiers should be
1382         // cleared since they are already added to the key by the AppKit.
1383         // Unfortunately, the only way to deal with when to clear the modifiers
1384         // or not seems to be to have hard-wired rules like this.
1385         if ( !((' ' == c) || (0xa0 == c) || (mods & MOD_MASK_CMD)) ) {
1386             mods &= ~MOD_MASK_SHIFT;
1387             mods &= ~MOD_MASK_CTRL;
1388             //NSLog(@"clear shift ctrl");
1389         }
1391         // HACK!  All Option+key presses go via 'insert text' messages, except
1392         // for <M-Space>.  If the Alt flag is not cleared for <M-Space> it does
1393         // not work to map to it.
1394         if (0xa0 == c && !(mods & MOD_MASK_CMD)) {
1395             //NSLog(@"clear alt");
1396             mods &= ~MOD_MASK_ALT;
1397         }
1398     }
1400     if (chars && length > 0) {
1401         if (mods) {
1402             //NSLog(@"adding mods: %d", mods);
1403             modChars[0] = CSI;
1404             modChars[1] = KS_MODIFIER;
1405             modChars[2] = mods;
1406             add_to_input_buf(modChars, 3);
1407         }
1409         //NSLog(@"add to input buf: 0x%x", chars[0]);
1410         // TODO: Check for CSI bytes?
1411         add_to_input_buf(chars, length);
1412     }
1415 - (void)queueMessage:(int)msgid data:(NSData *)data
1417     [queue addObject:[NSData dataWithBytes:&msgid length:sizeof(int)]];
1418     if (data)
1419         [queue addObject:data];
1420     else
1421         [queue addObject:[NSData data]];
1424 - (void)connectionDidDie:(NSNotification *)notification
1426     // If the main connection to MacVim is lost this means that MacVim was
1427     // either quit (by the user chosing Quit on the MacVim menu), or it has
1428     // crashed.  In either case our only option is to quit now.
1429     // TODO: Write backup file?
1431     //NSLog(@"A Vim process lots its connection to MacVim; quitting.");
1432     getout(0);
1435 - (void)blinkTimerFired:(NSTimer *)timer
1437     NSTimeInterval timeInterval = 0;
1439     [blinkTimer release];
1440     blinkTimer = nil;
1442     if (MMBlinkStateOn == blinkState) {
1443         gui_undraw_cursor();
1444         blinkState = MMBlinkStateOff;
1445         timeInterval = blinkOffInterval;
1446     } else if (MMBlinkStateOff == blinkState) {
1447         gui_update_cursor(TRUE, FALSE);
1448         blinkState = MMBlinkStateOn;
1449         timeInterval = blinkOnInterval;
1450     }
1452     if (timeInterval > 0) {
1453         blinkTimer = 
1454             [[NSTimer scheduledTimerWithTimeInterval:timeInterval target:self
1455                                             selector:@selector(blinkTimerFired:)
1456                                             userInfo:nil repeats:NO] retain];
1457         [self flushQueue:YES];
1458     }
1461 @end // MMBackend (Private)
1466 static int eventModifierFlagsToVimModMask(int modifierFlags)
1468     int modMask = 0;
1470     if (modifierFlags & NSShiftKeyMask)
1471         modMask |= MOD_MASK_SHIFT;
1472     if (modifierFlags & NSControlKeyMask)
1473         modMask |= MOD_MASK_CTRL;
1474     if (modifierFlags & NSAlternateKeyMask)
1475         modMask |= MOD_MASK_ALT;
1476     if (modifierFlags & NSCommandKeyMask)
1477         modMask |= MOD_MASK_CMD;
1479     return modMask;
1482 static int vimModMaskToEventModifierFlags(int mods)
1484     int flags = 0;
1486     if (mods & MOD_MASK_SHIFT)
1487         flags |= NSShiftKeyMask;
1488     if (mods & MOD_MASK_CTRL)
1489         flags |= NSControlKeyMask;
1490     if (mods & MOD_MASK_ALT)
1491         flags |= NSAlternateKeyMask;
1492     if (mods & MOD_MASK_CMD)
1493         flags |= NSCommandKeyMask;
1495     return flags;
1498 static int eventModifierFlagsToVimMouseModMask(int modifierFlags)
1500     int modMask = 0;
1502     if (modifierFlags & NSShiftKeyMask)
1503         modMask |= MOUSE_SHIFT;
1504     if (modifierFlags & NSControlKeyMask)
1505         modMask |= MOUSE_CTRL;
1506     if (modifierFlags & NSAlternateKeyMask)
1507         modMask |= MOUSE_ALT;
1509     return modMask;
1512 static int eventButtonNumberToVimMouseButton(int buttonNumber)
1514     static int mouseButton[] = { MOUSE_LEFT, MOUSE_RIGHT, MOUSE_MIDDLE,
1515             MOUSE_X1, MOUSE_X2 };
1517     return mouseButton[buttonNumber < 5 ? buttonNumber : 0];
1520 static int specialKeyToNSKey(int key)
1522     if (!IS_SPECIAL(key))
1523         return key;
1525     static struct {
1526         int special;
1527         int nskey;
1528     } sp2ns[] = {
1529         { K_UP, NSUpArrowFunctionKey },
1530         { K_DOWN, NSDownArrowFunctionKey },
1531         { K_LEFT, NSLeftArrowFunctionKey },
1532         { K_RIGHT, NSRightArrowFunctionKey },
1533         { K_F1, NSF1FunctionKey },
1534         { K_F2, NSF2FunctionKey },
1535         { K_F3, NSF3FunctionKey },
1536         { K_F4, NSF4FunctionKey },
1537         { K_F5, NSF5FunctionKey },
1538         { K_F6, NSF6FunctionKey },
1539         { K_F7, NSF7FunctionKey },
1540         { K_F8, NSF8FunctionKey },
1541         { K_F9, NSF9FunctionKey },
1542         { K_F10, NSF10FunctionKey },
1543         { K_F11, NSF11FunctionKey },
1544         { K_F12, NSF12FunctionKey },
1545         { K_F13, NSF13FunctionKey },
1546         { K_F14, NSF14FunctionKey },
1547         { K_F15, NSF15FunctionKey },
1548         { K_F16, NSF16FunctionKey },
1549         { K_F17, NSF17FunctionKey },
1550         { K_F18, NSF18FunctionKey },
1551         { K_F19, NSF19FunctionKey },
1552         { K_F20, NSF20FunctionKey },
1553         { K_F21, NSF21FunctionKey },
1554         { K_F22, NSF22FunctionKey },
1555         { K_F23, NSF23FunctionKey },
1556         { K_F24, NSF24FunctionKey },
1557         { K_F25, NSF25FunctionKey },
1558         { K_F26, NSF26FunctionKey },
1559         { K_F27, NSF27FunctionKey },
1560         { K_F28, NSF28FunctionKey },
1561         { K_F29, NSF29FunctionKey },
1562         { K_F30, NSF30FunctionKey },
1563         { K_F31, NSF31FunctionKey },
1564         { K_F32, NSF32FunctionKey },
1565         { K_F33, NSF33FunctionKey },
1566         { K_F34, NSF34FunctionKey },
1567         { K_F35, NSF35FunctionKey },
1568         { K_DEL, NSBackspaceCharacter },
1569         { K_BS, NSDeleteCharacter },
1570         { K_HOME, NSHomeFunctionKey },
1571         { K_END, NSEndFunctionKey },
1572         { K_PAGEUP, NSPageUpFunctionKey },
1573         { K_PAGEDOWN, NSPageDownFunctionKey }
1574     };
1576     int i;
1577     for (i = 0; i < sizeof(sp2ns)/sizeof(sp2ns[0]); ++i) {
1578         if (sp2ns[i].special == key)
1579             return sp2ns[i].nskey;
1580     }
1582     return 0;