Changed default title
[MacVim/jjgod.git] / MMBackend.m
blob71d5edc069f020b77d4bc6a67599efc4ef035373
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 static float MMFlushTimeoutInterval = 0.1f;
19 // TODO: Move to separate file.
20 static int eventModifierFlagsToVimModMask(int modifierFlags);
21 static int vimModMaskToEventModifierFlags(int mods);
22 static int eventModifierFlagsToVimMouseModMask(int modifierFlags);
23 static int eventButtonNumberToVimMouseButton(int buttonNumber);
24 static int specialKeyToNSKey(int key);
27 @interface MMBackend (Private)
28 - (void)handleMessage:(int)msgid data:(NSData *)data;
29 + (NSDictionary *)specialKeys;
30 - (void)handleKeyDown:(NSString *)key modifiers:(int)mods;
31 - (void)queueMessage:(int)msgid data:(NSData *)data;
32 - (void)connectionDidDie:(NSNotification *)notification;
33 @end
37 @implementation MMBackend
39 + (MMBackend *)sharedInstance
41     static MMBackend *singleton = nil;
42     return singleton ? singleton : (singleton = [MMBackend new]);
45 - (id)init
47     if ((self = [super init])) {
48         queue = [[NSMutableArray alloc] init];
49         drawData = [[NSMutableData alloc] initWithCapacity:1024];
50         NSString *path = [[NSBundle mainBundle] pathForResource:@"Colors"
51                                                          ofType:@"plist"];
52         colorDict = [[NSDictionary dictionaryWithContentsOfFile:path] retain];
53     }
55     return self;
58 - (void)dealloc
60     [[NSNotificationCenter defaultCenter] removeObserver:self];
62     [queue release];
63     [drawData release];
64     [frontendProxy release];
65     [connection release];
66     [colorDict release];
68     [super dealloc];
71 - (void)setBackgroundColor:(int)color
73     backgroundColor = color;
76 - (void)setForegroundColor:(int)color
78     foregroundColor = color;
81 - (void)setDefaultColorsBackground:(int)bg foreground:(int)fg
83     defaultBackgroundColor = bg;
84     defaultForegroundColor = fg;
86     NSMutableData *data = [NSMutableData data];
88     [data appendBytes:&bg length:sizeof(int)];
89     [data appendBytes:&fg length:sizeof(int)];
91     [self queueMessage:SetDefaultColorsMsgID data:data];
94 - (BOOL)checkin
96     NSBundle *mainBundle = [NSBundle mainBundle];
98     // NOTE!  If the name of the connection changes here it must also be
99     // updated in MMAppController.m.
100     NSString *name = [NSString stringWithFormat:@"%@-connection",
101              [mainBundle bundleIdentifier]];
102     connection = [NSConnection connectionWithRegisteredName:name host:nil];
103     if (!connection) {
104 #if 0
105         NSString *path = [mainBundle bundlePath];
106         if (![[NSWorkspace sharedWorkspace] launchApplication:path]) {
107             NSLog(@"WARNING: Failed to launch GUI with path %@", path);
108             return NO;
109         }
110 #else
111         // HACK!  It would be preferable to launch the GUI using NSWorkspace,
112         // however I have not managed to figure out how to pass arguments using
113         // NSWorkspace.
114         //
115         // NOTE!  Using NSTask to launch the GUI has the negative side-effect
116         // that the GUI won't be activated (or raised) so there is a hack in
117         // MMWindowController which always raises the app when a new window is
118         // opened.
119         NSMutableArray *args = [NSMutableArray arrayWithObjects:
120             [NSString stringWithFormat:@"-%@", MMNoWindowKey], @"yes", nil];
121         NSString *exeName = [[mainBundle infoDictionary]
122                 objectForKey:@"CFBundleExecutable"];
123         NSString *path = [mainBundle pathForAuxiliaryExecutable:exeName];
124         if (!path) {
125             NSLog(@"ERROR: Could not find MacVim executable in bundle");
126             return NO;
127         }
129         [NSTask launchedTaskWithLaunchPath:path arguments:args];
130 #endif
132         // HACK!  The NSWorkspaceDidLaunchApplicationNotification does not work
133         // for tasks like this, so poll the mach bootstrap server until it
134         // returns a valid connection.  Also set a time-out date so that we
135         // don't get stuck doing this forever.
136         NSDate *timeOutDate = [NSDate dateWithTimeIntervalSinceNow:15];
137         while (!connection &&
138                 NSOrderedDescending == [timeOutDate compare:[NSDate date]])
139         {
140             [[NSRunLoop currentRunLoop]
141                     runMode:NSDefaultRunLoopMode
142                  beforeDate:[NSDate dateWithTimeIntervalSinceNow:1]];
144             connection = [NSConnection connectionWithRegisteredName:name
145                                                                host:nil];
146         }
148         if (!connection) {
149             NSLog(@"WARNING: Timed-out waiting for GUI to launch.");
150             return NO;
151         }
152     }
154     id proxy = [connection rootProxy];
155     [proxy setProtocolForProxy:@protocol(MMAppProtocol)];
157     [[NSNotificationCenter defaultCenter] addObserver:self
158             selector:@selector(connectionDidDie:)
159                 name:NSConnectionDidDieNotification object:connection];
161     frontendProxy = [(NSDistantObject*)[proxy connectBackend:self] retain];
162     if (frontendProxy) {
163         [frontendProxy setProtocolForProxy:@protocol(MMAppProtocol)];
164     }
166     return connection && frontendProxy;
169 - (BOOL)openVimWindow
171     [self queueMessage:OpenVimWindowMsgID data:nil];
172     return YES;
175 - (void)clearAll
177     int type = ClearAllDrawType;
179     // Any draw commands in queue are effectively obsolete since this clearAll
180     // will negate any effect they have, therefore we may as well clear the
181     // draw queue.
182     [drawData setLength:0];
184     [drawData appendBytes:&type length:sizeof(int)];
186     [drawData appendBytes:&defaultBackgroundColor length:sizeof(int)];
189 - (void)clearBlockFromRow:(int)row1 column:(int)col1
190                     toRow:(int)row2 column:(int)col2
192     int type = ClearBlockDrawType;
194     [drawData appendBytes:&type length:sizeof(int)];
196     [drawData appendBytes:&defaultBackgroundColor length:sizeof(int)];
197     [drawData appendBytes:&row1 length:sizeof(int)];
198     [drawData appendBytes:&col1 length:sizeof(int)];
199     [drawData appendBytes:&row2 length:sizeof(int)];
200     [drawData appendBytes:&col2 length:sizeof(int)];
203 - (void)deleteLinesFromRow:(int)row count:(int)count
204               scrollBottom:(int)bottom left:(int)left right:(int)right
206     int type = DeleteLinesDrawType;
208     [drawData appendBytes:&type length:sizeof(int)];
210     [drawData appendBytes:&defaultBackgroundColor length:sizeof(int)];
211     [drawData appendBytes:&row length:sizeof(int)];
212     [drawData appendBytes:&count length:sizeof(int)];
213     [drawData appendBytes:&bottom length:sizeof(int)];
214     [drawData appendBytes:&left length:sizeof(int)];
215     [drawData appendBytes:&right length:sizeof(int)];
218 - (void)replaceString:(char*)s length:(int)len row:(int)row column:(int)col
219                 flags:(int)flags
221     int type = ReplaceStringDrawType;
223     [drawData appendBytes:&type length:sizeof(int)];
225     [drawData appendBytes:&backgroundColor length:sizeof(int)];
226     [drawData appendBytes:&foregroundColor length:sizeof(int)];
227     [drawData appendBytes:&row length:sizeof(int)];
228     [drawData appendBytes:&col length:sizeof(int)];
229     [drawData appendBytes:&flags length:sizeof(int)];
230     [drawData appendBytes:&len length:sizeof(int)];
231     [drawData appendBytes:s length:len];
234 - (void)insertLinesFromRow:(int)row count:(int)count
235               scrollBottom:(int)bottom left:(int)left right:(int)right
237     int type = InsertLinesDrawType;
239     [drawData appendBytes:&type length:sizeof(int)];
241     [drawData appendBytes:&defaultBackgroundColor length:sizeof(int)];
242     [drawData appendBytes:&row length:sizeof(int)];
243     [drawData appendBytes:&count length:sizeof(int)];
244     [drawData appendBytes:&bottom length:sizeof(int)];
245     [drawData appendBytes:&left length:sizeof(int)];
246     [drawData appendBytes:&right length:sizeof(int)];
249 - (void)flushQueue:(BOOL)force
251     // NOTE! This method gets called a lot; if we were to flush every time it
252     // was called MacVim would feel unresponsive.  So there is a time out which
253     // ensures that the queue isn't flushed too often.
254     if (!force && lastFlushDate && -[lastFlushDate timeIntervalSinceNow]
255             < MMFlushTimeoutInterval)
256         return;
258     if ([drawData length] > 0) {
259         [self queueMessage:BatchDrawMsgID data:[drawData copy]];
260         [drawData setLength:0];
261     }
263     if ([queue count] > 0) {
264         // TODO: Come up with a better way to handle the insertion point.
265         [self updateInsertionPoint];
267         [frontendProxy processCommandQueue:queue];
268         [queue removeAllObjects];
270         [lastFlushDate release];
271         lastFlushDate = [[NSDate date] retain];
272     }
275 - (BOOL)waitForInput:(int)milliseconds
277     NSDate *date = milliseconds > 0 ?
278             [NSDate dateWithTimeIntervalSinceNow:.001*milliseconds] : 
279             [NSDate distantFuture];
281     [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:date];
283     // I know of no way to figure out if the run loop exited because input was
284     // found or because of a time out, so I need to manually indicate when
285     // input was received in processInput:data: and then reset it every time
286     // here.
287     BOOL yn = inputReceived;
288     inputReceived = NO;
290     return yn;
293 - (void)exit
295     // By invalidating the NSConnection the MMWindowController immediately
296     // finds out that the connection is down and as a result
297     // [MMWindowController connectionDidDie:] is invoked.
298     [[NSNotificationCenter defaultCenter] removeObserver:self];
299     [connection invalidate];
302 - (void)selectTab:(int)index
304     //NSLog(@"%s%d", _cmd, index);
306     index -= 1;
307     NSData *data = [NSData dataWithBytes:&index length:sizeof(int)];
308     [self queueMessage:SelectTabMsgID data:data];
311 - (void)updateTabBar
313     //NSLog(@"%s", _cmd);
315     NSMutableData *data = [NSMutableData data];
317     int idx = tabpage_index(curtab) - 1;
318     [data appendBytes:&idx length:sizeof(int)];
320     tabpage_T *tp;
321     for (tp = first_tabpage; tp != NULL; tp = tp->tp_next) {
322         // This function puts the label of the tab in the global 'NameBuff'.
323         get_tabline_label(tp, FALSE);
324         int len = strlen((char*)NameBuff);
326         // Count the number of windows in the tabpage.
327         //win_T *wp = tp->tp_firstwin;
328         //int wincount;
329         //for (wincount = 0; wp != NULL; wp = wp->w_next, ++wincount);
331         //[data appendBytes:&wincount length:sizeof(int)];
332         [data appendBytes:&len length:sizeof(int)];
333         [data appendBytes:NameBuff length:len];
334     }
336     [self queueMessage:UpdateTabBarMsgID data:data];
339 - (BOOL)tabBarVisible
341     return tabBarVisible;
344 - (void)showTabBar:(BOOL)enable
346     tabBarVisible = enable;
348     int msgid = enable ? ShowTabBarMsgID : HideTabBarMsgID;
349     [self queueMessage:msgid data:nil];
352 - (void)setRows:(int)rows columns:(int)cols
354     //NSLog(@"[VimTask] setRows:%d columns:%d", rows, cols);
356     int dim[] = { rows, cols };
357     NSData *data = [NSData dataWithBytes:&dim length:2*sizeof(int)];
359     [self queueMessage:SetTextDimensionsMsgID data:data];
362 - (void)setVimWindowTitle:(char *)title
364     NSMutableData *data = [NSMutableData data];
365     int len = strlen(title);
367     [data appendBytes:&len length:sizeof(int)];
368     [data appendBytes:title length:len];
370     [self queueMessage:SetVimWindowTitleMsgID data:data];
373 - (oneway void)setBrowseForFileString:(in bycopy NSString *)string
375     // NOTE: This is called by [MMVimController panelDidEnd:::] to indicate
376     // that the save/open panel has finished.  If 'string == nil' that means
377     // the user pressed cancel.
378     browseForFileString = string ? [string copy] : nil;
381 - (char *)browseForFileInDirectory:(char *)dir title:(char *)title
382                             saving:(int)saving
384     //NSLog(@"browseForFileInDirectory:%s title:%s saving:%d", dir, title,
385     //        saving);
387     NSString *ds = dir
388             ? [NSString stringWithCString:dir encoding:NSUTF8StringEncoding]
389             : nil;
390     NSString *ts = title
391             ? [NSString stringWithCString:title encoding:NSUTF8StringEncoding]
392             : nil;
393     [frontendProxy showSavePanelForDirectory:ds title:ts saving:saving];
395     // Wait until a reply is sent from MMVimController.
396     [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
397                              beforeDate:[NSDate distantFuture]];
398     if (!browseForFileString)
399         return nil;
401     char_u *s = vim_strsave((char_u*)[browseForFileString UTF8String]);
402     [browseForFileString release];  browseForFileString = nil;
404     return (char *)s;
407 - (void)updateInsertionPoint
409     NSMutableData *data = [NSMutableData data];
411     int state = get_shape_idx(FALSE);
412     state = (state == SHAPE_IDX_I) || (state == SHAPE_IDX_CI);
414     [data appendBytes:&defaultForegroundColor length:sizeof(int)];
415     [data appendBytes:&gui.row length:sizeof(int)];
416     [data appendBytes:&gui.col length:sizeof(int)];
417     [data appendBytes:&state length:sizeof(int)];
419     [self queueMessage:UpdateInsertionPointMsgID data:data];
422 - (void)addMenuWithTag:(int)tag parent:(int)parentTag name:(char *)name
423                atIndex:(int)index
425     //NSLog(@"addMenuWithTag:%d parent:%d name:%s atIndex:%d", tag, parentTag,
426     //        name, index);
428     int namelen = name ? strlen(name) : 0;
429     NSMutableData *data = [NSMutableData data];
431     [data appendBytes:&tag length:sizeof(int)];
432     [data appendBytes:&parentTag length:sizeof(int)];
433     [data appendBytes:&namelen length:sizeof(int)];
434     if (namelen > 0) [data appendBytes:name length:namelen];
435     [data appendBytes:&index length:sizeof(int)];
437     [self queueMessage:AddMenuMsgID data:data];
440 - (void)addMenuItemWithTag:(int)tag parent:(int)parentTag name:(char *)name
441                        tip:(char *)tip icon:(char *)icon
442              keyEquivalent:(int)key modifiers:(int)mods
443                     action:(NSString *)action atIndex:(int)index
445     //NSLog(@"addMenuItemWithTag:%d parent:%d name:%s tip:%s atIndex:%d", tag,
446     //        parentTag, name, tip, index);
448     int namelen = name ? strlen(name) : 0;
449     int tiplen = tip ? strlen(tip) : 0;
450     int iconlen = icon ? strlen(icon) : 0;
451     int eventFlags = vimModMaskToEventModifierFlags(mods);
452     int actionlen = [action lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
453     NSMutableData *data = [NSMutableData data];
455     key = specialKeyToNSKey(key);
457     [data appendBytes:&tag length:sizeof(int)];
458     [data appendBytes:&parentTag length:sizeof(int)];
459     [data appendBytes:&namelen length:sizeof(int)];
460     if (namelen > 0) [data appendBytes:name length:namelen];
461     [data appendBytes:&tiplen length:sizeof(int)];
462     if (tiplen > 0) [data appendBytes:tip length:tiplen];
463     [data appendBytes:&iconlen length:sizeof(int)];
464     if (iconlen > 0) [data appendBytes:icon length:iconlen];
465     [data appendBytes:&actionlen length:sizeof(int)];
466     if (actionlen > 0) [data appendBytes:[action UTF8String] length:actionlen];
467     [data appendBytes:&index length:sizeof(int)];
468     [data appendBytes:&key length:sizeof(int)];
469     [data appendBytes:&eventFlags length:sizeof(int)];
471     [self queueMessage:AddMenuItemMsgID data:data];
474 - (void)removeMenuItemWithTag:(int)tag
476     NSMutableData *data = [NSMutableData data];
477     [data appendBytes:&tag length:sizeof(int)];
479     [self queueMessage:RemoveMenuItemMsgID data:data];
482 - (void)enableMenuItemWithTag:(int)tag state:(int)enabled
484     NSMutableData *data = [NSMutableData data];
486     [data appendBytes:&tag length:sizeof(int)];
487     [data appendBytes:&enabled length:sizeof(int)];
489     [self queueMessage:EnableMenuItemMsgID data:data];
492 - (void)showPopupMenuWithName:(char *)name atMouseLocation:(BOOL)mouse
494     NSMutableData *data = [NSMutableData data];
495     int len = strlen(name);
496     int row = -1, col = -1;
498     if (!mouse && curwin) {
499         row = curwin->w_wrow;
500         col = curwin->w_wcol;
501     }
503     [data appendBytes:&row length:sizeof(int)];
504     [data appendBytes:&col length:sizeof(int)];
505     [data appendBytes:&len length:sizeof(int)];
506     [data appendBytes:name length:len];
508     [self queueMessage:ShowPopupMenuMsgID data:data];
511 - (void)showToolbar:(int)enable flags:(int)flags
513     NSMutableData *data = [NSMutableData data];
515     [data appendBytes:&enable length:sizeof(int)];
516     [data appendBytes:&flags length:sizeof(int)];
518     [self queueMessage:ShowToolbarMsgID data:data];
521 - (void)createScrollbarWithIdentifier:(long)ident type:(int)type
523     NSMutableData *data = [NSMutableData data];
525     [data appendBytes:&ident length:sizeof(long)];
526     [data appendBytes:&type length:sizeof(int)];
528     [self queueMessage:CreateScrollbarMsgID data:data];
531 - (void)destroyScrollbarWithIdentifier:(long)ident
533     NSMutableData *data = [NSMutableData data];
534     [data appendBytes:&ident length:sizeof(long)];
536     [self queueMessage:DestroyScrollbarMsgID data:data];
539 - (void)showScrollbarWithIdentifier:(long)ident state:(int)visible
541     NSMutableData *data = [NSMutableData data];
543     [data appendBytes:&ident length:sizeof(long)];
544     [data appendBytes:&visible length:sizeof(int)];
546     [self queueMessage:ShowScrollbarMsgID data:data];
549 - (void)setScrollbarPosition:(int)pos length:(int)len identifier:(long)ident
551     NSMutableData *data = [NSMutableData data];
553     [data appendBytes:&ident length:sizeof(long)];
554     [data appendBytes:&pos length:sizeof(int)];
555     [data appendBytes:&len length:sizeof(int)];
557     [self queueMessage:SetScrollbarPositionMsgID data:data];
560 - (void)setScrollbarThumbValue:(long)val size:(long)size max:(long)max
561                     identifier:(long)ident
563     float fval = max-size+1 > 0 ? (float)val/(max-size+1) : 0;
564     float prop = (float)size/(max+1);
565     if (fval < 0) fval = 0;
566     else if (fval > 1.0f) fval = 1.0f;
567     if (prop < 0) prop = 0;
568     else if (prop > 1.0f) prop = 1.0f;
570     NSMutableData *data = [NSMutableData data];
572     [data appendBytes:&ident length:sizeof(long)];
573     [data appendBytes:&fval length:sizeof(float)];
574     [data appendBytes:&prop length:sizeof(float)];
576     [self queueMessage:SetScrollbarThumbMsgID data:data];
579 - (BOOL)setFontWithName:(char *)name
581     NSString *fontName;
582     float size = 0.0f;
583     BOOL parseFailed = NO;
585     if (name) {
586         fontName = [[[NSString alloc] initWithCString:name
587                 encoding:NSUTF8StringEncoding] autorelease];
588         NSArray *components = [fontName componentsSeparatedByString:@":"];
589         if ([components count] == 2) {
590             NSString *sizeString = [components lastObject];
591             if ([sizeString length] > 0
592                     && [sizeString characterAtIndex:0] == 'h') {
593                 sizeString = [sizeString substringFromIndex:1];
594                 if ([sizeString length] > 0) {
595                     size = [sizeString floatValue];
596                     fontName = [components objectAtIndex:0];
597                 }
598             } else {
599                 parseFailed = YES;
600             }
601         } else if ([components count] > 2) {
602             parseFailed = YES;
603         }
604     } else {
605         fontName = [[NSFont userFixedPitchFontOfSize:0] displayName];
606     }
608     if (!parseFailed && [fontName length] > 0) {
609         if (size < 6 || size > 100) {
610             // Font size 0.0 tells NSFont to use the 'user default size'.
611             size = 0.0f;
612         }
614         NSFont *font = [NSFont fontWithName:fontName size:size];
615         if (font) {
616             //NSLog(@"Setting font '%@' of size %.2f", fontName, size);
617             NSMutableData *data = [NSMutableData data];
618             int len = [fontName
619                     lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
621             [data appendBytes:&size length:sizeof(float)];
622             [data appendBytes:&len length:sizeof(int)];
623             [data appendBytes:[fontName UTF8String] length:len];
625             [self queueMessage:SetFontMsgID data:data];
626             return YES;
627         }
628     }
630     NSLog(@"WARNING: Cannot set font with name '%@' of size %.2f",
631             fontName, size);
632     return NO;
635 - (void)executeActionWithName:(NSString *)name
637     int len = [name lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
639     if (len > 0) {
640         NSMutableData *data = [NSMutableData data];
642         [data appendBytes:&len length:sizeof(int)];
643         [data appendBytes:[name UTF8String] length:len];
645         [self queueMessage:ExecuteActionMsgID data:data];
646     }
649 - (int)lookupColorWithKey:(NSString *)key
651     if (!(key && [key length] > 0))
652         return INVALCOLOR;
654     // First of all try to lookup key in the color dictionary; note that all
655     // keys in this dictionary are lowercase with no whitespace.
657     NSString *stripKey = [[[[key lowercaseString]
658         stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]
659             componentsSeparatedByString:@" "]
660                componentsJoinedByString:@""];
662     if (stripKey && [stripKey length] > 0) {
663         id obj = [colorDict objectForKey:stripKey];
664         if (obj) return [obj intValue];
666         // The key was not in the dictionary; is it perhaps of the form
667         // #rrggbb?
669         if ([stripKey length] > 1 && [stripKey characterAtIndex:0] == '#') {
670             NSScanner *scanner = [NSScanner scannerWithString:stripKey];
671             [scanner setScanLocation:1];
672             unsigned hex = 0;
673             if ([scanner scanHexInt:&hex]) {
674                 return (int)hex;
675             }
676         }
677     }
679     NSLog(@"WARNING: No color with key %@ found.", stripKey);
680     return INVALCOLOR;
683 - (oneway void)processInput:(int)msgid data:(in NSData *)data
685     [lastFlushDate release];
686     lastFlushDate = [[NSDate date] retain];
688     [self handleMessage:msgid data:data];
689     inputReceived = YES;
692 - (BOOL)checkForModifiedBuffers
694     buf_T *buf;
695     for (buf = firstbuf; buf != NULL; buf = buf->b_next) {
696         if (bufIsChanged(buf)) {
697             return YES;
698         }
699     }
701     return NO;
704 - (BOOL)starRegisterToPasteboard:(byref NSPasteboard *)pboard
706     if (VIsual_active && (State & NORMAL) && clip_star.available) {
707         // If there is no pasteboard, return YES to indicate that there is text
708         // to copy.
709         if (!pboard)
710             return YES;
712         clip_copy_selection();
714         // Get the text to put on the pasteboard.
715         long_u len = 0; char_u *str = 0;
716         int type = clip_convert_selection(&str, &len, &clip_star);
717         if (type < 0)
718             return NO;
719         
720         NSString *string = [[NSString alloc]
721             initWithBytes:str length:len encoding:NSUTF8StringEncoding];
723         NSArray *types = [NSArray arrayWithObject:NSStringPboardType];
724         [pboard declareTypes:types owner:nil];
725         BOOL ok = [pboard setString:string forType:NSStringPboardType];
726     
727         [string release];
728         vim_free(str);
730         return ok;
731     }
733     return NO;
736 - (BOOL)starRegisterFromPasteboard:(byref NSPasteboard *)pboard
738     if (curbuf && !curbuf->b_p_ro) {
739         return YES;
740     }
742     return NO;
745 @end // MMBackend
749 @implementation MMBackend (Private)
751 - (void)handleMessage:(int)msgid data:(NSData *)data
753     if (KillTaskMsgID == msgid) {
754         //NSLog(@"VimTask received kill message; exiting now.");
755         // Set this flag here so that exit does not send TaskExitedMsgID back
756         // to MMVimController.
757         receivedKillTaskMsg = YES;
758         getout(0);
759     } else if (InsertTextMsgID == msgid) {
760         if (!data) return;
761         NSString *key = [[NSString alloc] initWithData:data
762                                               encoding:NSUTF8StringEncoding];
763         //NSLog(@"insert text: %@  (hex=%x)", key, [key characterAtIndex:0]);
764         add_to_input_buf((char_u*)[key UTF8String],
765                 [key lengthOfBytesUsingEncoding:NSUTF8StringEncoding]);
766         [key release];
767     } else if (KeyDownMsgID == msgid || CmdKeyMsgID == msgid) {
768         if (!data) return;
769         const void *bytes = [data bytes];
770         int mods = *((int*)bytes);  bytes += sizeof(int);
771         int len = *((int*)bytes);  bytes += sizeof(int);
772         NSString *key = [[NSString alloc] initWithBytes:bytes length:len
773                                               encoding:NSUTF8StringEncoding];
774         mods = eventModifierFlagsToVimModMask(mods);
776         [self handleKeyDown:key modifiers:mods];
778         [key release];
779     } else if (SelectTabMsgID == msgid) {
780         if (!data) return;
781         const void *bytes = [data bytes];
782         int idx = *((int*)bytes) + 1;
783         //NSLog(@"Selecting tab %d", idx);
784         send_tabline_event(idx);
785     } else if (CloseTabMsgID == msgid) {
786         if (!data) return;
787         const void *bytes = [data bytes];
788         int idx = *((int*)bytes) + 1;
789         //NSLog(@"Closing tab %d", idx);
790         send_tabline_menu_event(idx, TABLINE_MENU_CLOSE);
791     } else if (AddNewTabMsgID == msgid) {
792         //NSLog(@"Adding new tab");
793         send_tabline_menu_event(0, TABLINE_MENU_NEW);
794     } else if (DraggedTabMsgID == msgid) {
795         if (!data) return;
796         const void *bytes = [data bytes];
797         // NOTE! The destination index is 0 based, so do not add 1 to make it 1
798         // based.
799         int idx = *((int*)bytes);
801         tabpage_move(idx);
802     } else if (ScrollWheelMsgID == msgid) {
803         if (!data) return;
804         const void *bytes = [data bytes];
806         int row = *((int*)bytes);  bytes += sizeof(int);
807         int col = *((int*)bytes);  bytes += sizeof(int);
808         int flags = *((int*)bytes);  bytes += sizeof(int);
809         float dy = *((float*)bytes);  bytes += sizeof(float);
811         int button = MOUSE_5;
812         if (dy > 0) button = MOUSE_4;
814         flags = eventModifierFlagsToVimMouseModMask(flags);
816         gui_send_mouse_event(button, col, row, NO, flags);
817     } else if (MouseDownMsgID == msgid) {
818         if (!data) return;
819         const void *bytes = [data bytes];
821         int row = *((int*)bytes);  bytes += sizeof(int);
822         int col = *((int*)bytes);  bytes += sizeof(int);
823         int button = *((int*)bytes);  bytes += sizeof(int);
824         int flags = *((int*)bytes);  bytes += sizeof(int);
825         int count = *((int*)bytes);  bytes += sizeof(int);
827         button = eventButtonNumberToVimMouseButton(button);
828         flags = eventModifierFlagsToVimMouseModMask(flags);
830         gui_send_mouse_event(button, col, row, 0 != count, flags);
831     } else if (MouseUpMsgID == msgid) {
832         if (!data) return;
833         const void *bytes = [data bytes];
835         int row = *((int*)bytes);  bytes += sizeof(int);
836         int col = *((int*)bytes);  bytes += sizeof(int);
837         int flags = *((int*)bytes);  bytes += sizeof(int);
839         flags = eventModifierFlagsToVimMouseModMask(flags);
841         gui_send_mouse_event(MOUSE_RELEASE, col, row, NO, flags);
842     } else if (MouseDraggedMsgID == msgid) {
843         if (!data) return;
844         const void *bytes = [data bytes];
846         int row = *((int*)bytes);  bytes += sizeof(int);
847         int col = *((int*)bytes);  bytes += sizeof(int);
848         int flags = *((int*)bytes);  bytes += sizeof(int);
850         flags = eventModifierFlagsToVimMouseModMask(flags);
852         gui_send_mouse_event(MOUSE_DRAG, col, row, NO, flags);
853     } else if (SetTextDimensionsMsgID == msgid) {
854         if (!data) return;
855         const void *bytes = [data bytes];
856         int rows = *((int*)bytes);  bytes += sizeof(int);
857         int cols = *((int*)bytes);  bytes += sizeof(int);
859         // NOTE! Vim doesn't call gui_mch_set_shellsize() after
860         // gui_resize_shell(), so we have to manually set the rows and columns
861         // here.  (MacVim doesn't change the rows and columns to avoid
862         // inconsistent states between Vim and MacVim.)
863         [self setRows:rows columns:cols];
865         //NSLog(@"[VimTask] Resizing shell to %dx%d.", cols, rows);
866         gui_resize_shell(cols, rows);
867     } else if (ExecuteMenuMsgID == msgid) {
868         if (!data) return;
869         const void *bytes = [data bytes];
870         int tag = *((int*)bytes);  bytes += sizeof(int);
872         vimmenu_T *menu = (vimmenu_T*)tag;
873         // TODO!  Make sure 'menu' is a valid menu pointer!
874         if (menu) {
875             gui_menu_cb(menu);
876         }
877     } else if (ToggleToolbarMsgID == msgid) {
878         char_u go[sizeof(GO_ALL)+2];
879         char_u *p;
880         int len;
882         STRCPY(go, p_go);
883         p = vim_strchr(go, GO_TOOLBAR);
884         len = STRLEN(go);
886         if (p != NULL) {
887             char_u *end = go + len;
888             while (p < end) {
889                 p[0] = p[1];
890                 ++p;
891             }
892         } else {
893             go[len] = GO_TOOLBAR;
894             go[len+1] = NUL;
895         }
897         set_option_value((char_u*)"guioptions", 0, go, 0);
899         // Force screen redraw (does it have to be this complicated?).
900         redraw_all_later(CLEAR);
901         update_screen(NOT_VALID);
902         setcursor();
903         out_flush();
904         gui_update_cursor(FALSE, FALSE);
905         gui_mch_flush();
906     } else if (ScrollbarEventMsgID == msgid) {
907         if (!data) return;
908         const void *bytes = [data bytes];
909         long ident = *((long*)bytes);  bytes += sizeof(long);
910         int hitPart = *((int*)bytes);  bytes += sizeof(int);
911         float fval = *((float*)bytes);  bytes += sizeof(float);
912         scrollbar_T *sb = gui_find_scrollbar(ident);
914         if (sb) {
915             scrollbar_T *sb_info = sb->wp ? &sb->wp->w_scrollbars[0] : sb;
916             long value = sb_info->value;
917             long size = sb_info->size;
918             long max = sb_info->max;
919             BOOL isStillDragging = NO;
920             BOOL updateKnob = YES;
922             switch (hitPart) {
923             case NSScrollerDecrementPage:
924                 value -= (size > 2 ? size - 2 : 1);
925                 break;
926             case NSScrollerIncrementPage:
927                 value += (size > 2 ? size - 2 : 1);
928                 break;
929             case NSScrollerDecrementLine:
930                 --value;
931                 break;
932             case NSScrollerIncrementLine:
933                 ++value;
934                 break;
935             case NSScrollerKnob:
936                 isStillDragging = YES;
937                 // fall through ...
938             case NSScrollerKnobSlot:
939                 value = (long)(fval * (max - size + 1));
940                 // fall through ...
941             default:
942                 updateKnob = NO;
943                 break;
944             }
946             //NSLog(@"value %d -> %d", sb_info->value, value);
947             gui_drag_scrollbar(sb, value, isStillDragging);
949             if (updateKnob) {
950                 // Dragging the knob or option+clicking automatically updates
951                 // the knob position (on the actual NSScroller), so we only
952                 // need to set the knob position in the other cases.
953                 if (sb->wp) {
954                     // Update both the left&right vertical scrollbars.
955                     long identLeft = sb->wp->w_scrollbars[SBAR_LEFT].ident;
956                     long identRight = sb->wp->w_scrollbars[SBAR_RIGHT].ident;
957                     [self setScrollbarThumbValue:value size:size max:max
958                                       identifier:identLeft];
959                     [self setScrollbarThumbValue:value size:size max:max
960                                       identifier:identRight];
961                 } else {
962                     // Update the horizontal scrollbar.
963                     [self setScrollbarThumbValue:value size:size max:max
964                                       identifier:ident];
965                 }
966             }
967         }
968     } else if (SetFontMsgID == msgid) {
969         if (!data) return;
970         const void *bytes = [data bytes];
971         float pointSize = *((float*)bytes);  bytes += sizeof(float);
972         //unsigned len = *((unsigned*)bytes);  bytes += sizeof(unsigned);
973         bytes += sizeof(unsigned);  // len not used
975         NSMutableString *name = [NSMutableString stringWithUTF8String:bytes];
976         [name appendString:[NSString stringWithFormat:@":h%.2f", pointSize]];
978         set_option_value((char_u*)"gfn", 0, (char_u*)[name UTF8String], 0);
980         // Force screen redraw (does it have to be this complicated?).
981         redraw_all_later(CLEAR);
982         update_screen(NOT_VALID);
983         setcursor();
984         out_flush();
985         gui_update_cursor(FALSE, FALSE);
986         gui_mch_flush();
987     } else if (VimShouldCloseMsgID == msgid) {
988         gui_shell_closed();
989     } else if (DropFilesMsgID == msgid) {
990 #ifdef FEAT_DND
991         const void *bytes = [data bytes];
992         int n = *((int*)bytes);  bytes += sizeof(int);
994 #if 0
995         int row = *((int*)bytes);  bytes += sizeof(int);
996         int col = *((int*)bytes);  bytes += sizeof(int);
998         char_u **fnames = (char_u **)alloc(n * sizeof(char_u *));
999         if (fnames) {
1000             const void *end = [data bytes] + [data length];
1001             int i = 0;
1002             while (bytes < end && i < n) {
1003                 int len = *((int*)bytes);  bytes += sizeof(int);
1004                 fnames[i++] = vim_strnsave((char_u*)bytes, len);
1005                 bytes += len;
1006             }
1008             // NOTE!  This function will free 'fnames'.
1009             gui_handle_drop(col, row, 0, fnames, i < n ? i : n);
1010         }
1011 #else
1012         // HACK!  I'm not sure how to get Vim to open a list of files in tabs,
1013         // so instead I create a ':tab drop' command with all the files to open
1014         // and execute it.
1015         NSMutableString *cmd = (n > 1)
1016                 ? [NSMutableString stringWithString:@":tab drop"]
1017                 : [NSMutableString stringWithString:@":drop"];
1019         const void *end = [data bytes] + [data length];
1020         int i;
1021         for (i = 0; i < n && bytes < end; ++i) {
1022             int len = *((int*)bytes);  bytes += sizeof(int);
1023             NSMutableString *file =
1024                     [NSMutableString stringWithUTF8String:bytes];
1025             [file replaceOccurrencesOfString:@" "
1026                                   withString:@"\\ "
1027                                      options:0
1028                                        range:NSMakeRange(0, [file length])];
1029             bytes += len;
1031             [cmd appendString:@" "];
1032             [cmd appendString:file];
1033         }
1035         // By going to the last tabpage we ensure that the new tabs will appear
1036         // last (if this call is left out, the taborder becomes messy).
1037         goto_tabpage(9999);
1039         do_cmdline_cmd((char_u*)[cmd UTF8String]);
1041         // Force screen redraw (does it have to be this complicated?).
1042         // (This code was taken from the end of gui_handle_drop().)
1043         update_screen(NOT_VALID);
1044         setcursor();
1045         out_flush();
1046         gui_update_cursor(FALSE, FALSE);
1047         gui_mch_flush();
1048 #endif
1049 #endif // FEAT_DND
1050     } else if (DropStringMsgID == msgid) {
1051 #ifdef FEAT_DND
1052         char_u  dropkey[3] = { CSI, KS_EXTRA, (char_u)KE_DROP };
1053         const void *bytes = [data bytes];
1054         int len = *((int*)bytes);  bytes += sizeof(int);
1055         NSMutableString *string = [NSMutableString stringWithUTF8String:bytes];
1057         // Replace unrecognized end-of-line sequences with \x0a (line feed).
1058         NSRange range = { 0, [string length] };
1059         unsigned n = [string replaceOccurrencesOfString:@"\x0d\x0a"
1060                                              withString:@"\x0a" options:0
1061                                                   range:range];
1062         if (0 == n) {
1063             n = [string replaceOccurrencesOfString:@"\x0d" withString:@"\x0a"
1064                                            options:0 range:range];
1065         }
1067         len = [string lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
1068         dnd_yank_drag_data((char_u*)[string UTF8String], len);
1069         add_to_input_buf(dropkey, sizeof(dropkey));
1070 #endif // FEAT_DND
1071     } else {
1072         NSLog(@"WARNING: Unknown message received (msgid=%d)", msgid);
1073     }
1076 + (NSDictionary *)specialKeys
1078     static NSDictionary *specialKeys = nil;
1080     if (!specialKeys) {
1081         NSBundle *mainBundle = [NSBundle mainBundle];
1082         NSString *path = [mainBundle pathForResource:@"SpecialKeys"
1083                                               ofType:@"plist"];
1084         specialKeys = [[NSDictionary alloc] initWithContentsOfFile:path];
1085     }
1087     return specialKeys;
1090 - (void)handleKeyDown:(NSString *)key modifiers:(int)mods
1092     char_u special[3];
1093     char_u modChars[3];
1094     char_u *chars = 0;
1095     int length = 0;
1097     // Special keys (arrow keys, function keys, etc.) are stored in a plist so
1098     // that new keys can easily be added.
1099     NSString *specialString = [[MMBackend specialKeys]
1100             objectForKey:key];
1101     if (specialString && [specialString length] > 1) {
1102         //NSLog(@"special key: %@", specialString);
1103         int ikey = TO_SPECIAL([specialString characterAtIndex:0],
1104                 [specialString characterAtIndex:1]);
1106         ikey = simplify_key(ikey, &mods);
1107         if (ikey == CSI)
1108             ikey = K_CSI;
1110         special[0] = CSI;
1111         special[1] = K_SECOND(ikey);
1112         special[2] = K_THIRD(ikey);
1114         chars = special;
1115         length = 3;
1116     } else if ([key length] > 0) {
1117         chars = (char_u*)[key UTF8String];
1118         length = [key lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
1119         unichar c = [key characterAtIndex:0];
1121         if ((c == Ctrl_C && ctrl_c_interrupts)
1122                 || (c == intr_char && intr_char != Ctrl_C)) {
1123             // TODO: The run loop is not touched while Vim is processing, so
1124             // effectively it is impossible to interrupt Vim.
1125             trash_input_buf();
1126             got_int = TRUE;
1127         }
1129         //NSLog(@"non-special: %@ (hex=%x, mods=%d)", key,
1130         //        [key characterAtIndex:0], mods);
1132         // HACK!  In most circumstances the Ctrl and Shift modifiers should be
1133         // cleared since they are already added to the key by the AppKit.
1134         // Unfortunately, the only way to deal with when to clear the modifiers
1135         // or not seems to be to have hard-wired rules like this.
1136         if ( !((' ' == c) || (0xa0 == c) || (mods & MOD_MASK_CMD)) ) {
1137             mods &= ~MOD_MASK_SHIFT;
1138             mods &= ~MOD_MASK_CTRL;
1139             //NSLog(@"clear shift ctrl");
1140         }
1142         // HACK!  All Option+key presses go via 'insert text' messages, except
1143         // for <M-Space>.  If the Alt flag is not cleared for <M-Space> it does
1144         // not work to map to it.
1145         if (0xa0 == c && !(mods & MOD_MASK_CMD)) {
1146             //NSLog(@"clear alt");
1147             mods &= ~MOD_MASK_ALT;
1148         }
1149     }
1151     if (chars && length > 0) {
1152         if (mods) {
1153             //NSLog(@"adding mods: %d", mods);
1154             modChars[0] = CSI;
1155             modChars[1] = KS_MODIFIER;
1156             modChars[2] = mods;
1157             add_to_input_buf(modChars, 3);
1158         }
1160         //NSLog(@"add to input buf: 0x%x", chars[0]);
1161         add_to_input_buf(chars, length);
1162     }
1165 - (void)queueMessage:(int)msgid data:(NSData *)data
1167     [queue addObject:[NSData dataWithBytes:&msgid length:sizeof(int)]];
1168     if (data)
1169         [queue addObject:data];
1170     else
1171         [queue addObject:[NSData data]];
1174 - (void)connectionDidDie:(NSNotification *)notification
1176     // If the main connection to MacVim is lost this means that MacVim was
1177     // either quit (by the user chosing Quit on the MacVim menu), or it has
1178     // crashed.  In either case our only option is to quit now.
1179     // TODO: Write backup file?
1181     //NSLog(@"A Vim process lots its connection to MacVim; quitting.");
1182     getout(0);
1185 @end // MMBackend (Private)
1190 static int eventModifierFlagsToVimModMask(int modifierFlags)
1192     int modMask = 0;
1194     if (modifierFlags & NSShiftKeyMask)
1195         modMask |= MOD_MASK_SHIFT;
1196     if (modifierFlags & NSControlKeyMask)
1197         modMask |= MOD_MASK_CTRL;
1198     if (modifierFlags & NSAlternateKeyMask)
1199         modMask |= MOD_MASK_ALT;
1200     if (modifierFlags & NSCommandKeyMask)
1201         modMask |= MOD_MASK_CMD;
1203     return modMask;
1206 static int vimModMaskToEventModifierFlags(int mods)
1208     int flags = 0;
1210     if (mods & MOD_MASK_SHIFT)
1211         flags |= NSShiftKeyMask;
1212     if (mods & MOD_MASK_CTRL)
1213         flags |= NSControlKeyMask;
1214     if (mods & MOD_MASK_ALT)
1215         flags |= NSAlternateKeyMask;
1216     if (mods & MOD_MASK_CMD)
1217         flags |= NSCommandKeyMask;
1219     return flags;
1222 static int eventModifierFlagsToVimMouseModMask(int modifierFlags)
1224     int modMask = 0;
1226     if (modifierFlags & NSShiftKeyMask)
1227         modMask |= MOUSE_SHIFT;
1228     if (modifierFlags & NSControlKeyMask)
1229         modMask |= MOUSE_CTRL;
1230     if (modifierFlags & NSAlternateKeyMask)
1231         modMask |= MOUSE_ALT;
1233     return modMask;
1236 static int eventButtonNumberToVimMouseButton(int buttonNumber)
1238     static int mouseButton[] = { MOUSE_LEFT, MOUSE_RIGHT, MOUSE_MIDDLE,
1239             MOUSE_X1, MOUSE_X2 };
1241     return mouseButton[buttonNumber < 5 ? buttonNumber : 0];
1244 static int specialKeyToNSKey(int key)
1246     if (!IS_SPECIAL(key))
1247         return key;
1249     static struct {
1250         int special;
1251         int nskey;
1252     } sp2ns[] = {
1253         { K_UP, NSUpArrowFunctionKey },
1254         { K_DOWN, NSDownArrowFunctionKey },
1255         { K_LEFT, NSLeftArrowFunctionKey },
1256         { K_RIGHT, NSRightArrowFunctionKey },
1257         { K_F1, NSF1FunctionKey },
1258         { K_F2, NSF2FunctionKey },
1259         { K_F3, NSF3FunctionKey },
1260         { K_F4, NSF4FunctionKey },
1261         { K_F5, NSF5FunctionKey },
1262         { K_F6, NSF6FunctionKey },
1263         { K_F7, NSF7FunctionKey },
1264         { K_F8, NSF8FunctionKey },
1265         { K_F9, NSF9FunctionKey },
1266         { K_F10, NSF10FunctionKey },
1267         { K_F11, NSF11FunctionKey },
1268         { K_F12, NSF12FunctionKey },
1269         { K_F13, NSF13FunctionKey },
1270         { K_F14, NSF14FunctionKey },
1271         { K_F15, NSF15FunctionKey },
1272         { K_F16, NSF16FunctionKey },
1273         { K_F17, NSF17FunctionKey },
1274         { K_F18, NSF18FunctionKey },
1275         { K_F19, NSF19FunctionKey },
1276         { K_F20, NSF20FunctionKey },
1277         { K_F21, NSF21FunctionKey },
1278         { K_F22, NSF22FunctionKey },
1279         { K_F23, NSF23FunctionKey },
1280         { K_F24, NSF24FunctionKey },
1281         { K_F25, NSF25FunctionKey },
1282         { K_F26, NSF26FunctionKey },
1283         { K_F27, NSF27FunctionKey },
1284         { K_F28, NSF28FunctionKey },
1285         { K_F29, NSF29FunctionKey },
1286         { K_F30, NSF30FunctionKey },
1287         { K_F31, NSF31FunctionKey },
1288         { K_F32, NSF32FunctionKey },
1289         { K_F33, NSF33FunctionKey },
1290         { K_F34, NSF34FunctionKey },
1291         { K_F35, NSF35FunctionKey },
1292         { K_DEL, NSBackspaceCharacter },
1293         { K_BS, NSDeleteCharacter },
1294         { K_HOME, NSHomeFunctionKey },
1295         { K_END, NSEndFunctionKey },
1296         { K_PAGEUP, NSPageUpFunctionKey },
1297         { K_PAGEDOWN, NSPageDownFunctionKey }
1298     };
1300     int i;
1301     for (i = 0; i < sizeof(sp2ns)/sizeof(sp2ns[0]); ++i) {
1302         if (sp2ns[i].special == key)
1303             return sp2ns[i].nskey;
1304     }
1306     return 0;