Fixed rounding problem when looking up system colors.
[MacVim/jjgod.git] / MMBackend.m
blob646b44a862a73985a9ecd47b0b0aeb4c65685f41
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 - (void)focusChange:(BOOL)on;
45 - (void)processInputBegin;
46 - (void)processInputEnd;
47 @end
51 @implementation MMBackend
53 + (MMBackend *)sharedInstance
55     static MMBackend *singleton = nil;
56     return singleton ? singleton : (singleton = [MMBackend new]);
59 - (id)init
61     if ((self = [super init])) {
62         queue = [[NSMutableArray alloc] init];
63 #if MM_USE_INPUT_QUEUE
64         inputQueue = [[NSMutableArray alloc] init];
65 #endif
66         drawData = [[NSMutableData alloc] initWithCapacity:1024];
68         NSString *path = [[NSBundle mainBundle] pathForResource:@"Colors"
69                                                          ofType:@"plist"];
70         if (path) {
71             colorDict = [[NSDictionary dictionaryWithContentsOfFile:path]
72                 retain];
73         } else {
74             NSLog(@"WARNING: Could not locate Colors.plist.");
75         }
77         path = [[NSBundle mainBundle] pathForResource:@"SystemColors"
78                                                ofType:@"plist"];
79         if (path) {
80             sysColorDict = [[NSDictionary dictionaryWithContentsOfFile:path]
81                 retain];
82         } else {
83             NSLog(@"WARNING: Could not locate SystemColors.plist.");
84         }
85     }
87     return self;
90 - (void)dealloc
92     //NSLog(@"%@ %s", [self className], _cmd);
94     [[NSNotificationCenter defaultCenter] removeObserver:self];
96     [blinkTimer release];  blinkTimer = nil;
97 #if MM_USE_INPUT_QUEUE
98     [inputQueue release];  inputQueue = nil;
99 #endif
100     [queue release];  queue = nil;
101     [drawData release];  drawData = nil;
102     [frontendProxy release];  frontendProxy = nil;
103     [connection release];  connection = nil;
104     [sysColorDict release];  sysColorDict = nil;
105     [colorDict release];  colorDict = nil;
107     [super dealloc];
110 - (void)setBackgroundColor:(int)color
112     backgroundColor = color;
115 - (void)setForegroundColor:(int)color
117     foregroundColor = color;
120 - (void)setSpecialColor:(int)color
122     specialColor = color;
125 - (void)setDefaultColorsBackground:(int)bg foreground:(int)fg
127     defaultBackgroundColor = bg;
128     defaultForegroundColor = fg;
130     NSMutableData *data = [NSMutableData data];
132     [data appendBytes:&bg length:sizeof(int)];
133     [data appendBytes:&fg length:sizeof(int)];
135     [self queueMessage:SetDefaultColorsMsgID data:data];
138 - (NSString *)macVimConnectionName
140     // NOTE!  If the name of the connection changes here it must also be
141     // updated in MMAppController.m.
142     return [NSString stringWithFormat:@"%@-connection",
143            [[NSBundle mainBundle] bundleIdentifier]];
146 - (BOOL)checkin
148     NSBundle *mainBundle = [NSBundle mainBundle];
150     NSString *name = [self macVimConnectionName];
151     connection = [NSConnection connectionWithRegisteredName:name host:nil];
152     if (!connection) {
153 #if 0
154         NSString *path = [mainBundle bundlePath];
155         if (![[NSWorkspace sharedWorkspace] launchApplication:path]) {
156             NSLog(@"WARNING: Failed to launch GUI with path %@", path);
157             return NO;
158         }
159 #else
160         // HACK!  It would be preferable to launch the GUI using NSWorkspace,
161         // however I have not managed to figure out how to pass arguments using
162         // NSWorkspace.
163         //
164         // NOTE!  Using NSTask to launch the GUI has the negative side-effect
165         // that the GUI won't be activated (or raised) so there is a hack in
166         // MMWindowController which always raises the app when a new window is
167         // opened.
168         NSMutableArray *args = [NSMutableArray arrayWithObjects:
169             [NSString stringWithFormat:@"-%@", MMNoWindowKey], @"yes", nil];
170         NSString *exeName = [[mainBundle infoDictionary]
171                 objectForKey:@"CFBundleExecutable"];
172         NSString *path = [mainBundle pathForAuxiliaryExecutable:exeName];
173         if (!path) {
174             NSLog(@"ERROR: Could not find MacVim executable in bundle");
175             return NO;
176         }
178         [NSTask launchedTaskWithLaunchPath:path arguments:args];
179 #endif
181         // HACK!  The NSWorkspaceDidLaunchApplicationNotification does not work
182         // for tasks like this, so poll the mach bootstrap server until it
183         // returns a valid connection.  Also set a time-out date so that we
184         // don't get stuck doing this forever.
185         NSDate *timeOutDate = [NSDate dateWithTimeIntervalSinceNow:15];
186         while (!connection &&
187                 NSOrderedDescending == [timeOutDate compare:[NSDate date]])
188         {
189             [[NSRunLoop currentRunLoop]
190                     runMode:NSDefaultRunLoopMode
191                  beforeDate:[NSDate dateWithTimeIntervalSinceNow:1]];
193             connection = [NSConnection connectionWithRegisteredName:name
194                                                                host:nil];
195         }
197         if (!connection) {
198             NSLog(@"WARNING: Timed-out waiting for GUI to launch.");
199             return NO;
200         }
201     }
203     id proxy = [connection rootProxy];
204     [proxy setProtocolForProxy:@protocol(MMAppProtocol)];
206     [[NSNotificationCenter defaultCenter] addObserver:self
207             selector:@selector(connectionDidDie:)
208                 name:NSConnectionDidDieNotification object:connection];
210     int pid = [[NSProcessInfo processInfo] processIdentifier];
212     @try {
213         frontendProxy = [(NSDistantObject*)[proxy connectBackend:self
214                                                              pid:pid] retain];
215     }
216     @catch (NSException *e) {
217         NSLog(@"Exception caught when trying to connect backend: \"%@\"", e);
218     }
220     if (frontendProxy) {
221         [frontendProxy setProtocolForProxy:@protocol(MMAppProtocol)];
222     }
224     return connection && frontendProxy;
227 - (BOOL)openVimWindow
229     [self queueMessage:OpenVimWindowMsgID data:nil];
230     return YES;
233 - (void)clearAll
235     int type = ClearAllDrawType;
237     // Any draw commands in queue are effectively obsolete since this clearAll
238     // will negate any effect they have, therefore we may as well clear the
239     // draw queue.
240     [drawData setLength:0];
242     [drawData appendBytes:&type length:sizeof(int)];
244     [drawData appendBytes:&defaultBackgroundColor length:sizeof(int)];
247 - (void)clearBlockFromRow:(int)row1 column:(int)col1
248                     toRow:(int)row2 column:(int)col2
250     int type = ClearBlockDrawType;
252     [drawData appendBytes:&type length:sizeof(int)];
254     [drawData appendBytes:&defaultBackgroundColor length:sizeof(int)];
255     [drawData appendBytes:&row1 length:sizeof(int)];
256     [drawData appendBytes:&col1 length:sizeof(int)];
257     [drawData appendBytes:&row2 length:sizeof(int)];
258     [drawData appendBytes:&col2 length:sizeof(int)];
261 - (void)deleteLinesFromRow:(int)row count:(int)count
262               scrollBottom:(int)bottom left:(int)left right:(int)right
264     int type = DeleteLinesDrawType;
266     [drawData appendBytes:&type length:sizeof(int)];
268     [drawData appendBytes:&defaultBackgroundColor length:sizeof(int)];
269     [drawData appendBytes:&row length:sizeof(int)];
270     [drawData appendBytes:&count length:sizeof(int)];
271     [drawData appendBytes:&bottom length:sizeof(int)];
272     [drawData appendBytes:&left length:sizeof(int)];
273     [drawData appendBytes:&right length:sizeof(int)];
276 - (void)replaceString:(char*)s length:(int)len row:(int)row column:(int)col
277                 flags:(int)flags
279     if (len <= 0) return;
281     int type = ReplaceStringDrawType;
283     [drawData appendBytes:&type length:sizeof(int)];
285     [drawData appendBytes:&backgroundColor length:sizeof(int)];
286     [drawData appendBytes:&foregroundColor length:sizeof(int)];
287     [drawData appendBytes:&specialColor length:sizeof(int)];
288     [drawData appendBytes:&row length:sizeof(int)];
289     [drawData appendBytes:&col length:sizeof(int)];
290     [drawData appendBytes:&flags length:sizeof(int)];
291     [drawData appendBytes:&len length:sizeof(int)];
292     [drawData appendBytes:s length:len];
295 - (void)insertLinesFromRow:(int)row count:(int)count
296               scrollBottom:(int)bottom left:(int)left right:(int)right
298     int type = InsertLinesDrawType;
300     [drawData appendBytes:&type length:sizeof(int)];
302     [drawData appendBytes:&defaultBackgroundColor length:sizeof(int)];
303     [drawData appendBytes:&row length:sizeof(int)];
304     [drawData appendBytes:&count length:sizeof(int)];
305     [drawData appendBytes:&bottom length:sizeof(int)];
306     [drawData appendBytes:&left length:sizeof(int)];
307     [drawData appendBytes:&right length:sizeof(int)];
310 - (void)drawCursorAtRow:(int)row column:(int)col shape:(int)shape
311                fraction:(int)percent color:(int)color
313     int type = DrawCursorDrawType;
315     [drawData appendBytes:&type length:sizeof(int)];
317     [drawData appendBytes:&color length:sizeof(int)];
318     [drawData appendBytes:&row length:sizeof(int)];
319     [drawData appendBytes:&col length:sizeof(int)];
320     [drawData appendBytes:&shape length:sizeof(int)];
321     [drawData appendBytes:&percent length:sizeof(int)];
324 - (void)flushQueue:(BOOL)force
326     // NOTE! This method gets called a lot; if we were to flush every time it
327     // was called MacVim would feel unresponsive.  So there is a time out which
328     // ensures that the queue isn't flushed too often.
329     if (!force && lastFlushDate && -[lastFlushDate timeIntervalSinceNow]
330             < MMFlushTimeoutInterval)
331         return;
333     if ([drawData length] > 0) {
334         [self queueMessage:BatchDrawMsgID data:[drawData copy]];
335         [drawData setLength:0];
336     }
338     if ([queue count] > 0) {
339         @try {
340             [frontendProxy processCommandQueue:queue];
341         }
342         @catch (NSException *e) {
343             NSLog(@"Exception caught when processing command queue: \"%@\"", e);
344         }
346         [queue removeAllObjects];
348         [lastFlushDate release];
349         lastFlushDate = [[NSDate date] retain];
350     }
353 - (BOOL)waitForInput:(int)milliseconds
355     NSDate *date = milliseconds > 0 ?
356             [NSDate dateWithTimeIntervalSinceNow:.001*milliseconds] : 
357             [NSDate distantFuture];
359     [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:date];
361     // I know of no way to figure out if the run loop exited because input was
362     // found or because of a time out, so I need to manually indicate when
363     // input was received in processInput:data: and then reset it every time
364     // here.
365     BOOL yn = inputReceived;
366     inputReceived = NO;
368     return yn;
371 - (void)exit
373 #ifdef MAC_CLIENTSERVER
374     // The default connection is used for the client/server code.
375     [[NSConnection defaultConnection] setRootObject:nil];
376     [[NSConnection defaultConnection] invalidate];
377 #endif
379     // By invalidating the NSConnection the MMWindowController immediately
380     // finds out that the connection is down and as a result
381     // [MMWindowController connectionDidDie:] is invoked.
382     //NSLog(@"%@ %s", [self className], _cmd);
383     [[NSNotificationCenter defaultCenter] removeObserver:self];
384     [connection invalidate];
387 - (void)selectTab:(int)index
389     //NSLog(@"%s%d", _cmd, index);
391     index -= 1;
392     NSData *data = [NSData dataWithBytes:&index length:sizeof(int)];
393     [self queueMessage:SelectTabMsgID data:data];
396 - (void)updateTabBar
398     //NSLog(@"%s", _cmd);
400     NSMutableData *data = [NSMutableData data];
402     int idx = tabpage_index(curtab) - 1;
403     [data appendBytes:&idx length:sizeof(int)];
405     tabpage_T *tp;
406     for (tp = first_tabpage; tp != NULL; tp = tp->tp_next) {
407         // This function puts the label of the tab in the global 'NameBuff'.
408         get_tabline_label(tp, FALSE);
409         int len = strlen((char*)NameBuff);
410         if (len <= 0) continue;
412         // Count the number of windows in the tabpage.
413         //win_T *wp = tp->tp_firstwin;
414         //int wincount;
415         //for (wincount = 0; wp != NULL; wp = wp->w_next, ++wincount);
417         //[data appendBytes:&wincount length:sizeof(int)];
418         [data appendBytes:&len length:sizeof(int)];
419         [data appendBytes:NameBuff length:len];
420     }
422     [self queueMessage:UpdateTabBarMsgID data:data];
425 - (BOOL)tabBarVisible
427     return tabBarVisible;
430 - (void)showTabBar:(BOOL)enable
432     tabBarVisible = enable;
434     int msgid = enable ? ShowTabBarMsgID : HideTabBarMsgID;
435     [self queueMessage:msgid data:nil];
438 - (void)setRows:(int)rows columns:(int)cols
440     //NSLog(@"[VimTask] setRows:%d columns:%d", rows, cols);
442     int dim[] = { rows, cols };
443     NSData *data = [NSData dataWithBytes:&dim length:2*sizeof(int)];
445     [self queueMessage:SetTextDimensionsMsgID data:data];
448 - (void)setVimWindowTitle:(char *)title
450     NSMutableData *data = [NSMutableData data];
451     int len = strlen(title);
452     if (len <= 0) return;
454     [data appendBytes:&len length:sizeof(int)];
455     [data appendBytes:title length:len];
457     [self queueMessage:SetVimWindowTitleMsgID data:data];
460 - (char *)browseForFileInDirectory:(char *)dir title:(char *)title
461                             saving:(int)saving
463     //NSLog(@"browseForFileInDirectory:%s title:%s saving:%d", dir, title,
464     //        saving);
466     char_u *s = NULL;
467     NSString *ds = dir
468             ? [NSString stringWithCString:dir encoding:NSUTF8StringEncoding]
469             : nil;
470     NSString *ts = title
471             ? [NSString stringWithCString:title encoding:NSUTF8StringEncoding]
472             : nil;
473     @try {
474         [frontendProxy showSavePanelForDirectory:ds title:ts saving:saving];
476         // Wait until a reply is sent from MMVimController.
477         [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
478                                  beforeDate:[NSDate distantFuture]];
480         if (dialogReturn && [dialogReturn isKindOfClass:[NSString class]]) {
481             s = vim_strsave((char_u*)[dialogReturn UTF8String]);
482         }
484         [dialogReturn release];  dialogReturn = nil;
485     }
486     @catch (NSException *e) {
487         NSLog(@"Exception caught when showing save panel: \"%@\"", e);
488     }
490     return (char *)s;
493 - (oneway void)setDialogReturn:(in bycopy id)obj
495     // NOTE: This is called by
496     //   - [MMVimController panelDidEnd:::], and
497     //   - [MMVimController alertDidEnd:::],
498     // to indicate that a save/open panel or alert has finished.
500     if (obj != dialogReturn) {
501         [dialogReturn release];
502         dialogReturn = [obj retain];
503     }
506 - (int)presentDialogWithType:(int)type title:(char *)title message:(char *)msg
507                      buttons:(char *)btns textField:(char *)txtfield
509     int retval = 0;
510     NSString *message = nil, *text = nil, *textFieldString = nil;
511     NSArray *buttons = nil;
512     int style = NSInformationalAlertStyle;
514     if (VIM_WARNING == type) style = NSWarningAlertStyle;
515     else if (VIM_ERROR == type) style = NSCriticalAlertStyle;
517     if (btns) {
518         NSString *btnString = [NSString stringWithUTF8String:btns];
519         buttons = [btnString componentsSeparatedByString:@"\n"];
520     }
521     if (title)
522         message = [NSString stringWithUTF8String:title];
523     if (msg) {
524         text = [NSString stringWithUTF8String:msg];
525         if (!message) {
526             // HACK! If there is a '\n\n' or '\n' sequence in the message, then
527             // make the part up to there into the title.  We only do this
528             // because Vim has lots of dialogs without a title and they look
529             // ugly that way.
530             // TODO: Fix the actual dialog texts.
531             NSRange eolRange = [text rangeOfString:@"\n\n"];
532             if (NSNotFound == eolRange.location)
533                 eolRange = [text rangeOfString:@"\n"];
534             if (NSNotFound != eolRange.location) {
535                 message = [text substringToIndex:eolRange.location];
536                 text = [text substringFromIndex:NSMaxRange(eolRange)];
537             }
538         }
539     }
540     if (txtfield)
541         textFieldString = [NSString stringWithUTF8String:txtfield];
543     @try {
544         [frontendProxy presentDialogWithStyle:style message:message
545                               informativeText:text buttonTitles:buttons
546                               textFieldString:textFieldString];
548         // Wait until a reply is sent from MMVimController.
549         [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
550                                  beforeDate:[NSDate distantFuture]];
552         if (dialogReturn && [dialogReturn isKindOfClass:[NSArray class]]
553                 && [dialogReturn count]) {
554             retval = [[dialogReturn objectAtIndex:0] intValue];
555             if (txtfield && [dialogReturn count] > 1) {
556                 NSString *retString = [dialogReturn objectAtIndex:1];
557                 vim_strncpy((char_u*)txtfield, (char_u*)[retString UTF8String],
558                         IOSIZE - 1);
559             }
560         }
562         [dialogReturn release]; dialogReturn = nil;
563     }
564     @catch (NSException *e) {
565         NSLog(@"Exception caught while showing alert dialog: \"%@\"", e);
566     }
568     return retval;
571 - (void)addMenuWithTag:(int)tag parent:(int)parentTag name:(char *)name
572                atIndex:(int)index
574     //NSLog(@"addMenuWithTag:%d parent:%d name:%s atIndex:%d", tag, parentTag,
575     //        name, index);
577     int namelen = name ? strlen(name) : 0;
578     NSMutableData *data = [NSMutableData data];
580     [data appendBytes:&tag length:sizeof(int)];
581     [data appendBytes:&parentTag length:sizeof(int)];
582     [data appendBytes:&namelen length:sizeof(int)];
583     if (namelen > 0) [data appendBytes:name length:namelen];
584     [data appendBytes:&index length:sizeof(int)];
586     [self queueMessage:AddMenuMsgID data:data];
589 - (void)addMenuItemWithTag:(int)tag parent:(int)parentTag name:(char *)name
590                        tip:(char *)tip icon:(char *)icon
591              keyEquivalent:(int)key modifiers:(int)mods
592                     action:(NSString *)action atIndex:(int)index
594     //NSLog(@"addMenuItemWithTag:%d parent:%d name:%s tip:%s atIndex:%d", tag,
595     //        parentTag, name, tip, index);
597     int namelen = name ? strlen(name) : 0;
598     int tiplen = tip ? strlen(tip) : 0;
599     int iconlen = icon ? strlen(icon) : 0;
600     int eventFlags = vimModMaskToEventModifierFlags(mods);
601     int actionlen = [action lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
602     NSMutableData *data = [NSMutableData data];
604     key = specialKeyToNSKey(key);
606     [data appendBytes:&tag length:sizeof(int)];
607     [data appendBytes:&parentTag length:sizeof(int)];
608     [data appendBytes:&namelen length:sizeof(int)];
609     if (namelen > 0) [data appendBytes:name length:namelen];
610     [data appendBytes:&tiplen length:sizeof(int)];
611     if (tiplen > 0) [data appendBytes:tip length:tiplen];
612     [data appendBytes:&iconlen length:sizeof(int)];
613     if (iconlen > 0) [data appendBytes:icon length:iconlen];
614     [data appendBytes:&actionlen length:sizeof(int)];
615     if (actionlen > 0) [data appendBytes:[action UTF8String] length:actionlen];
616     [data appendBytes:&index length:sizeof(int)];
617     [data appendBytes:&key length:sizeof(int)];
618     [data appendBytes:&eventFlags length:sizeof(int)];
620     [self queueMessage:AddMenuItemMsgID data:data];
623 - (void)removeMenuItemWithTag:(int)tag
625     NSMutableData *data = [NSMutableData data];
626     [data appendBytes:&tag length:sizeof(int)];
628     [self queueMessage:RemoveMenuItemMsgID data:data];
631 - (void)enableMenuItemWithTag:(int)tag state:(int)enabled
633     NSMutableData *data = [NSMutableData data];
635     [data appendBytes:&tag length:sizeof(int)];
636     [data appendBytes:&enabled length:sizeof(int)];
638     [self queueMessage:EnableMenuItemMsgID data:data];
641 - (void)showPopupMenuWithName:(char *)name atMouseLocation:(BOOL)mouse
643     int len = strlen(name);
644     int row = -1, col = -1;
646     if (len <= 0) return;
648     if (!mouse && curwin) {
649         row = curwin->w_wrow;
650         col = curwin->w_wcol;
651     }
653     NSMutableData *data = [NSMutableData data];
655     [data appendBytes:&row length:sizeof(int)];
656     [data appendBytes:&col length:sizeof(int)];
657     [data appendBytes:&len length:sizeof(int)];
658     [data appendBytes:name length:len];
660     [self queueMessage:ShowPopupMenuMsgID data:data];
663 - (void)showToolbar:(int)enable flags:(int)flags
665     NSMutableData *data = [NSMutableData data];
667     [data appendBytes:&enable length:sizeof(int)];
668     [data appendBytes:&flags length:sizeof(int)];
670     [self queueMessage:ShowToolbarMsgID data:data];
673 - (void)createScrollbarWithIdentifier:(long)ident type:(int)type
675     NSMutableData *data = [NSMutableData data];
677     [data appendBytes:&ident length:sizeof(long)];
678     [data appendBytes:&type length:sizeof(int)];
680     [self queueMessage:CreateScrollbarMsgID data:data];
683 - (void)destroyScrollbarWithIdentifier:(long)ident
685     NSMutableData *data = [NSMutableData data];
686     [data appendBytes:&ident length:sizeof(long)];
688     [self queueMessage:DestroyScrollbarMsgID data:data];
691 - (void)showScrollbarWithIdentifier:(long)ident state:(int)visible
693     NSMutableData *data = [NSMutableData data];
695     [data appendBytes:&ident length:sizeof(long)];
696     [data appendBytes:&visible length:sizeof(int)];
698     [self queueMessage:ShowScrollbarMsgID data:data];
701 - (void)setScrollbarPosition:(int)pos length:(int)len identifier:(long)ident
703     NSMutableData *data = [NSMutableData data];
705     [data appendBytes:&ident length:sizeof(long)];
706     [data appendBytes:&pos length:sizeof(int)];
707     [data appendBytes:&len length:sizeof(int)];
709     [self queueMessage:SetScrollbarPositionMsgID data:data];
712 - (void)setScrollbarThumbValue:(long)val size:(long)size max:(long)max
713                     identifier:(long)ident
715     float fval = max-size+1 > 0 ? (float)val/(max-size+1) : 0;
716     float prop = (float)size/(max+1);
717     if (fval < 0) fval = 0;
718     else if (fval > 1.0f) fval = 1.0f;
719     if (prop < 0) prop = 0;
720     else if (prop > 1.0f) prop = 1.0f;
722     NSMutableData *data = [NSMutableData data];
724     [data appendBytes:&ident length:sizeof(long)];
725     [data appendBytes:&fval length:sizeof(float)];
726     [data appendBytes:&prop length:sizeof(float)];
728     [self queueMessage:SetScrollbarThumbMsgID data:data];
731 - (BOOL)setFontWithName:(char *)name
733     NSString *fontName;
734     float size = 0.0f;
735     BOOL parseFailed = NO;
737     if (name) {
738         fontName = [[[NSString alloc] initWithCString:name
739                 encoding:NSUTF8StringEncoding] autorelease];
740         NSArray *components = [fontName componentsSeparatedByString:@":"];
741         if ([components count] == 2) {
742             NSString *sizeString = [components lastObject];
743             if ([sizeString length] > 0
744                     && [sizeString characterAtIndex:0] == 'h') {
745                 sizeString = [sizeString substringFromIndex:1];
746                 if ([sizeString length] > 0) {
747                     size = [sizeString floatValue];
748                     fontName = [components objectAtIndex:0];
749                 }
750             } else {
751                 parseFailed = YES;
752             }
753         } else if ([components count] > 2) {
754             parseFailed = YES;
755         }
756     } else {
757         fontName = [[NSFont userFixedPitchFontOfSize:0] displayName];
758     }
760     if (!parseFailed && [fontName length] > 0) {
761         if (size < 6 || size > 100) {
762             // Font size 0.0 tells NSFont to use the 'user default size'.
763             size = 0.0f;
764         }
766         NSFont *font = [NSFont fontWithName:fontName size:size];
767         if (font) {
768             //NSLog(@"Setting font '%@' of size %.2f", fontName, size);
769             int len = [fontName
770                     lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
771             if (len > 0) {
772                 NSMutableData *data = [NSMutableData data];
774                 [data appendBytes:&size length:sizeof(float)];
775                 [data appendBytes:&len length:sizeof(int)];
776                 [data appendBytes:[fontName UTF8String] length:len];
778                 [self queueMessage:SetFontMsgID data:data];
779                 return YES;
780             }
781         }
782     }
784     NSLog(@"WARNING: Cannot set font with name '%@' of size %.2f",
785             fontName, size);
786     return NO;
789 - (void)executeActionWithName:(NSString *)name
791     int len = [name lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
793     if (len > 0) {
794         NSMutableData *data = [NSMutableData data];
796         [data appendBytes:&len length:sizeof(int)];
797         [data appendBytes:[name UTF8String] length:len];
799         [self queueMessage:ExecuteActionMsgID data:data];
800     }
803 - (void)setMouseShape:(int)shape
805     NSMutableData *data = [NSMutableData data];
806     [data appendBytes:&shape length:sizeof(int)];
807     [self queueMessage:SetMouseShapeMsgID data:data];
810 - (void)setBlinkWait:(int)wait on:(int)on off:(int)off
812     // Vim specifies times in milliseconds, whereas Cocoa wants them in
813     // seconds.
814     blinkWaitInterval = .001f*wait;
815     blinkOnInterval = .001f*on;
816     blinkOffInterval = .001f*off;
819 - (void)startBlink
821     if (blinkTimer) {
822         [blinkTimer invalidate];
823         [blinkTimer release];
824         blinkTimer = nil;
825     }
827     if (blinkWaitInterval > 0 && blinkOnInterval > 0 && blinkOffInterval > 0
828             && gui.in_focus) {
829         blinkState = MMBlinkStateOn;
830         blinkTimer =
831             [[NSTimer scheduledTimerWithTimeInterval:blinkWaitInterval
832                                               target:self
833                                             selector:@selector(blinkTimerFired:)
834                                             userInfo:nil repeats:NO] retain];
835         gui_update_cursor(TRUE, FALSE);
836         [self flushQueue:YES];
837     }
840 - (void)stopBlink
842     if (MMBlinkStateOff == blinkState) {
843         gui_update_cursor(TRUE, FALSE);
844         [self flushQueue:YES];
845     }
847     blinkState = MMBlinkStateNone;
850 - (void)adjustLinespace:(int)linespace
852     NSMutableData *data = [NSMutableData data];
853     [data appendBytes:&linespace length:sizeof(int)];
854     [self queueMessage:AdjustLinespaceMsgID data:data];
857 - (void)activate
859     [self queueMessage:ActivateMsgID data:nil];
862 - (int)lookupColorWithKey:(NSString *)key
864     if (!(key && [key length] > 0))
865         return INVALCOLOR;
867     NSString *stripKey = [[[[key lowercaseString]
868         stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]
869             componentsSeparatedByString:@" "]
870                componentsJoinedByString:@""];
872     if (stripKey && [stripKey length] > 0) {
873         // First of all try to lookup key in the color dictionary; note that
874         // all keys in this dictionary are lowercase with no whitespace.
875         id obj = [colorDict objectForKey:stripKey];
876         if (obj) return [obj intValue];
878         // The key was not in the dictionary; is it perhaps of the form
879         // #rrggbb?
880         if ([stripKey length] > 1 && [stripKey characterAtIndex:0] == '#') {
881             NSScanner *scanner = [NSScanner scannerWithString:stripKey];
882             [scanner setScanLocation:1];
883             unsigned hex = 0;
884             if ([scanner scanHexInt:&hex]) {
885                 return (int)hex;
886             }
887         }
889         // As a last resort, check if it is one of the system defined colors.
890         // The keys in this dictionary are also lowercase with no whitespace.
891         obj = [sysColorDict objectForKey:stripKey];
892         if (obj) {
893             NSColor *col = [NSColor performSelector:NSSelectorFromString(obj)];
894             if (col) {
895                 float r, g, b, a;
896                 col = [col colorUsingColorSpaceName:NSCalibratedRGBColorSpace];
897                 [col getRed:&r green:&g blue:&b alpha:&a];
898                 return (((int)(r*255+.5f) & 0xff) << 16)
899                      + (((int)(g*255+.5f) & 0xff) << 8)
900                      +  ((int)(b*255+.5f) & 0xff);
901             }
902         }
903     }
905     NSLog(@"WARNING: No color with key %@ found.", stripKey);
906     return INVALCOLOR;
909 - (BOOL)hasSpecialKeyWithValue:(NSString *)value
911     NSEnumerator *e = [[MMBackend specialKeys] objectEnumerator];
912     id obj;
914     while ((obj = [e nextObject])) {
915         if ([value isEqual:obj])
916             return YES;
917     }
919     return NO;
922 - (oneway void)processInput:(int)msgid data:(in NSData *)data
924     // NOTE: This method might get called whenever the run loop is tended to.
925     // Thus it might get called whilst input is being processed.  Normally this
926     // is not a problem, but if it gets called often then it might become
927     // dangerous.  E.g. when a focus messages is received the screen is redrawn
928     // because the selection color changes and if another focus message is
929     // received whilst the first one is being processed Vim might crash.  To
930     // deal with this problem at the moment, we simply drop messages that are
931     // received while other input is being processed.
932     if (inProcessInput) {
933 #if MM_USE_INPUT_QUEUE
934         [inputQueue addObject:[NSNumber numberWithInt:msgid]];
935         [inputQueue addObject:data];
936 #else
937         // Just drop the input
938         NSLog(@"WARNING: Dropping input in %s", _cmd);
939 #endif
940     } else {
941         [self processInputBegin];
942         [self handleMessage:msgid data:data];
943         [self processInputEnd];
944     }
947 - (oneway void)processInputAndData:(in NSArray *)messages
949     // NOTE: See comment in processInput:data:.
950     unsigned i, count = [messages count];
951     if (count % 2) {
952         NSLog(@"WARNING: [messages count] is odd in %s", _cmd);
953         return;
954     }
956     if (inProcessInput) {
957 #if MM_USE_INPUT_QUEUE
958         [inputQueue addObjectsFromArray:messages];
959 #else
960         // Just drop the input
961         NSLog(@"WARNING: Dropping input in %s", _cmd);
962 #endif
963     } else {
964         [self processInputBegin];
966         for (i = 0; i < count; i += 2) {
967             int msgid = [[messages objectAtIndex:i] intValue];
968             id data = [messages objectAtIndex:i+1];
969             if ([data isEqual:[NSNull null]])
970                 data = nil;
972             [self handleMessage:msgid data:data];
973         }
975         [self processInputEnd];
976     }
979 - (BOOL)checkForModifiedBuffers
981     buf_T *buf;
982     for (buf = firstbuf; buf != NULL; buf = buf->b_next) {
983         if (bufIsChanged(buf)) {
984             return YES;
985         }
986     }
988     return NO;
991 - (BOOL)starRegisterToPasteboard:(byref NSPasteboard *)pboard
993     if (VIsual_active && (State & NORMAL) && clip_star.available) {
994         // If there is no pasteboard, return YES to indicate that there is text
995         // to copy.
996         if (!pboard)
997             return YES;
999         clip_copy_selection();
1001         // Get the text to put on the pasteboard.
1002         long_u len = 0; char_u *str = 0;
1003         int type = clip_convert_selection(&str, &len, &clip_star);
1004         if (type < 0)
1005             return NO;
1006         
1007         NSString *string = [[NSString alloc]
1008             initWithBytes:str length:len encoding:NSUTF8StringEncoding];
1010         NSArray *types = [NSArray arrayWithObject:NSStringPboardType];
1011         [pboard declareTypes:types owner:nil];
1012         BOOL ok = [pboard setString:string forType:NSStringPboardType];
1013     
1014         [string release];
1015         vim_free(str);
1017         return ok;
1018     }
1020     return NO;
1023 - (NSString *)evaluateExpression:(in bycopy NSString *)expr
1025     NSString *eval = nil;
1026     char_u *res = eval_client_expr_to_string((char_u*)[expr UTF8String]);
1028     if (res != NULL) {
1029         eval = [NSString stringWithUTF8String:(char*)res];
1030         vim_free(res);
1031     }
1033     return eval;
1036 @end // MMBackend
1040 @implementation MMBackend (Private)
1042 - (void)handleMessage:(int)msgid data:(NSData *)data
1044     if (InsertTextMsgID == msgid) {
1045         if (!data) return;
1046         NSString *key = [[NSString alloc] initWithData:data
1047                                               encoding:NSUTF8StringEncoding];
1048         char_u *str = (char_u*)[key UTF8String];
1049         int i, len = [key lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
1051 #if MM_ENABLE_CONV
1052         char_u *conv_str = NULL;
1053         if (input_conv.vc_type != CONV_NONE) {
1054             conv_str = string_convert(&input_conv, str, &len);
1055             if (conv_str)
1056                 str = conv_str;
1057         }
1058 #endif
1060         for (i = 0; i < len; ++i) {
1061             add_to_input_buf(str+i, 1);
1062             if (CSI == str[i]) {
1063                 // NOTE: If the converted string contains the byte CSI, then it
1064                 // must be followed by the bytes KS_EXTRA, KE_CSI or things
1065                 // won't work.
1066                 static char_u extra[2] = { KS_EXTRA, KE_CSI };
1067                 add_to_input_buf(extra, 2);
1068             }
1069         }
1071 #if MM_ENABLE_CONV
1072         if (conv_str)
1073             vim_free(conv_str);
1074 #endif
1075         [key release];
1076     } else if (KeyDownMsgID == msgid || CmdKeyMsgID == msgid) {
1077         if (!data) return;
1078         const void *bytes = [data bytes];
1079         int mods = *((int*)bytes);  bytes += sizeof(int);
1080         int len = *((int*)bytes);  bytes += sizeof(int);
1081         NSString *key = [[NSString alloc] initWithBytes:bytes length:len
1082                                               encoding:NSUTF8StringEncoding];
1083         mods = eventModifierFlagsToVimModMask(mods);
1085         [self handleKeyDown:key modifiers:mods];
1087         [key release];
1088     } else if (SelectTabMsgID == msgid) {
1089         if (!data) return;
1090         const void *bytes = [data bytes];
1091         int idx = *((int*)bytes) + 1;
1092         //NSLog(@"Selecting tab %d", idx);
1093         send_tabline_event(idx);
1094     } else if (CloseTabMsgID == msgid) {
1095         if (!data) return;
1096         const void *bytes = [data bytes];
1097         int idx = *((int*)bytes) + 1;
1098         //NSLog(@"Closing tab %d", idx);
1099         send_tabline_menu_event(idx, TABLINE_MENU_CLOSE);
1100     } else if (AddNewTabMsgID == msgid) {
1101         //NSLog(@"Adding new tab");
1102         send_tabline_menu_event(0, TABLINE_MENU_NEW);
1103     } else if (DraggedTabMsgID == msgid) {
1104         if (!data) return;
1105         const void *bytes = [data bytes];
1106         // NOTE! The destination index is 0 based, so do not add 1 to make it 1
1107         // based.
1108         int idx = *((int*)bytes);
1110         tabpage_move(idx);
1111     } else if (ScrollWheelMsgID == msgid) {
1112         if (!data) return;
1113         const void *bytes = [data bytes];
1115         int row = *((int*)bytes);  bytes += sizeof(int);
1116         int col = *((int*)bytes);  bytes += sizeof(int);
1117         int flags = *((int*)bytes);  bytes += sizeof(int);
1118         float dy = *((float*)bytes);  bytes += sizeof(float);
1120         int button = MOUSE_5;
1121         if (dy > 0) button = MOUSE_4;
1123         flags = eventModifierFlagsToVimMouseModMask(flags);
1125         gui_send_mouse_event(button, col, row, NO, flags);
1126     } else if (MouseDownMsgID == msgid) {
1127         if (!data) return;
1128         const void *bytes = [data bytes];
1130         int row = *((int*)bytes);  bytes += sizeof(int);
1131         int col = *((int*)bytes);  bytes += sizeof(int);
1132         int button = *((int*)bytes);  bytes += sizeof(int);
1133         int flags = *((int*)bytes);  bytes += sizeof(int);
1134         int count = *((int*)bytes);  bytes += sizeof(int);
1136         button = eventButtonNumberToVimMouseButton(button);
1137         flags = eventModifierFlagsToVimMouseModMask(flags);
1139         gui_send_mouse_event(button, col, row, 0 != count, flags);
1140     } else if (MouseUpMsgID == msgid) {
1141         if (!data) return;
1142         const void *bytes = [data bytes];
1144         int row = *((int*)bytes);  bytes += sizeof(int);
1145         int col = *((int*)bytes);  bytes += sizeof(int);
1146         int flags = *((int*)bytes);  bytes += sizeof(int);
1148         flags = eventModifierFlagsToVimMouseModMask(flags);
1150         gui_send_mouse_event(MOUSE_RELEASE, col, row, NO, flags);
1151     } else if (MouseDraggedMsgID == msgid) {
1152         if (!data) return;
1153         const void *bytes = [data bytes];
1155         int row = *((int*)bytes);  bytes += sizeof(int);
1156         int col = *((int*)bytes);  bytes += sizeof(int);
1157         int flags = *((int*)bytes);  bytes += sizeof(int);
1159         flags = eventModifierFlagsToVimMouseModMask(flags);
1161         gui_send_mouse_event(MOUSE_DRAG, col, row, NO, flags);
1162     } else if (SetTextDimensionsMsgID == msgid) {
1163         if (!data) return;
1164         const void *bytes = [data bytes];
1165         int rows = *((int*)bytes);  bytes += sizeof(int);
1166         int cols = *((int*)bytes);  bytes += sizeof(int);
1168         // NOTE! Vim doesn't call gui_mch_set_shellsize() after
1169         // gui_resize_shell(), so we have to manually set the rows and columns
1170         // here.  (MacVim doesn't change the rows and columns to avoid
1171         // inconsistent states between Vim and MacVim.)
1172         [self setRows:rows columns:cols];
1174         //NSLog(@"[VimTask] Resizing shell to %dx%d.", cols, rows);
1175         gui_resize_shell(cols, rows);
1176     } else if (ExecuteMenuMsgID == msgid) {
1177         if (!data) return;
1178         const void *bytes = [data bytes];
1179         int tag = *((int*)bytes);  bytes += sizeof(int);
1181         vimmenu_T *menu = (vimmenu_T*)tag;
1182         // TODO!  Make sure 'menu' is a valid menu pointer!
1183         if (menu) {
1184             gui_menu_cb(menu);
1185         }
1186     } else if (ToggleToolbarMsgID == msgid) {
1187         char_u go[sizeof(GO_ALL)+2];
1188         char_u *p;
1189         int len;
1191         STRCPY(go, p_go);
1192         p = vim_strchr(go, GO_TOOLBAR);
1193         len = STRLEN(go);
1195         if (p != NULL) {
1196             char_u *end = go + len;
1197             while (p < end) {
1198                 p[0] = p[1];
1199                 ++p;
1200             }
1201         } else {
1202             go[len] = GO_TOOLBAR;
1203             go[len+1] = NUL;
1204         }
1206         set_option_value((char_u*)"guioptions", 0, go, 0);
1208         // Force screen redraw (does it have to be this complicated?).
1209         redraw_all_later(CLEAR);
1210         update_screen(NOT_VALID);
1211         setcursor();
1212         out_flush();
1213         gui_update_cursor(FALSE, FALSE);
1214         gui_mch_flush();
1215     } else if (ScrollbarEventMsgID == msgid) {
1216         if (!data) return;
1217         const void *bytes = [data bytes];
1218         long ident = *((long*)bytes);  bytes += sizeof(long);
1219         int hitPart = *((int*)bytes);  bytes += sizeof(int);
1220         float fval = *((float*)bytes);  bytes += sizeof(float);
1221         scrollbar_T *sb = gui_find_scrollbar(ident);
1223         if (sb) {
1224             scrollbar_T *sb_info = sb->wp ? &sb->wp->w_scrollbars[0] : sb;
1225             long value = sb_info->value;
1226             long size = sb_info->size;
1227             long max = sb_info->max;
1228             BOOL isStillDragging = NO;
1229             BOOL updateKnob = YES;
1231             switch (hitPart) {
1232             case NSScrollerDecrementPage:
1233                 value -= (size > 2 ? size - 2 : 1);
1234                 break;
1235             case NSScrollerIncrementPage:
1236                 value += (size > 2 ? size - 2 : 1);
1237                 break;
1238             case NSScrollerDecrementLine:
1239                 --value;
1240                 break;
1241             case NSScrollerIncrementLine:
1242                 ++value;
1243                 break;
1244             case NSScrollerKnob:
1245                 isStillDragging = YES;
1246                 // fall through ...
1247             case NSScrollerKnobSlot:
1248                 value = (long)(fval * (max - size + 1));
1249                 // fall through ...
1250             default:
1251                 updateKnob = NO;
1252                 break;
1253             }
1255             //NSLog(@"value %d -> %d", sb_info->value, value);
1256             gui_drag_scrollbar(sb, value, isStillDragging);
1258             if (updateKnob) {
1259                 // Dragging the knob or option+clicking automatically updates
1260                 // the knob position (on the actual NSScroller), so we only
1261                 // need to set the knob position in the other cases.
1262                 if (sb->wp) {
1263                     // Update both the left&right vertical scrollbars.
1264                     long identLeft = sb->wp->w_scrollbars[SBAR_LEFT].ident;
1265                     long identRight = sb->wp->w_scrollbars[SBAR_RIGHT].ident;
1266                     [self setScrollbarThumbValue:value size:size max:max
1267                                       identifier:identLeft];
1268                     [self setScrollbarThumbValue:value size:size max:max
1269                                       identifier:identRight];
1270                 } else {
1271                     // Update the horizontal scrollbar.
1272                     [self setScrollbarThumbValue:value size:size max:max
1273                                       identifier:ident];
1274                 }
1275             }
1276         }
1277     } else if (SetFontMsgID == msgid) {
1278         if (!data) return;
1279         const void *bytes = [data bytes];
1280         float pointSize = *((float*)bytes);  bytes += sizeof(float);
1281         //unsigned len = *((unsigned*)bytes);  bytes += sizeof(unsigned);
1282         bytes += sizeof(unsigned);  // len not used
1284         NSMutableString *name = [NSMutableString stringWithUTF8String:bytes];
1285         [name appendString:[NSString stringWithFormat:@":h%.2f", pointSize]];
1287         set_option_value((char_u*)"gfn", 0, (char_u*)[name UTF8String], 0);
1289         // Force screen redraw (does it have to be this complicated?).
1290         redraw_all_later(CLEAR);
1291         update_screen(NOT_VALID);
1292         setcursor();
1293         out_flush();
1294         gui_update_cursor(FALSE, FALSE);
1295         gui_mch_flush();
1296     } else if (VimShouldCloseMsgID == msgid) {
1297         gui_shell_closed();
1298     } else if (DropFilesMsgID == msgid) {
1299 #ifdef FEAT_DND
1300         const void *bytes = [data bytes];
1301         int n = *((int*)bytes);  bytes += sizeof(int);
1303 #if 0
1304         int row = *((int*)bytes);  bytes += sizeof(int);
1305         int col = *((int*)bytes);  bytes += sizeof(int);
1307         char_u **fnames = (char_u **)alloc(n * sizeof(char_u *));
1308         if (fnames) {
1309             const void *end = [data bytes] + [data length];
1310             int i = 0;
1311             while (bytes < end && i < n) {
1312                 int len = *((int*)bytes);  bytes += sizeof(int);
1313                 fnames[i++] = vim_strnsave((char_u*)bytes, len);
1314                 bytes += len;
1315             }
1317             // NOTE!  This function will free 'fnames'.
1318             gui_handle_drop(col, row, 0, fnames, i < n ? i : n);
1319         }
1320 #else
1321         // HACK!  I'm not sure how to get Vim to open a list of files in tabs,
1322         // so instead I create a ':tab drop' command with all the files to open
1323         // and execute it.
1324         NSMutableString *cmd = (n > 1)
1325                 ? [NSMutableString stringWithString:@":tab drop"]
1326                 : [NSMutableString stringWithString:@":drop"];
1328         const void *end = [data bytes] + [data length];
1329         int i;
1330         for (i = 0; i < n && bytes < end; ++i) {
1331             int len = *((int*)bytes);  bytes += sizeof(int);
1332             NSMutableString *file =
1333                     [NSMutableString stringWithUTF8String:bytes];
1334             [file replaceOccurrencesOfString:@" "
1335                                   withString:@"\\ "
1336                                      options:0
1337                                        range:NSMakeRange(0, [file length])];
1338             bytes += len;
1340             [cmd appendString:@" "];
1341             [cmd appendString:file];
1342         }
1344         // By going to the last tabpage we ensure that the new tabs will appear
1345         // last (if this call is left out, the taborder becomes messy).
1346         goto_tabpage(9999);
1348         do_cmdline_cmd((char_u*)[cmd UTF8String]);
1350         // Force screen redraw (does it have to be this complicated?).
1351         // (This code was taken from the end of gui_handle_drop().)
1352         update_screen(NOT_VALID);
1353         setcursor();
1354         out_flush();
1355         gui_update_cursor(FALSE, FALSE);
1356         gui_mch_flush();
1357 #endif
1358 #endif // FEAT_DND
1359     } else if (DropStringMsgID == msgid) {
1360 #ifdef FEAT_DND
1361         char_u  dropkey[3] = { CSI, KS_EXTRA, (char_u)KE_DROP };
1362         const void *bytes = [data bytes];
1363         int len = *((int*)bytes);  bytes += sizeof(int);
1364         NSMutableString *string = [NSMutableString stringWithUTF8String:bytes];
1366         // Replace unrecognized end-of-line sequences with \x0a (line feed).
1367         NSRange range = { 0, [string length] };
1368         unsigned n = [string replaceOccurrencesOfString:@"\x0d\x0a"
1369                                              withString:@"\x0a" options:0
1370                                                   range:range];
1371         if (0 == n) {
1372             n = [string replaceOccurrencesOfString:@"\x0d" withString:@"\x0a"
1373                                            options:0 range:range];
1374         }
1376         len = [string lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
1377         dnd_yank_drag_data((char_u*)[string UTF8String], len);
1378         add_to_input_buf(dropkey, sizeof(dropkey));
1379 #endif // FEAT_DND
1380     } else if (GotFocusMsgID == msgid) {
1381         if (!gui.in_focus)
1382             [self focusChange:YES];
1383     } else if (LostFocusMsgID == msgid) {
1384         if (gui.in_focus)
1385             [self focusChange:NO];
1386     } else if (MouseMovedMsgID == msgid) {
1387         const void *bytes = [data bytes];
1388         int row = *((int*)bytes);  bytes += sizeof(int);
1389         int col = *((int*)bytes);  bytes += sizeof(int);
1391         gui_mouse_moved(col, row);
1392     } else if (SetMouseShapeMsgID == msgid) {
1393         const void *bytes = [data bytes];
1394         int shape = *((int*)bytes);  bytes += sizeof(int);
1395         update_mouseshape(shape);
1396     } else if (ServerAddInputMsgID == msgid) {
1397         const void *bytes = [data bytes];
1398         /*int len = *((int*)bytes);*/  bytes += sizeof(int);
1399         char_u *cmd = (char_u*)bytes;
1401         server_to_input_buf(cmd);
1402     } else {
1403         NSLog(@"WARNING: Unknown message received (msgid=%d)", msgid);
1404     }
1407 + (NSDictionary *)specialKeys
1409     static NSDictionary *specialKeys = nil;
1411     if (!specialKeys) {
1412         NSBundle *mainBundle = [NSBundle mainBundle];
1413         NSString *path = [mainBundle pathForResource:@"SpecialKeys"
1414                                               ofType:@"plist"];
1415         specialKeys = [[NSDictionary alloc] initWithContentsOfFile:path];
1416     }
1418     return specialKeys;
1421 - (void)handleKeyDown:(NSString *)key modifiers:(int)mods
1423     char_u special[3];
1424     char_u modChars[3];
1425     char_u *chars = 0;
1426     int length = 0;
1428     // Special keys (arrow keys, function keys, etc.) are stored in a plist so
1429     // that new keys can easily be added.
1430     NSString *specialString = [[MMBackend specialKeys]
1431             objectForKey:key];
1432     if (specialString && [specialString length] > 1) {
1433         //NSLog(@"special key: %@", specialString);
1434         int ikey = TO_SPECIAL([specialString characterAtIndex:0],
1435                 [specialString characterAtIndex:1]);
1437         ikey = simplify_key(ikey, &mods);
1438         if (ikey == CSI)
1439             ikey = K_CSI;
1441         special[0] = CSI;
1442         special[1] = K_SECOND(ikey);
1443         special[2] = K_THIRD(ikey);
1445         chars = special;
1446         length = 3;
1447     } else if ([key length] > 0) {
1448         chars = (char_u*)[key UTF8String];
1449         length = [key lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
1450         unichar c = [key characterAtIndex:0];
1452         //NSLog(@"non-special: %@ (hex=%x, mods=%d)", key,
1453         //        [key characterAtIndex:0], mods);
1455         if (length == 1 && ((c == Ctrl_C && ctrl_c_interrupts)
1456                 || (c == intr_char && intr_char != Ctrl_C))) {
1457             trash_input_buf();
1458             got_int = TRUE;
1459         }
1461         // HACK!  In most circumstances the Ctrl and Shift modifiers should be
1462         // cleared since they are already added to the key by the AppKit.
1463         // Unfortunately, the only way to deal with when to clear the modifiers
1464         // or not seems to be to have hard-wired rules like this.
1465         if ( !((' ' == c) || (0xa0 == c) || (mods & MOD_MASK_CMD)) ) {
1466             mods &= ~MOD_MASK_SHIFT;
1467             mods &= ~MOD_MASK_CTRL;
1468             //NSLog(@"clear shift ctrl");
1469         }
1471         // HACK!  All Option+key presses go via 'insert text' messages, except
1472         // for <M-Space>.  If the Alt flag is not cleared for <M-Space> it does
1473         // not work to map to it.
1474         if (0xa0 == c && !(mods & MOD_MASK_CMD)) {
1475             //NSLog(@"clear alt");
1476             mods &= ~MOD_MASK_ALT;
1477         }
1478     }
1480     if (chars && length > 0) {
1481         if (mods) {
1482             //NSLog(@"adding mods: %d", mods);
1483             modChars[0] = CSI;
1484             modChars[1] = KS_MODIFIER;
1485             modChars[2] = mods;
1486             add_to_input_buf(modChars, 3);
1487         }
1489         //NSLog(@"add to input buf: 0x%x", chars[0]);
1490         // TODO: Check for CSI bytes?
1491         add_to_input_buf(chars, length);
1492     }
1495 - (void)queueMessage:(int)msgid data:(NSData *)data
1497     [queue addObject:[NSData dataWithBytes:&msgid length:sizeof(int)]];
1498     if (data)
1499         [queue addObject:data];
1500     else
1501         [queue addObject:[NSData data]];
1504 - (void)connectionDidDie:(NSNotification *)notification
1506     // If the main connection to MacVim is lost this means that MacVim was
1507     // either quit (by the user chosing Quit on the MacVim menu), or it has
1508     // crashed.  In either case our only option is to quit now.
1509     // TODO: Write backup file?
1511     //NSLog(@"A Vim process lots its connection to MacVim; quitting.");
1512     getout(0);
1515 - (void)blinkTimerFired:(NSTimer *)timer
1517     NSTimeInterval timeInterval = 0;
1519     [blinkTimer release];
1520     blinkTimer = nil;
1522     if (MMBlinkStateOn == blinkState) {
1523         gui_undraw_cursor();
1524         blinkState = MMBlinkStateOff;
1525         timeInterval = blinkOffInterval;
1526     } else if (MMBlinkStateOff == blinkState) {
1527         gui_update_cursor(TRUE, FALSE);
1528         blinkState = MMBlinkStateOn;
1529         timeInterval = blinkOnInterval;
1530     }
1532     if (timeInterval > 0) {
1533         blinkTimer = 
1534             [[NSTimer scheduledTimerWithTimeInterval:timeInterval target:self
1535                                             selector:@selector(blinkTimerFired:)
1536                                             userInfo:nil repeats:NO] retain];
1537         [self flushQueue:YES];
1538     }
1541 - (void)focusChange:(BOOL)on
1543     gui_focus_change(on);
1546 - (void)processInputBegin
1548     inProcessInput = YES;
1549     [lastFlushDate release];
1550     lastFlushDate = [[NSDate date] retain];
1553 - (void)processInputEnd
1555 #if MM_USE_INPUT_QUEUE
1556     int count = [inputQueue count];
1557     if (count % 2) {
1558         // TODO: This is troubling, but it is not hard to get Vim to end up
1559         // here.  Why does this happen?
1560         NSLog(@"WARNING: inputQueue has odd number of objects (%d)", count);
1561         [inputQueue removeAllObjects];
1562     } else if (count > 0) {
1563         // TODO: Dispatch these messages?  Maybe not; usually when the
1564         // 'inputQueue' is non-empty it means that a LOT of messages has been
1565         // sent simultaneously.  The only way this happens is when Vim is being
1566         // tormented, e.g. if the user holds down <D-`> to rapidly switch
1567         // windows.
1568         unsigned i;
1569         for (i = 0; i < count; i+=2) {
1570             int msgid = [[inputQueue objectAtIndex:i] intValue];
1571             NSLog(@"%s: Dropping message %s", _cmd, MessageStrings[msgid]);
1572         }
1574         [inputQueue removeAllObjects];
1575     }
1576 #endif
1578 #if 0 // This does not work...for now, just don't care if a focus msg was lost.
1579     // HACK! A focus message might get lost, but whenever we get here the GUI
1580     // is in focus.
1581     if (!gui.in_focus)
1582         [self focusChange:TRUE];
1583 #endif
1585     inputReceived = YES;
1586     inProcessInput = NO;
1589 @end // MMBackend (Private)
1594 static int eventModifierFlagsToVimModMask(int modifierFlags)
1596     int modMask = 0;
1598     if (modifierFlags & NSShiftKeyMask)
1599         modMask |= MOD_MASK_SHIFT;
1600     if (modifierFlags & NSControlKeyMask)
1601         modMask |= MOD_MASK_CTRL;
1602     if (modifierFlags & NSAlternateKeyMask)
1603         modMask |= MOD_MASK_ALT;
1604     if (modifierFlags & NSCommandKeyMask)
1605         modMask |= MOD_MASK_CMD;
1607     return modMask;
1610 static int vimModMaskToEventModifierFlags(int mods)
1612     int flags = 0;
1614     if (mods & MOD_MASK_SHIFT)
1615         flags |= NSShiftKeyMask;
1616     if (mods & MOD_MASK_CTRL)
1617         flags |= NSControlKeyMask;
1618     if (mods & MOD_MASK_ALT)
1619         flags |= NSAlternateKeyMask;
1620     if (mods & MOD_MASK_CMD)
1621         flags |= NSCommandKeyMask;
1623     return flags;
1626 static int eventModifierFlagsToVimMouseModMask(int modifierFlags)
1628     int modMask = 0;
1630     if (modifierFlags & NSShiftKeyMask)
1631         modMask |= MOUSE_SHIFT;
1632     if (modifierFlags & NSControlKeyMask)
1633         modMask |= MOUSE_CTRL;
1634     if (modifierFlags & NSAlternateKeyMask)
1635         modMask |= MOUSE_ALT;
1637     return modMask;
1640 static int eventButtonNumberToVimMouseButton(int buttonNumber)
1642     static int mouseButton[] = { MOUSE_LEFT, MOUSE_RIGHT, MOUSE_MIDDLE,
1643             MOUSE_X1, MOUSE_X2 };
1645     return mouseButton[buttonNumber < 5 ? buttonNumber : 0];
1648 static int specialKeyToNSKey(int key)
1650     if (!IS_SPECIAL(key))
1651         return key;
1653     static struct {
1654         int special;
1655         int nskey;
1656     } sp2ns[] = {
1657         { K_UP, NSUpArrowFunctionKey },
1658         { K_DOWN, NSDownArrowFunctionKey },
1659         { K_LEFT, NSLeftArrowFunctionKey },
1660         { K_RIGHT, NSRightArrowFunctionKey },
1661         { K_F1, NSF1FunctionKey },
1662         { K_F2, NSF2FunctionKey },
1663         { K_F3, NSF3FunctionKey },
1664         { K_F4, NSF4FunctionKey },
1665         { K_F5, NSF5FunctionKey },
1666         { K_F6, NSF6FunctionKey },
1667         { K_F7, NSF7FunctionKey },
1668         { K_F8, NSF8FunctionKey },
1669         { K_F9, NSF9FunctionKey },
1670         { K_F10, NSF10FunctionKey },
1671         { K_F11, NSF11FunctionKey },
1672         { K_F12, NSF12FunctionKey },
1673         { K_F13, NSF13FunctionKey },
1674         { K_F14, NSF14FunctionKey },
1675         { K_F15, NSF15FunctionKey },
1676         { K_F16, NSF16FunctionKey },
1677         { K_F17, NSF17FunctionKey },
1678         { K_F18, NSF18FunctionKey },
1679         { K_F19, NSF19FunctionKey },
1680         { K_F20, NSF20FunctionKey },
1681         { K_F21, NSF21FunctionKey },
1682         { K_F22, NSF22FunctionKey },
1683         { K_F23, NSF23FunctionKey },
1684         { K_F24, NSF24FunctionKey },
1685         { K_F25, NSF25FunctionKey },
1686         { K_F26, NSF26FunctionKey },
1687         { K_F27, NSF27FunctionKey },
1688         { K_F28, NSF28FunctionKey },
1689         { K_F29, NSF29FunctionKey },
1690         { K_F30, NSF30FunctionKey },
1691         { K_F31, NSF31FunctionKey },
1692         { K_F32, NSF32FunctionKey },
1693         { K_F33, NSF33FunctionKey },
1694         { K_F34, NSF34FunctionKey },
1695         { K_F35, NSF35FunctionKey },
1696         { K_DEL, NSBackspaceCharacter },
1697         { K_BS, NSDeleteCharacter },
1698         { K_HOME, NSHomeFunctionKey },
1699         { K_END, NSEndFunctionKey },
1700         { K_PAGEUP, NSPageUpFunctionKey },
1701         { K_PAGEDOWN, NSPageDownFunctionKey }
1702     };
1704     int i;
1705     for (i = 0; i < sizeof(sp2ns)/sizeof(sp2ns[0]); ++i) {
1706         if (sp2ns[i].special == key)
1707             return sp2ns[i].nskey;
1708     }
1710     return 0;