git-svn-id: http://macvim.googlecode.com/svn/trunk@220 96c4425d-ca35-0410-94e5-3396d5...
[MacVim/jjgod.git] / MMBackend.m
blobd2fe34de58771a7119e82eceb75c0f80acd8c4fa
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 - (BOOL)checkin
140     NSBundle *mainBundle = [NSBundle mainBundle];
142     // NOTE!  If the name of the connection changes here it must also be
143     // updated in MMAppController.m.
144     NSString *name = [NSString stringWithFormat:@"%@-connection",
145              [mainBundle bundleIdentifier]];
146     connection = [NSConnection connectionWithRegisteredName:name host:nil];
147     if (!connection) {
148 #if 0
149         NSString *path = [mainBundle bundlePath];
150         if (![[NSWorkspace sharedWorkspace] launchApplication:path]) {
151             NSLog(@"WARNING: Failed to launch GUI with path %@", path);
152             return NO;
153         }
154 #else
155         // HACK!  It would be preferable to launch the GUI using NSWorkspace,
156         // however I have not managed to figure out how to pass arguments using
157         // NSWorkspace.
158         //
159         // NOTE!  Using NSTask to launch the GUI has the negative side-effect
160         // that the GUI won't be activated (or raised) so there is a hack in
161         // MMWindowController which always raises the app when a new window is
162         // opened.
163         NSMutableArray *args = [NSMutableArray arrayWithObjects:
164             [NSString stringWithFormat:@"-%@", MMNoWindowKey], @"yes", nil];
165         NSString *exeName = [[mainBundle infoDictionary]
166                 objectForKey:@"CFBundleExecutable"];
167         NSString *path = [mainBundle pathForAuxiliaryExecutable:exeName];
168         if (!path) {
169             NSLog(@"ERROR: Could not find MacVim executable in bundle");
170             return NO;
171         }
173         [NSTask launchedTaskWithLaunchPath:path arguments:args];
174 #endif
176         // HACK!  The NSWorkspaceDidLaunchApplicationNotification does not work
177         // for tasks like this, so poll the mach bootstrap server until it
178         // returns a valid connection.  Also set a time-out date so that we
179         // don't get stuck doing this forever.
180         NSDate *timeOutDate = [NSDate dateWithTimeIntervalSinceNow:15];
181         while (!connection &&
182                 NSOrderedDescending == [timeOutDate compare:[NSDate date]])
183         {
184             [[NSRunLoop currentRunLoop]
185                     runMode:NSDefaultRunLoopMode
186                  beforeDate:[NSDate dateWithTimeIntervalSinceNow:1]];
188             connection = [NSConnection connectionWithRegisteredName:name
189                                                                host:nil];
190         }
192         if (!connection) {
193             NSLog(@"WARNING: Timed-out waiting for GUI to launch.");
194             return NO;
195         }
196     }
198     id proxy = [connection rootProxy];
199     [proxy setProtocolForProxy:@protocol(MMAppProtocol)];
201     [[NSNotificationCenter defaultCenter] addObserver:self
202             selector:@selector(connectionDidDie:)
203                 name:NSConnectionDidDieNotification object:connection];
205     int pid = [[NSProcessInfo processInfo] processIdentifier];
207     @try {
208         frontendProxy = [(NSDistantObject*)[proxy connectBackend:self
209                                                              pid:pid] retain];
210     }
211     @catch (NSException *e) {
212         NSLog(@"Exception caught when trying to connect backend: \"%@\"", e);
213     }
215     if (frontendProxy) {
216         [frontendProxy setProtocolForProxy:@protocol(MMAppProtocol)];
217     }
219     return connection && frontendProxy;
222 - (BOOL)openVimWindow
224     [self queueMessage:OpenVimWindowMsgID data:nil];
225     return YES;
228 - (void)clearAll
230     int type = ClearAllDrawType;
232     // Any draw commands in queue are effectively obsolete since this clearAll
233     // will negate any effect they have, therefore we may as well clear the
234     // draw queue.
235     [drawData setLength:0];
237     [drawData appendBytes:&type length:sizeof(int)];
239     [drawData appendBytes:&defaultBackgroundColor length:sizeof(int)];
242 - (void)clearBlockFromRow:(int)row1 column:(int)col1
243                     toRow:(int)row2 column:(int)col2
245     int type = ClearBlockDrawType;
247     [drawData appendBytes:&type length:sizeof(int)];
249     [drawData appendBytes:&defaultBackgroundColor length:sizeof(int)];
250     [drawData appendBytes:&row1 length:sizeof(int)];
251     [drawData appendBytes:&col1 length:sizeof(int)];
252     [drawData appendBytes:&row2 length:sizeof(int)];
253     [drawData appendBytes:&col2 length:sizeof(int)];
256 - (void)deleteLinesFromRow:(int)row count:(int)count
257               scrollBottom:(int)bottom left:(int)left right:(int)right
259     int type = DeleteLinesDrawType;
261     [drawData appendBytes:&type length:sizeof(int)];
263     [drawData appendBytes:&defaultBackgroundColor length:sizeof(int)];
264     [drawData appendBytes:&row length:sizeof(int)];
265     [drawData appendBytes:&count length:sizeof(int)];
266     [drawData appendBytes:&bottom length:sizeof(int)];
267     [drawData appendBytes:&left length:sizeof(int)];
268     [drawData appendBytes:&right length:sizeof(int)];
271 - (void)replaceString:(char*)s length:(int)len row:(int)row column:(int)col
272                 flags:(int)flags
274     if (len <= 0) return;
276     int type = ReplaceStringDrawType;
278     [drawData appendBytes:&type length:sizeof(int)];
280     [drawData appendBytes:&backgroundColor length:sizeof(int)];
281     [drawData appendBytes:&foregroundColor length:sizeof(int)];
282     [drawData appendBytes:&specialColor length:sizeof(int)];
283     [drawData appendBytes:&row length:sizeof(int)];
284     [drawData appendBytes:&col length:sizeof(int)];
285     [drawData appendBytes:&flags length:sizeof(int)];
286     [drawData appendBytes:&len length:sizeof(int)];
287     [drawData appendBytes:s length:len];
290 - (void)insertLinesFromRow:(int)row count:(int)count
291               scrollBottom:(int)bottom left:(int)left right:(int)right
293     int type = InsertLinesDrawType;
295     [drawData appendBytes:&type length:sizeof(int)];
297     [drawData appendBytes:&defaultBackgroundColor length:sizeof(int)];
298     [drawData appendBytes:&row length:sizeof(int)];
299     [drawData appendBytes:&count length:sizeof(int)];
300     [drawData appendBytes:&bottom length:sizeof(int)];
301     [drawData appendBytes:&left length:sizeof(int)];
302     [drawData appendBytes:&right length:sizeof(int)];
305 - (void)drawCursorAtRow:(int)row column:(int)col shape:(int)shape
306                fraction:(int)percent color:(int)color
308     int type = DrawCursorDrawType;
310     [drawData appendBytes:&type length:sizeof(int)];
312     [drawData appendBytes:&color length:sizeof(int)];
313     [drawData appendBytes:&row length:sizeof(int)];
314     [drawData appendBytes:&col length:sizeof(int)];
315     [drawData appendBytes:&shape length:sizeof(int)];
316     [drawData appendBytes:&percent length:sizeof(int)];
319 - (void)flushQueue:(BOOL)force
321     // NOTE! This method gets called a lot; if we were to flush every time it
322     // was called MacVim would feel unresponsive.  So there is a time out which
323     // ensures that the queue isn't flushed too often.
324     if (!force && lastFlushDate && -[lastFlushDate timeIntervalSinceNow]
325             < MMFlushTimeoutInterval)
326         return;
328     if ([drawData length] > 0) {
329         [self queueMessage:BatchDrawMsgID data:[drawData copy]];
330         [drawData setLength:0];
331     }
333     if ([queue count] > 0) {
334         @try {
335             [frontendProxy processCommandQueue:queue];
336         }
337         @catch (NSException *e) {
338             NSLog(@"Exception caught when processing command queue: \"%@\"", e);
339         }
341         [queue removeAllObjects];
343         [lastFlushDate release];
344         lastFlushDate = [[NSDate date] retain];
345     }
348 - (BOOL)waitForInput:(int)milliseconds
350     NSDate *date = milliseconds > 0 ?
351             [NSDate dateWithTimeIntervalSinceNow:.001*milliseconds] : 
352             [NSDate distantFuture];
354     [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:date];
356     // I know of no way to figure out if the run loop exited because input was
357     // found or because of a time out, so I need to manually indicate when
358     // input was received in processInput:data: and then reset it every time
359     // here.
360     BOOL yn = inputReceived;
361     inputReceived = NO;
363     return yn;
366 - (void)exit
368     // By invalidating the NSConnection the MMWindowController immediately
369     // finds out that the connection is down and as a result
370     // [MMWindowController connectionDidDie:] is invoked.
371     //NSLog(@"%@ %s", [self className], _cmd);
372     [[NSNotificationCenter defaultCenter] removeObserver:self];
373     [connection invalidate];
376 - (void)selectTab:(int)index
378     //NSLog(@"%s%d", _cmd, index);
380     index -= 1;
381     NSData *data = [NSData dataWithBytes:&index length:sizeof(int)];
382     [self queueMessage:SelectTabMsgID data:data];
385 - (void)updateTabBar
387     //NSLog(@"%s", _cmd);
389     NSMutableData *data = [NSMutableData data];
391     int idx = tabpage_index(curtab) - 1;
392     [data appendBytes:&idx length:sizeof(int)];
394     tabpage_T *tp;
395     for (tp = first_tabpage; tp != NULL; tp = tp->tp_next) {
396         // This function puts the label of the tab in the global 'NameBuff'.
397         get_tabline_label(tp, FALSE);
398         int len = strlen((char*)NameBuff);
399         if (len <= 0) continue;
401         // Count the number of windows in the tabpage.
402         //win_T *wp = tp->tp_firstwin;
403         //int wincount;
404         //for (wincount = 0; wp != NULL; wp = wp->w_next, ++wincount);
406         //[data appendBytes:&wincount length:sizeof(int)];
407         [data appendBytes:&len length:sizeof(int)];
408         [data appendBytes:NameBuff length:len];
409     }
411     [self queueMessage:UpdateTabBarMsgID data:data];
414 - (BOOL)tabBarVisible
416     return tabBarVisible;
419 - (void)showTabBar:(BOOL)enable
421     tabBarVisible = enable;
423     int msgid = enable ? ShowTabBarMsgID : HideTabBarMsgID;
424     [self queueMessage:msgid data:nil];
427 - (void)setRows:(int)rows columns:(int)cols
429     //NSLog(@"[VimTask] setRows:%d columns:%d", rows, cols);
431     int dim[] = { rows, cols };
432     NSData *data = [NSData dataWithBytes:&dim length:2*sizeof(int)];
434     [self queueMessage:SetTextDimensionsMsgID data:data];
437 - (void)setVimWindowTitle:(char *)title
439     NSMutableData *data = [NSMutableData data];
440     int len = strlen(title);
441     if (len <= 0) return;
443     [data appendBytes:&len length:sizeof(int)];
444     [data appendBytes:title length:len];
446     [self queueMessage:SetVimWindowTitleMsgID data:data];
449 - (char *)browseForFileInDirectory:(char *)dir title:(char *)title
450                             saving:(int)saving
452     //NSLog(@"browseForFileInDirectory:%s title:%s saving:%d", dir, title,
453     //        saving);
455     char_u *s = NULL;
456     NSString *ds = dir
457             ? [NSString stringWithCString:dir encoding:NSUTF8StringEncoding]
458             : nil;
459     NSString *ts = title
460             ? [NSString stringWithCString:title encoding:NSUTF8StringEncoding]
461             : nil;
462     @try {
463         [frontendProxy showSavePanelForDirectory:ds title:ts saving:saving];
465         // Wait until a reply is sent from MMVimController.
466         [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
467                                  beforeDate:[NSDate distantFuture]];
469         if (dialogReturn && [dialogReturn isKindOfClass:[NSString class]]) {
470             s = vim_strsave((char_u*)[dialogReturn UTF8String]);
471         }
473         [dialogReturn release];  dialogReturn = nil;
474     }
475     @catch (NSException *e) {
476         NSLog(@"Exception caught when showing save panel: \"%@\"", e);
477     }
479     return (char *)s;
482 - (oneway void)setDialogReturn:(in bycopy id)obj
484     // NOTE: This is called by
485     //   - [MMVimController panelDidEnd:::], and
486     //   - [MMVimController alertDidEnd:::],
487     // to indicate that a save/open panel or alert has finished.
489     if (obj != dialogReturn) {
490         [dialogReturn release];
491         dialogReturn = [obj retain];
492     }
495 - (int)presentDialogWithType:(int)type title:(char *)title message:(char *)msg
496                      buttons:(char *)btns textField:(char *)txtfield
498     int retval = 0;
499     NSString *message = nil, *text = nil, *textFieldString = nil;
500     NSArray *buttons = nil;
501     int style = NSInformationalAlertStyle;
503     if (VIM_WARNING == type) style = NSWarningAlertStyle;
504     else if (VIM_ERROR == type) style = NSCriticalAlertStyle;
506     if (btns) {
507         NSString *btnString = [NSString stringWithUTF8String:btns];
508         buttons = [btnString componentsSeparatedByString:@"\n"];
509     }
510     if (title)
511         message = [NSString stringWithUTF8String:title];
512     if (msg) {
513         text = [NSString stringWithUTF8String:msg];
514         if (!message) {
515             // HACK! If there is a '\n\n' or '\n' sequence in the message, then
516             // make the part up to there into the title.  We only do this
517             // because Vim has lots of dialogs without a title and they look
518             // ugly that way.
519             // TODO: Fix the actual dialog texts.
520             NSRange eolRange = [text rangeOfString:@"\n\n"];
521             if (NSNotFound == eolRange.location)
522                 eolRange = [text rangeOfString:@"\n"];
523             if (NSNotFound != eolRange.location) {
524                 message = [text substringToIndex:eolRange.location];
525                 text = [text substringFromIndex:NSMaxRange(eolRange)];
526             }
527         }
528     }
529     if (txtfield)
530         textFieldString = [NSString stringWithUTF8String:txtfield];
532     @try {
533         [frontendProxy presentDialogWithStyle:style message:message
534                               informativeText:text buttonTitles:buttons
535                               textFieldString:textFieldString];
537         // Wait until a reply is sent from MMVimController.
538         [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
539                                  beforeDate:[NSDate distantFuture]];
541         if (dialogReturn && [dialogReturn isKindOfClass:[NSArray class]]
542                 && [dialogReturn count]) {
543             retval = [[dialogReturn objectAtIndex:0] intValue];
544             if (txtfield && [dialogReturn count] > 1) {
545                 NSString *retString = [dialogReturn objectAtIndex:1];
546                 vim_strncpy((char_u*)txtfield, (char_u*)[retString UTF8String],
547                         IOSIZE - 1);
548             }
549         }
551         [dialogReturn release]; dialogReturn = nil;
552     }
553     @catch (NSException *e) {
554         NSLog(@"Exception caught while showing alert dialog: \"%@\"", e);
555     }
557     return retval;
560 - (void)addMenuWithTag:(int)tag parent:(int)parentTag name:(char *)name
561                atIndex:(int)index
563     //NSLog(@"addMenuWithTag:%d parent:%d name:%s atIndex:%d", tag, parentTag,
564     //        name, index);
566     int namelen = name ? strlen(name) : 0;
567     NSMutableData *data = [NSMutableData data];
569     [data appendBytes:&tag length:sizeof(int)];
570     [data appendBytes:&parentTag length:sizeof(int)];
571     [data appendBytes:&namelen length:sizeof(int)];
572     if (namelen > 0) [data appendBytes:name length:namelen];
573     [data appendBytes:&index length:sizeof(int)];
575     [self queueMessage:AddMenuMsgID data:data];
578 - (void)addMenuItemWithTag:(int)tag parent:(int)parentTag name:(char *)name
579                        tip:(char *)tip icon:(char *)icon
580              keyEquivalent:(int)key modifiers:(int)mods
581                     action:(NSString *)action atIndex:(int)index
583     //NSLog(@"addMenuItemWithTag:%d parent:%d name:%s tip:%s atIndex:%d", tag,
584     //        parentTag, name, tip, index);
586     int namelen = name ? strlen(name) : 0;
587     int tiplen = tip ? strlen(tip) : 0;
588     int iconlen = icon ? strlen(icon) : 0;
589     int eventFlags = vimModMaskToEventModifierFlags(mods);
590     int actionlen = [action lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
591     NSMutableData *data = [NSMutableData data];
593     key = specialKeyToNSKey(key);
595     [data appendBytes:&tag length:sizeof(int)];
596     [data appendBytes:&parentTag length:sizeof(int)];
597     [data appendBytes:&namelen length:sizeof(int)];
598     if (namelen > 0) [data appendBytes:name length:namelen];
599     [data appendBytes:&tiplen length:sizeof(int)];
600     if (tiplen > 0) [data appendBytes:tip length:tiplen];
601     [data appendBytes:&iconlen length:sizeof(int)];
602     if (iconlen > 0) [data appendBytes:icon length:iconlen];
603     [data appendBytes:&actionlen length:sizeof(int)];
604     if (actionlen > 0) [data appendBytes:[action UTF8String] length:actionlen];
605     [data appendBytes:&index length:sizeof(int)];
606     [data appendBytes:&key length:sizeof(int)];
607     [data appendBytes:&eventFlags length:sizeof(int)];
609     [self queueMessage:AddMenuItemMsgID data:data];
612 - (void)removeMenuItemWithTag:(int)tag
614     NSMutableData *data = [NSMutableData data];
615     [data appendBytes:&tag length:sizeof(int)];
617     [self queueMessage:RemoveMenuItemMsgID data:data];
620 - (void)enableMenuItemWithTag:(int)tag state:(int)enabled
622     NSMutableData *data = [NSMutableData data];
624     [data appendBytes:&tag length:sizeof(int)];
625     [data appendBytes:&enabled length:sizeof(int)];
627     [self queueMessage:EnableMenuItemMsgID data:data];
630 - (void)showPopupMenuWithName:(char *)name atMouseLocation:(BOOL)mouse
632     int len = strlen(name);
633     int row = -1, col = -1;
635     if (len <= 0) return;
637     if (!mouse && curwin) {
638         row = curwin->w_wrow;
639         col = curwin->w_wcol;
640     }
642     NSMutableData *data = [NSMutableData data];
644     [data appendBytes:&row length:sizeof(int)];
645     [data appendBytes:&col length:sizeof(int)];
646     [data appendBytes:&len length:sizeof(int)];
647     [data appendBytes:name length:len];
649     [self queueMessage:ShowPopupMenuMsgID data:data];
652 - (void)showToolbar:(int)enable flags:(int)flags
654     NSMutableData *data = [NSMutableData data];
656     [data appendBytes:&enable length:sizeof(int)];
657     [data appendBytes:&flags length:sizeof(int)];
659     [self queueMessage:ShowToolbarMsgID data:data];
662 - (void)createScrollbarWithIdentifier:(long)ident type:(int)type
664     NSMutableData *data = [NSMutableData data];
666     [data appendBytes:&ident length:sizeof(long)];
667     [data appendBytes:&type length:sizeof(int)];
669     [self queueMessage:CreateScrollbarMsgID data:data];
672 - (void)destroyScrollbarWithIdentifier:(long)ident
674     NSMutableData *data = [NSMutableData data];
675     [data appendBytes:&ident length:sizeof(long)];
677     [self queueMessage:DestroyScrollbarMsgID data:data];
680 - (void)showScrollbarWithIdentifier:(long)ident state:(int)visible
682     NSMutableData *data = [NSMutableData data];
684     [data appendBytes:&ident length:sizeof(long)];
685     [data appendBytes:&visible length:sizeof(int)];
687     [self queueMessage:ShowScrollbarMsgID data:data];
690 - (void)setScrollbarPosition:(int)pos length:(int)len identifier:(long)ident
692     NSMutableData *data = [NSMutableData data];
694     [data appendBytes:&ident length:sizeof(long)];
695     [data appendBytes:&pos length:sizeof(int)];
696     [data appendBytes:&len length:sizeof(int)];
698     [self queueMessage:SetScrollbarPositionMsgID data:data];
701 - (void)setScrollbarThumbValue:(long)val size:(long)size max:(long)max
702                     identifier:(long)ident
704     float fval = max-size+1 > 0 ? (float)val/(max-size+1) : 0;
705     float prop = (float)size/(max+1);
706     if (fval < 0) fval = 0;
707     else if (fval > 1.0f) fval = 1.0f;
708     if (prop < 0) prop = 0;
709     else if (prop > 1.0f) prop = 1.0f;
711     NSMutableData *data = [NSMutableData data];
713     [data appendBytes:&ident length:sizeof(long)];
714     [data appendBytes:&fval length:sizeof(float)];
715     [data appendBytes:&prop length:sizeof(float)];
717     [self queueMessage:SetScrollbarThumbMsgID data:data];
720 - (BOOL)setFontWithName:(char *)name
722     NSString *fontName;
723     float size = 0.0f;
724     BOOL parseFailed = NO;
726     if (name) {
727         fontName = [[[NSString alloc] initWithCString:name
728                 encoding:NSUTF8StringEncoding] autorelease];
729         NSArray *components = [fontName componentsSeparatedByString:@":"];
730         if ([components count] == 2) {
731             NSString *sizeString = [components lastObject];
732             if ([sizeString length] > 0
733                     && [sizeString characterAtIndex:0] == 'h') {
734                 sizeString = [sizeString substringFromIndex:1];
735                 if ([sizeString length] > 0) {
736                     size = [sizeString floatValue];
737                     fontName = [components objectAtIndex:0];
738                 }
739             } else {
740                 parseFailed = YES;
741             }
742         } else if ([components count] > 2) {
743             parseFailed = YES;
744         }
745     } else {
746         fontName = [[NSFont userFixedPitchFontOfSize:0] displayName];
747     }
749     if (!parseFailed && [fontName length] > 0) {
750         if (size < 6 || size > 100) {
751             // Font size 0.0 tells NSFont to use the 'user default size'.
752             size = 0.0f;
753         }
755         NSFont *font = [NSFont fontWithName:fontName size:size];
756         if (font) {
757             //NSLog(@"Setting font '%@' of size %.2f", fontName, size);
758             int len = [fontName
759                     lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
760             if (len > 0) {
761                 NSMutableData *data = [NSMutableData data];
763                 [data appendBytes:&size length:sizeof(float)];
764                 [data appendBytes:&len length:sizeof(int)];
765                 [data appendBytes:[fontName UTF8String] length:len];
767                 [self queueMessage:SetFontMsgID data:data];
768                 return YES;
769             }
770         }
771     }
773     NSLog(@"WARNING: Cannot set font with name '%@' of size %.2f",
774             fontName, size);
775     return NO;
778 - (void)executeActionWithName:(NSString *)name
780     int len = [name lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
782     if (len > 0) {
783         NSMutableData *data = [NSMutableData data];
785         [data appendBytes:&len length:sizeof(int)];
786         [data appendBytes:[name UTF8String] length:len];
788         [self queueMessage:ExecuteActionMsgID data:data];
789     }
792 - (void)setMouseShape:(int)shape
794     NSMutableData *data = [NSMutableData data];
795     [data appendBytes:&shape length:sizeof(int)];
796     [self queueMessage:SetMouseShapeMsgID data:data];
799 - (void)setBlinkWait:(int)wait on:(int)on off:(int)off
801     // Vim specifies times in milliseconds, whereas Cocoa wants them in
802     // seconds.
803     blinkWaitInterval = .001f*wait;
804     blinkOnInterval = .001f*on;
805     blinkOffInterval = .001f*off;
808 - (void)startBlink
810     if (blinkTimer) {
811         [blinkTimer invalidate];
812         [blinkTimer release];
813         blinkTimer = nil;
814     }
816     if (blinkWaitInterval > 0 && blinkOnInterval > 0 && blinkOffInterval > 0
817             && gui.in_focus) {
818         blinkState = MMBlinkStateOn;
819         blinkTimer =
820             [[NSTimer scheduledTimerWithTimeInterval:blinkWaitInterval
821                                               target:self
822                                             selector:@selector(blinkTimerFired:)
823                                             userInfo:nil repeats:NO] retain];
824         gui_update_cursor(TRUE, FALSE);
825         [self flushQueue:YES];
826     }
829 - (void)stopBlink
831     if (MMBlinkStateOff == blinkState) {
832         gui_update_cursor(TRUE, FALSE);
833         [self flushQueue:YES];
834     }
836     blinkState = MMBlinkStateNone;
839 - (int)lookupColorWithKey:(NSString *)key
841     if (!(key && [key length] > 0))
842         return INVALCOLOR;
844     NSString *stripKey = [[[[key lowercaseString]
845         stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]
846             componentsSeparatedByString:@" "]
847                componentsJoinedByString:@""];
849     if (stripKey && [stripKey length] > 0) {
850         // First of all try to lookup key in the color dictionary; note that
851         // all keys in this dictionary are lowercase with no whitespace.
852         id obj = [colorDict objectForKey:stripKey];
853         if (obj) return [obj intValue];
855         // The key was not in the dictionary; is it perhaps of the form
856         // #rrggbb?
857         if ([stripKey length] > 1 && [stripKey characterAtIndex:0] == '#') {
858             NSScanner *scanner = [NSScanner scannerWithString:stripKey];
859             [scanner setScanLocation:1];
860             unsigned hex = 0;
861             if ([scanner scanHexInt:&hex]) {
862                 return (int)hex;
863             }
864         }
866         // As a last resort, check if it is one of the system defined colors.
867         // The keys in this dictionary are also lowercase with no whitespace.
868         obj = [sysColorDict objectForKey:stripKey];
869         if (obj) {
870             NSColor *col = [NSColor performSelector:NSSelectorFromString(obj)];
871             if (col) {
872                 float r, g, b, a;
873                 col = [col colorUsingColorSpaceName:NSCalibratedRGBColorSpace];
874                 [col getRed:&r green:&g blue:&b alpha:&a];
875                 return ((int)(r*255) << 16) + ((int)(g*255) << 8)
876                     + (int)(b*255);
877             }
878         }
879     }
881     NSLog(@"WARNING: No color with key %@ found.", stripKey);
882     return INVALCOLOR;
885 - (BOOL)hasSpecialKeyWithValue:(NSString *)value
887     NSEnumerator *e = [[MMBackend specialKeys] objectEnumerator];
888     id obj;
890     while ((obj = [e nextObject])) {
891         if ([value isEqual:obj])
892             return YES;
893     }
895     return NO;
898 - (oneway void)processInput:(int)msgid data:(in NSData *)data
900     // NOTE: This method might get called whenever the run loop is tended to.
901     // Thus it might get called whilst input is being processed.  Normally this
902     // is not a problem, but if it gets called often then it might become
903     // dangerous.  E.g. when a focus messages is received the screen is redrawn
904     // because the selection color changes and if another focus message is
905     // received whilst the first one is being processed Vim might crash.  To
906     // deal with this problem at the moment, we simply drop messages that are
907     // received while other input is being processed.
908     if (inProcessInput) {
909 #if MM_USE_INPUT_QUEUE
910         [inputQueue addObject:[NSNumber numberWithInt:msgid]];
911         [inputQueue addObject:data];
912 #else
913         // Just drop the input
914         NSLog(@"WARNING: Dropping input in %s", _cmd);
915 #endif
916     } else {
917         [self processInputBegin];
918         [self handleMessage:msgid data:data];
919         [self processInputEnd];
920     }
923 - (oneway void)processInputAndData:(in NSArray *)messages
925     // NOTE: See comment in processInput:data:.
926     unsigned i, count = [messages count];
927     if (count % 2) {
928         NSLog(@"WARNING: [messages count] is odd in %s", _cmd);
929         return;
930     }
932     if (inProcessInput) {
933 #if MM_USE_INPUT_QUEUE
934         [inputQueue addObjectsFromArray:messages];
935 #else
936         // Just drop the input
937         NSLog(@"WARNING: Dropping input in %s", _cmd);
938 #endif
939     } else {
940         [self processInputBegin];
942         for (i = 0; i < count; i += 2) {
943             int msgid = [[messages objectAtIndex:i] intValue];
944             id data = [messages objectAtIndex:i+1];
945             if ([data isEqual:[NSNull null]])
946                 data = nil;
948             [self handleMessage:msgid data:data];
949         }
951         [self processInputEnd];
952     }
955 - (BOOL)checkForModifiedBuffers
957     buf_T *buf;
958     for (buf = firstbuf; buf != NULL; buf = buf->b_next) {
959         if (bufIsChanged(buf)) {
960             return YES;
961         }
962     }
964     return NO;
967 - (BOOL)starRegisterToPasteboard:(byref NSPasteboard *)pboard
969     if (VIsual_active && (State & NORMAL) && clip_star.available) {
970         // If there is no pasteboard, return YES to indicate that there is text
971         // to copy.
972         if (!pboard)
973             return YES;
975         clip_copy_selection();
977         // Get the text to put on the pasteboard.
978         long_u len = 0; char_u *str = 0;
979         int type = clip_convert_selection(&str, &len, &clip_star);
980         if (type < 0)
981             return NO;
982         
983         NSString *string = [[NSString alloc]
984             initWithBytes:str length:len encoding:NSUTF8StringEncoding];
986         NSArray *types = [NSArray arrayWithObject:NSStringPboardType];
987         [pboard declareTypes:types owner:nil];
988         BOOL ok = [pboard setString:string forType:NSStringPboardType];
989     
990         [string release];
991         vim_free(str);
993         return ok;
994     }
996     return NO;
999 @end // MMBackend
1003 @implementation MMBackend (Private)
1005 - (void)handleMessage:(int)msgid data:(NSData *)data
1007     if (InsertTextMsgID == msgid) {
1008         if (!data) return;
1009         NSString *key = [[NSString alloc] initWithData:data
1010                                               encoding:NSUTF8StringEncoding];
1011         char_u *str = (char_u*)[key UTF8String];
1012         int i, len = [key lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
1014 #if MM_ENABLE_CONV
1015         char_u *conv_str = NULL;
1016         if (input_conv.vc_type != CONV_NONE) {
1017             conv_str = string_convert(&input_conv, str, &len);
1018             if (conv_str)
1019                 str = conv_str;
1020         }
1021 #endif
1023         for (i = 0; i < len; ++i) {
1024             add_to_input_buf(str+i, 1);
1025             if (CSI == str[i]) {
1026                 // NOTE: If the converted string contains the byte CSI, then it
1027                 // must be followed by the bytes KS_EXTRA, KE_CSI or things
1028                 // won't work.
1029                 static char_u extra[2] = { KS_EXTRA, KE_CSI };
1030                 add_to_input_buf(extra, 2);
1031             }
1032         }
1034 #if MM_ENABLE_CONV
1035         if (conv_str)
1036             vim_free(conv_str);
1037 #endif
1038         [key release];
1039     } else if (KeyDownMsgID == msgid || CmdKeyMsgID == msgid) {
1040         if (!data) return;
1041         const void *bytes = [data bytes];
1042         int mods = *((int*)bytes);  bytes += sizeof(int);
1043         int len = *((int*)bytes);  bytes += sizeof(int);
1044         NSString *key = [[NSString alloc] initWithBytes:bytes length:len
1045                                               encoding:NSUTF8StringEncoding];
1046         mods = eventModifierFlagsToVimModMask(mods);
1048         [self handleKeyDown:key modifiers:mods];
1050         [key release];
1051     } else if (SelectTabMsgID == msgid) {
1052         if (!data) return;
1053         const void *bytes = [data bytes];
1054         int idx = *((int*)bytes) + 1;
1055         //NSLog(@"Selecting tab %d", idx);
1056         send_tabline_event(idx);
1057     } else if (CloseTabMsgID == msgid) {
1058         if (!data) return;
1059         const void *bytes = [data bytes];
1060         int idx = *((int*)bytes) + 1;
1061         //NSLog(@"Closing tab %d", idx);
1062         send_tabline_menu_event(idx, TABLINE_MENU_CLOSE);
1063     } else if (AddNewTabMsgID == msgid) {
1064         //NSLog(@"Adding new tab");
1065         send_tabline_menu_event(0, TABLINE_MENU_NEW);
1066     } else if (DraggedTabMsgID == msgid) {
1067         if (!data) return;
1068         const void *bytes = [data bytes];
1069         // NOTE! The destination index is 0 based, so do not add 1 to make it 1
1070         // based.
1071         int idx = *((int*)bytes);
1073         tabpage_move(idx);
1074     } else if (ScrollWheelMsgID == msgid) {
1075         if (!data) return;
1076         const void *bytes = [data bytes];
1078         int row = *((int*)bytes);  bytes += sizeof(int);
1079         int col = *((int*)bytes);  bytes += sizeof(int);
1080         int flags = *((int*)bytes);  bytes += sizeof(int);
1081         float dy = *((float*)bytes);  bytes += sizeof(float);
1083         int button = MOUSE_5;
1084         if (dy > 0) button = MOUSE_4;
1086         flags = eventModifierFlagsToVimMouseModMask(flags);
1088         gui_send_mouse_event(button, col, row, NO, flags);
1089     } else if (MouseDownMsgID == msgid) {
1090         if (!data) return;
1091         const void *bytes = [data bytes];
1093         int row = *((int*)bytes);  bytes += sizeof(int);
1094         int col = *((int*)bytes);  bytes += sizeof(int);
1095         int button = *((int*)bytes);  bytes += sizeof(int);
1096         int flags = *((int*)bytes);  bytes += sizeof(int);
1097         int count = *((int*)bytes);  bytes += sizeof(int);
1099         button = eventButtonNumberToVimMouseButton(button);
1100         flags = eventModifierFlagsToVimMouseModMask(flags);
1102         gui_send_mouse_event(button, col, row, 0 != count, flags);
1103     } else if (MouseUpMsgID == msgid) {
1104         if (!data) return;
1105         const void *bytes = [data bytes];
1107         int row = *((int*)bytes);  bytes += sizeof(int);
1108         int col = *((int*)bytes);  bytes += sizeof(int);
1109         int flags = *((int*)bytes);  bytes += sizeof(int);
1111         flags = eventModifierFlagsToVimMouseModMask(flags);
1113         gui_send_mouse_event(MOUSE_RELEASE, col, row, NO, flags);
1114     } else if (MouseDraggedMsgID == msgid) {
1115         if (!data) return;
1116         const void *bytes = [data bytes];
1118         int row = *((int*)bytes);  bytes += sizeof(int);
1119         int col = *((int*)bytes);  bytes += sizeof(int);
1120         int flags = *((int*)bytes);  bytes += sizeof(int);
1122         flags = eventModifierFlagsToVimMouseModMask(flags);
1124         gui_send_mouse_event(MOUSE_DRAG, col, row, NO, flags);
1125     } else if (SetTextDimensionsMsgID == msgid) {
1126         if (!data) return;
1127         const void *bytes = [data bytes];
1128         int rows = *((int*)bytes);  bytes += sizeof(int);
1129         int cols = *((int*)bytes);  bytes += sizeof(int);
1131         // NOTE! Vim doesn't call gui_mch_set_shellsize() after
1132         // gui_resize_shell(), so we have to manually set the rows and columns
1133         // here.  (MacVim doesn't change the rows and columns to avoid
1134         // inconsistent states between Vim and MacVim.)
1135         [self setRows:rows columns:cols];
1137         //NSLog(@"[VimTask] Resizing shell to %dx%d.", cols, rows);
1138         gui_resize_shell(cols, rows);
1139     } else if (ExecuteMenuMsgID == msgid) {
1140         if (!data) return;
1141         const void *bytes = [data bytes];
1142         int tag = *((int*)bytes);  bytes += sizeof(int);
1144         vimmenu_T *menu = (vimmenu_T*)tag;
1145         // TODO!  Make sure 'menu' is a valid menu pointer!
1146         if (menu) {
1147             gui_menu_cb(menu);
1148         }
1149     } else if (ToggleToolbarMsgID == msgid) {
1150         char_u go[sizeof(GO_ALL)+2];
1151         char_u *p;
1152         int len;
1154         STRCPY(go, p_go);
1155         p = vim_strchr(go, GO_TOOLBAR);
1156         len = STRLEN(go);
1158         if (p != NULL) {
1159             char_u *end = go + len;
1160             while (p < end) {
1161                 p[0] = p[1];
1162                 ++p;
1163             }
1164         } else {
1165             go[len] = GO_TOOLBAR;
1166             go[len+1] = NUL;
1167         }
1169         set_option_value((char_u*)"guioptions", 0, go, 0);
1171         // Force screen redraw (does it have to be this complicated?).
1172         redraw_all_later(CLEAR);
1173         update_screen(NOT_VALID);
1174         setcursor();
1175         out_flush();
1176         gui_update_cursor(FALSE, FALSE);
1177         gui_mch_flush();
1178     } else if (ScrollbarEventMsgID == msgid) {
1179         if (!data) return;
1180         const void *bytes = [data bytes];
1181         long ident = *((long*)bytes);  bytes += sizeof(long);
1182         int hitPart = *((int*)bytes);  bytes += sizeof(int);
1183         float fval = *((float*)bytes);  bytes += sizeof(float);
1184         scrollbar_T *sb = gui_find_scrollbar(ident);
1186         if (sb) {
1187             scrollbar_T *sb_info = sb->wp ? &sb->wp->w_scrollbars[0] : sb;
1188             long value = sb_info->value;
1189             long size = sb_info->size;
1190             long max = sb_info->max;
1191             BOOL isStillDragging = NO;
1192             BOOL updateKnob = YES;
1194             switch (hitPart) {
1195             case NSScrollerDecrementPage:
1196                 value -= (size > 2 ? size - 2 : 1);
1197                 break;
1198             case NSScrollerIncrementPage:
1199                 value += (size > 2 ? size - 2 : 1);
1200                 break;
1201             case NSScrollerDecrementLine:
1202                 --value;
1203                 break;
1204             case NSScrollerIncrementLine:
1205                 ++value;
1206                 break;
1207             case NSScrollerKnob:
1208                 isStillDragging = YES;
1209                 // fall through ...
1210             case NSScrollerKnobSlot:
1211                 value = (long)(fval * (max - size + 1));
1212                 // fall through ...
1213             default:
1214                 updateKnob = NO;
1215                 break;
1216             }
1218             //NSLog(@"value %d -> %d", sb_info->value, value);
1219             gui_drag_scrollbar(sb, value, isStillDragging);
1221             if (updateKnob) {
1222                 // Dragging the knob or option+clicking automatically updates
1223                 // the knob position (on the actual NSScroller), so we only
1224                 // need to set the knob position in the other cases.
1225                 if (sb->wp) {
1226                     // Update both the left&right vertical scrollbars.
1227                     long identLeft = sb->wp->w_scrollbars[SBAR_LEFT].ident;
1228                     long identRight = sb->wp->w_scrollbars[SBAR_RIGHT].ident;
1229                     [self setScrollbarThumbValue:value size:size max:max
1230                                       identifier:identLeft];
1231                     [self setScrollbarThumbValue:value size:size max:max
1232                                       identifier:identRight];
1233                 } else {
1234                     // Update the horizontal scrollbar.
1235                     [self setScrollbarThumbValue:value size:size max:max
1236                                       identifier:ident];
1237                 }
1238             }
1239         }
1240     } else if (SetFontMsgID == msgid) {
1241         if (!data) return;
1242         const void *bytes = [data bytes];
1243         float pointSize = *((float*)bytes);  bytes += sizeof(float);
1244         //unsigned len = *((unsigned*)bytes);  bytes += sizeof(unsigned);
1245         bytes += sizeof(unsigned);  // len not used
1247         NSMutableString *name = [NSMutableString stringWithUTF8String:bytes];
1248         [name appendString:[NSString stringWithFormat:@":h%.2f", pointSize]];
1250         set_option_value((char_u*)"gfn", 0, (char_u*)[name UTF8String], 0);
1252         // Force screen redraw (does it have to be this complicated?).
1253         redraw_all_later(CLEAR);
1254         update_screen(NOT_VALID);
1255         setcursor();
1256         out_flush();
1257         gui_update_cursor(FALSE, FALSE);
1258         gui_mch_flush();
1259     } else if (VimShouldCloseMsgID == msgid) {
1260         gui_shell_closed();
1261     } else if (DropFilesMsgID == msgid) {
1262 #ifdef FEAT_DND
1263         const void *bytes = [data bytes];
1264         int n = *((int*)bytes);  bytes += sizeof(int);
1266 #if 0
1267         int row = *((int*)bytes);  bytes += sizeof(int);
1268         int col = *((int*)bytes);  bytes += sizeof(int);
1270         char_u **fnames = (char_u **)alloc(n * sizeof(char_u *));
1271         if (fnames) {
1272             const void *end = [data bytes] + [data length];
1273             int i = 0;
1274             while (bytes < end && i < n) {
1275                 int len = *((int*)bytes);  bytes += sizeof(int);
1276                 fnames[i++] = vim_strnsave((char_u*)bytes, len);
1277                 bytes += len;
1278             }
1280             // NOTE!  This function will free 'fnames'.
1281             gui_handle_drop(col, row, 0, fnames, i < n ? i : n);
1282         }
1283 #else
1284         // HACK!  I'm not sure how to get Vim to open a list of files in tabs,
1285         // so instead I create a ':tab drop' command with all the files to open
1286         // and execute it.
1287         NSMutableString *cmd = (n > 1)
1288                 ? [NSMutableString stringWithString:@":tab drop"]
1289                 : [NSMutableString stringWithString:@":drop"];
1291         const void *end = [data bytes] + [data length];
1292         int i;
1293         for (i = 0; i < n && bytes < end; ++i) {
1294             int len = *((int*)bytes);  bytes += sizeof(int);
1295             NSMutableString *file =
1296                     [NSMutableString stringWithUTF8String:bytes];
1297             [file replaceOccurrencesOfString:@" "
1298                                   withString:@"\\ "
1299                                      options:0
1300                                        range:NSMakeRange(0, [file length])];
1301             bytes += len;
1303             [cmd appendString:@" "];
1304             [cmd appendString:file];
1305         }
1307         // By going to the last tabpage we ensure that the new tabs will appear
1308         // last (if this call is left out, the taborder becomes messy).
1309         goto_tabpage(9999);
1311         do_cmdline_cmd((char_u*)[cmd UTF8String]);
1313         // Force screen redraw (does it have to be this complicated?).
1314         // (This code was taken from the end of gui_handle_drop().)
1315         update_screen(NOT_VALID);
1316         setcursor();
1317         out_flush();
1318         gui_update_cursor(FALSE, FALSE);
1319         gui_mch_flush();
1320 #endif
1321 #endif // FEAT_DND
1322     } else if (DropStringMsgID == msgid) {
1323 #ifdef FEAT_DND
1324         char_u  dropkey[3] = { CSI, KS_EXTRA, (char_u)KE_DROP };
1325         const void *bytes = [data bytes];
1326         int len = *((int*)bytes);  bytes += sizeof(int);
1327         NSMutableString *string = [NSMutableString stringWithUTF8String:bytes];
1329         // Replace unrecognized end-of-line sequences with \x0a (line feed).
1330         NSRange range = { 0, [string length] };
1331         unsigned n = [string replaceOccurrencesOfString:@"\x0d\x0a"
1332                                              withString:@"\x0a" options:0
1333                                                   range:range];
1334         if (0 == n) {
1335             n = [string replaceOccurrencesOfString:@"\x0d" withString:@"\x0a"
1336                                            options:0 range:range];
1337         }
1339         len = [string lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
1340         dnd_yank_drag_data((char_u*)[string UTF8String], len);
1341         add_to_input_buf(dropkey, sizeof(dropkey));
1342 #endif // FEAT_DND
1343     } else if (GotFocusMsgID == msgid) {
1344         if (!gui.in_focus)
1345             [self focusChange:YES];
1346     } else if (LostFocusMsgID == msgid) {
1347         if (gui.in_focus)
1348             [self focusChange:NO];
1349     } else if (MouseMovedMsgID == msgid) {
1350         const void *bytes = [data bytes];
1351         int row = *((int*)bytes);  bytes += sizeof(int);
1352         int col = *((int*)bytes);  bytes += sizeof(int);
1354         gui_mouse_moved(col, row);
1355     } else if (SetMouseShapeMsgID == msgid) {
1356         const void *bytes = [data bytes];
1357         int shape = *((int*)bytes);  bytes += sizeof(int);
1358         update_mouseshape(shape);
1359     } else {
1360         NSLog(@"WARNING: Unknown message received (msgid=%d)", msgid);
1361     }
1364 + (NSDictionary *)specialKeys
1366     static NSDictionary *specialKeys = nil;
1368     if (!specialKeys) {
1369         NSBundle *mainBundle = [NSBundle mainBundle];
1370         NSString *path = [mainBundle pathForResource:@"SpecialKeys"
1371                                               ofType:@"plist"];
1372         specialKeys = [[NSDictionary alloc] initWithContentsOfFile:path];
1373     }
1375     return specialKeys;
1378 - (void)handleKeyDown:(NSString *)key modifiers:(int)mods
1380     char_u special[3];
1381     char_u modChars[3];
1382     char_u *chars = 0;
1383     int length = 0;
1385     // Special keys (arrow keys, function keys, etc.) are stored in a plist so
1386     // that new keys can easily be added.
1387     NSString *specialString = [[MMBackend specialKeys]
1388             objectForKey:key];
1389     if (specialString && [specialString length] > 1) {
1390         //NSLog(@"special key: %@", specialString);
1391         int ikey = TO_SPECIAL([specialString characterAtIndex:0],
1392                 [specialString characterAtIndex:1]);
1394         ikey = simplify_key(ikey, &mods);
1395         if (ikey == CSI)
1396             ikey = K_CSI;
1398         special[0] = CSI;
1399         special[1] = K_SECOND(ikey);
1400         special[2] = K_THIRD(ikey);
1402         chars = special;
1403         length = 3;
1404     } else if ([key length] > 0) {
1405         chars = (char_u*)[key UTF8String];
1406         length = [key lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
1407         unichar c = [key characterAtIndex:0];
1409         //NSLog(@"non-special: %@ (hex=%x, mods=%d)", key,
1410         //        [key characterAtIndex:0], mods);
1412         if (length == 1 && ((c == Ctrl_C && ctrl_c_interrupts)
1413                 || (c == intr_char && intr_char != Ctrl_C))) {
1414             trash_input_buf();
1415             got_int = TRUE;
1416         }
1418         // HACK!  In most circumstances the Ctrl and Shift modifiers should be
1419         // cleared since they are already added to the key by the AppKit.
1420         // Unfortunately, the only way to deal with when to clear the modifiers
1421         // or not seems to be to have hard-wired rules like this.
1422         if ( !((' ' == c) || (0xa0 == c) || (mods & MOD_MASK_CMD)) ) {
1423             mods &= ~MOD_MASK_SHIFT;
1424             mods &= ~MOD_MASK_CTRL;
1425             //NSLog(@"clear shift ctrl");
1426         }
1428         // HACK!  All Option+key presses go via 'insert text' messages, except
1429         // for <M-Space>.  If the Alt flag is not cleared for <M-Space> it does
1430         // not work to map to it.
1431         if (0xa0 == c && !(mods & MOD_MASK_CMD)) {
1432             //NSLog(@"clear alt");
1433             mods &= ~MOD_MASK_ALT;
1434         }
1435     }
1437     if (chars && length > 0) {
1438         if (mods) {
1439             //NSLog(@"adding mods: %d", mods);
1440             modChars[0] = CSI;
1441             modChars[1] = KS_MODIFIER;
1442             modChars[2] = mods;
1443             add_to_input_buf(modChars, 3);
1444         }
1446         //NSLog(@"add to input buf: 0x%x", chars[0]);
1447         // TODO: Check for CSI bytes?
1448         add_to_input_buf(chars, length);
1449     }
1452 - (void)queueMessage:(int)msgid data:(NSData *)data
1454     [queue addObject:[NSData dataWithBytes:&msgid length:sizeof(int)]];
1455     if (data)
1456         [queue addObject:data];
1457     else
1458         [queue addObject:[NSData data]];
1461 - (void)connectionDidDie:(NSNotification *)notification
1463     // If the main connection to MacVim is lost this means that MacVim was
1464     // either quit (by the user chosing Quit on the MacVim menu), or it has
1465     // crashed.  In either case our only option is to quit now.
1466     // TODO: Write backup file?
1468     //NSLog(@"A Vim process lots its connection to MacVim; quitting.");
1469     getout(0);
1472 - (void)blinkTimerFired:(NSTimer *)timer
1474     NSTimeInterval timeInterval = 0;
1476     [blinkTimer release];
1477     blinkTimer = nil;
1479     if (MMBlinkStateOn == blinkState) {
1480         gui_undraw_cursor();
1481         blinkState = MMBlinkStateOff;
1482         timeInterval = blinkOffInterval;
1483     } else if (MMBlinkStateOff == blinkState) {
1484         gui_update_cursor(TRUE, FALSE);
1485         blinkState = MMBlinkStateOn;
1486         timeInterval = blinkOnInterval;
1487     }
1489     if (timeInterval > 0) {
1490         blinkTimer = 
1491             [[NSTimer scheduledTimerWithTimeInterval:timeInterval target:self
1492                                             selector:@selector(blinkTimerFired:)
1493                                             userInfo:nil repeats:NO] retain];
1494         [self flushQueue:YES];
1495     }
1498 - (void)focusChange:(BOOL)on
1500     // This is a bit of an ugly way to change the selection color.
1501     // TODO: Is there a nicer way to do this?
1502     // TODO: Store selection color and restore it when focus is regained.
1503     char *cmd = on
1504         ? "hi Visual guibg=MacSelectedTextBackgroundColor"
1505         : "hi Visual guibg=MacSecondarySelectedControlColor";
1507     do_cmdline_cmd((char_u*)cmd);
1508     gui_focus_change(on);
1510     // TODO: Is all this necessary just to get the highlights to update?
1511     redraw_all_later(CLEAR);
1512     update_screen(NOT_VALID);
1513     setcursor();
1514     out_flush();
1515     gui_update_cursor(FALSE, FALSE);
1516     gui_mch_flush();
1519 - (void)processInputBegin
1521     inProcessInput = YES;
1522     [lastFlushDate release];
1523     lastFlushDate = [[NSDate date] retain];
1526 - (void)processInputEnd
1528 #if MM_USE_INPUT_QUEUE
1529     int count = [inputQueue count];
1530     if (count % 2) {
1531         // TODO: This is troubling, but it is not hard to get Vim to end up
1532         // here.  Why does this happen?
1533         NSLog(@"WARNING: inputQueue has odd number of objects (%d)", count);
1534         [inputQueue removeAllObjects];
1535     } else if (count > 0) {
1536         // TODO: Dispatch these messages?  Maybe not; usually when the
1537         // 'inputQueue' is non-empty it means that a LOT of messages has been
1538         // sent simultaneously.  The only way this happens is when Vim is being
1539         // tormented, e.g. if the user holds down <D-`> to rapidly switch
1540         // windows.
1541         unsigned i;
1542         for (i = 0; i < count; i+=2) {
1543             int msgid = [[inputQueue objectAtIndex:i] intValue];
1544             NSLog(@"%s: Dropping message %s", _cmd, MessageStrings[msgid]);
1545         }
1547         [inputQueue removeAllObjects];
1548     }
1549 #endif
1551 #if 0 // This does not work...for now, just don't care if a focus msg was lost.
1552     // HACK! A focus message might get lost, but whenever we get here the GUI
1553     // is in focus.
1554     if (!gui.in_focus)
1555         [self focusChange:TRUE];
1556 #endif
1558     inputReceived = YES;
1559     inProcessInput = NO;
1562 @end // MMBackend (Private)
1567 static int eventModifierFlagsToVimModMask(int modifierFlags)
1569     int modMask = 0;
1571     if (modifierFlags & NSShiftKeyMask)
1572         modMask |= MOD_MASK_SHIFT;
1573     if (modifierFlags & NSControlKeyMask)
1574         modMask |= MOD_MASK_CTRL;
1575     if (modifierFlags & NSAlternateKeyMask)
1576         modMask |= MOD_MASK_ALT;
1577     if (modifierFlags & NSCommandKeyMask)
1578         modMask |= MOD_MASK_CMD;
1580     return modMask;
1583 static int vimModMaskToEventModifierFlags(int mods)
1585     int flags = 0;
1587     if (mods & MOD_MASK_SHIFT)
1588         flags |= NSShiftKeyMask;
1589     if (mods & MOD_MASK_CTRL)
1590         flags |= NSControlKeyMask;
1591     if (mods & MOD_MASK_ALT)
1592         flags |= NSAlternateKeyMask;
1593     if (mods & MOD_MASK_CMD)
1594         flags |= NSCommandKeyMask;
1596     return flags;
1599 static int eventModifierFlagsToVimMouseModMask(int modifierFlags)
1601     int modMask = 0;
1603     if (modifierFlags & NSShiftKeyMask)
1604         modMask |= MOUSE_SHIFT;
1605     if (modifierFlags & NSControlKeyMask)
1606         modMask |= MOUSE_CTRL;
1607     if (modifierFlags & NSAlternateKeyMask)
1608         modMask |= MOUSE_ALT;
1610     return modMask;
1613 static int eventButtonNumberToVimMouseButton(int buttonNumber)
1615     static int mouseButton[] = { MOUSE_LEFT, MOUSE_RIGHT, MOUSE_MIDDLE,
1616             MOUSE_X1, MOUSE_X2 };
1618     return mouseButton[buttonNumber < 5 ? buttonNumber : 0];
1621 static int specialKeyToNSKey(int key)
1623     if (!IS_SPECIAL(key))
1624         return key;
1626     static struct {
1627         int special;
1628         int nskey;
1629     } sp2ns[] = {
1630         { K_UP, NSUpArrowFunctionKey },
1631         { K_DOWN, NSDownArrowFunctionKey },
1632         { K_LEFT, NSLeftArrowFunctionKey },
1633         { K_RIGHT, NSRightArrowFunctionKey },
1634         { K_F1, NSF1FunctionKey },
1635         { K_F2, NSF2FunctionKey },
1636         { K_F3, NSF3FunctionKey },
1637         { K_F4, NSF4FunctionKey },
1638         { K_F5, NSF5FunctionKey },
1639         { K_F6, NSF6FunctionKey },
1640         { K_F7, NSF7FunctionKey },
1641         { K_F8, NSF8FunctionKey },
1642         { K_F9, NSF9FunctionKey },
1643         { K_F10, NSF10FunctionKey },
1644         { K_F11, NSF11FunctionKey },
1645         { K_F12, NSF12FunctionKey },
1646         { K_F13, NSF13FunctionKey },
1647         { K_F14, NSF14FunctionKey },
1648         { K_F15, NSF15FunctionKey },
1649         { K_F16, NSF16FunctionKey },
1650         { K_F17, NSF17FunctionKey },
1651         { K_F18, NSF18FunctionKey },
1652         { K_F19, NSF19FunctionKey },
1653         { K_F20, NSF20FunctionKey },
1654         { K_F21, NSF21FunctionKey },
1655         { K_F22, NSF22FunctionKey },
1656         { K_F23, NSF23FunctionKey },
1657         { K_F24, NSF24FunctionKey },
1658         { K_F25, NSF25FunctionKey },
1659         { K_F26, NSF26FunctionKey },
1660         { K_F27, NSF27FunctionKey },
1661         { K_F28, NSF28FunctionKey },
1662         { K_F29, NSF29FunctionKey },
1663         { K_F30, NSF30FunctionKey },
1664         { K_F31, NSF31FunctionKey },
1665         { K_F32, NSF32FunctionKey },
1666         { K_F33, NSF33FunctionKey },
1667         { K_F34, NSF34FunctionKey },
1668         { K_F35, NSF35FunctionKey },
1669         { K_DEL, NSBackspaceCharacter },
1670         { K_BS, NSDeleteCharacter },
1671         { K_HOME, NSHomeFunctionKey },
1672         { K_END, NSEndFunctionKey },
1673         { K_PAGEUP, NSPageUpFunctionKey },
1674         { K_PAGEDOWN, NSPageDownFunctionKey }
1675     };
1677     int i;
1678     for (i = 0; i < sizeof(sp2ns)/sizeof(sp2ns[0]); ++i) {
1679         if (sp2ns[i].special == key)
1680             return sp2ns[i].nskey;
1681     }
1683     return 0;