1 /* vi:set ts=8 sts=4 sw=4 ft=objc:
3 * VIM - Vi IMproved by Bram Moolenaar
4 * MacVim GUI port by Bjorn Winckler
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.
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);
31 @interface MMBackend (Private)
32 - (void)handleMessage:(int)msgid data:(NSData *)data;
33 + (NSDictionary *)specialKeys;
34 - (void)handleKeyDown:(NSString *)key modifiers:(int)mods;
35 - (void)queueMessage:(int)msgid data:(NSData *)data;
36 - (void)connectionDidDie:(NSNotification *)notification;
41 @implementation MMBackend
43 + (MMBackend *)sharedInstance
45 static MMBackend *singleton = nil;
46 return singleton ? singleton : (singleton = [MMBackend new]);
51 if ((self = [super init])) {
52 queue = [[NSMutableArray alloc] init];
53 drawData = [[NSMutableData alloc] initWithCapacity:1024];
54 NSString *path = [[NSBundle mainBundle] pathForResource:@"Colors"
56 colorDict = [[NSDictionary dictionaryWithContentsOfFile:path] retain];
64 //NSLog(@"%@ %s", [self className], _cmd);
66 [[NSNotificationCenter defaultCenter] removeObserver:self];
70 [frontendProxy release];
77 - (void)setBackgroundColor:(int)color
79 backgroundColor = color;
82 - (void)setForegroundColor:(int)color
84 foregroundColor = color;
87 - (void)setSpecialColor:(int)color
92 - (void)setDefaultColorsBackground:(int)bg foreground:(int)fg
94 defaultBackgroundColor = bg;
95 defaultForegroundColor = fg;
97 NSMutableData *data = [NSMutableData data];
99 [data appendBytes:&bg length:sizeof(int)];
100 [data appendBytes:&fg length:sizeof(int)];
102 [self queueMessage:SetDefaultColorsMsgID data:data];
107 NSBundle *mainBundle = [NSBundle mainBundle];
109 // NOTE! If the name of the connection changes here it must also be
110 // updated in MMAppController.m.
111 NSString *name = [NSString stringWithFormat:@"%@-connection",
112 [mainBundle bundleIdentifier]];
113 connection = [NSConnection connectionWithRegisteredName:name host:nil];
116 NSString *path = [mainBundle bundlePath];
117 if (![[NSWorkspace sharedWorkspace] launchApplication:path]) {
118 NSLog(@"WARNING: Failed to launch GUI with path %@", path);
122 // HACK! It would be preferable to launch the GUI using NSWorkspace,
123 // however I have not managed to figure out how to pass arguments using
126 // NOTE! Using NSTask to launch the GUI has the negative side-effect
127 // that the GUI won't be activated (or raised) so there is a hack in
128 // MMWindowController which always raises the app when a new window is
130 NSMutableArray *args = [NSMutableArray arrayWithObjects:
131 [NSString stringWithFormat:@"-%@", MMNoWindowKey], @"yes", nil];
132 NSString *exeName = [[mainBundle infoDictionary]
133 objectForKey:@"CFBundleExecutable"];
134 NSString *path = [mainBundle pathForAuxiliaryExecutable:exeName];
136 NSLog(@"ERROR: Could not find MacVim executable in bundle");
140 [NSTask launchedTaskWithLaunchPath:path arguments:args];
143 // HACK! The NSWorkspaceDidLaunchApplicationNotification does not work
144 // for tasks like this, so poll the mach bootstrap server until it
145 // returns a valid connection. Also set a time-out date so that we
146 // don't get stuck doing this forever.
147 NSDate *timeOutDate = [NSDate dateWithTimeIntervalSinceNow:15];
148 while (!connection &&
149 NSOrderedDescending == [timeOutDate compare:[NSDate date]])
151 [[NSRunLoop currentRunLoop]
152 runMode:NSDefaultRunLoopMode
153 beforeDate:[NSDate dateWithTimeIntervalSinceNow:1]];
155 connection = [NSConnection connectionWithRegisteredName:name
160 NSLog(@"WARNING: Timed-out waiting for GUI to launch.");
165 id proxy = [connection rootProxy];
166 [proxy setProtocolForProxy:@protocol(MMAppProtocol)];
168 [[NSNotificationCenter defaultCenter] addObserver:self
169 selector:@selector(connectionDidDie:)
170 name:NSConnectionDidDieNotification object:connection];
172 int pid = [[NSProcessInfo processInfo] processIdentifier];
175 frontendProxy = [(NSDistantObject*)[proxy connectBackend:self
178 @catch (NSException *e) {
179 NSLog(@"Exception caught when trying to connect backend: \"%@\"", e);
183 [frontendProxy setProtocolForProxy:@protocol(MMAppProtocol)];
186 return connection && frontendProxy;
189 - (BOOL)openVimWindow
191 [self queueMessage:OpenVimWindowMsgID data:nil];
197 int type = ClearAllDrawType;
199 // Any draw commands in queue are effectively obsolete since this clearAll
200 // will negate any effect they have, therefore we may as well clear the
202 [drawData setLength:0];
204 [drawData appendBytes:&type length:sizeof(int)];
206 [drawData appendBytes:&defaultBackgroundColor length:sizeof(int)];
209 - (void)clearBlockFromRow:(int)row1 column:(int)col1
210 toRow:(int)row2 column:(int)col2
212 int type = ClearBlockDrawType;
214 [drawData appendBytes:&type length:sizeof(int)];
216 [drawData appendBytes:&defaultBackgroundColor length:sizeof(int)];
217 [drawData appendBytes:&row1 length:sizeof(int)];
218 [drawData appendBytes:&col1 length:sizeof(int)];
219 [drawData appendBytes:&row2 length:sizeof(int)];
220 [drawData appendBytes:&col2 length:sizeof(int)];
223 - (void)deleteLinesFromRow:(int)row count:(int)count
224 scrollBottom:(int)bottom left:(int)left right:(int)right
226 int type = DeleteLinesDrawType;
228 [drawData appendBytes:&type length:sizeof(int)];
230 [drawData appendBytes:&defaultBackgroundColor length:sizeof(int)];
231 [drawData appendBytes:&row length:sizeof(int)];
232 [drawData appendBytes:&count length:sizeof(int)];
233 [drawData appendBytes:&bottom length:sizeof(int)];
234 [drawData appendBytes:&left length:sizeof(int)];
235 [drawData appendBytes:&right length:sizeof(int)];
238 - (void)replaceString:(char*)s length:(int)len row:(int)row column:(int)col
241 if (len <= 0) return;
243 int type = ReplaceStringDrawType;
245 [drawData appendBytes:&type length:sizeof(int)];
247 [drawData appendBytes:&backgroundColor length:sizeof(int)];
248 [drawData appendBytes:&foregroundColor length:sizeof(int)];
249 [drawData appendBytes:&specialColor length:sizeof(int)];
250 [drawData appendBytes:&row length:sizeof(int)];
251 [drawData appendBytes:&col length:sizeof(int)];
252 [drawData appendBytes:&flags length:sizeof(int)];
253 [drawData appendBytes:&len length:sizeof(int)];
254 [drawData appendBytes:s length:len];
257 - (void)insertLinesFromRow:(int)row count:(int)count
258 scrollBottom:(int)bottom left:(int)left right:(int)right
260 int type = InsertLinesDrawType;
262 [drawData appendBytes:&type length:sizeof(int)];
264 [drawData appendBytes:&defaultBackgroundColor length:sizeof(int)];
265 [drawData appendBytes:&row length:sizeof(int)];
266 [drawData appendBytes:&count length:sizeof(int)];
267 [drawData appendBytes:&bottom length:sizeof(int)];
268 [drawData appendBytes:&left length:sizeof(int)];
269 [drawData appendBytes:&right length:sizeof(int)];
272 - (void)flushQueue:(BOOL)force
274 // NOTE! This method gets called a lot; if we were to flush every time it
275 // was called MacVim would feel unresponsive. So there is a time out which
276 // ensures that the queue isn't flushed too often.
277 if (!force && lastFlushDate && -[lastFlushDate timeIntervalSinceNow]
278 < MMFlushTimeoutInterval)
281 if ([drawData length] > 0) {
282 [self queueMessage:BatchDrawMsgID data:[drawData copy]];
283 [drawData setLength:0];
286 if ([queue count] > 0) {
287 // TODO: Come up with a better way to handle the insertion point.
288 [self updateInsertionPoint];
291 [frontendProxy processCommandQueue:queue];
293 @catch (NSException *e) {
294 NSLog(@"Exception caught when processing command queue: \"%@\"", e);
297 [queue removeAllObjects];
299 [lastFlushDate release];
300 lastFlushDate = [[NSDate date] retain];
304 - (BOOL)waitForInput:(int)milliseconds
306 NSDate *date = milliseconds > 0 ?
307 [NSDate dateWithTimeIntervalSinceNow:.001*milliseconds] :
308 [NSDate distantFuture];
310 [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:date];
312 // I know of no way to figure out if the run loop exited because input was
313 // found or because of a time out, so I need to manually indicate when
314 // input was received in processInput:data: and then reset it every time
316 BOOL yn = inputReceived;
324 // By invalidating the NSConnection the MMWindowController immediately
325 // finds out that the connection is down and as a result
326 // [MMWindowController connectionDidDie:] is invoked.
327 //NSLog(@"%@ %s", [self className], _cmd);
328 [[NSNotificationCenter defaultCenter] removeObserver:self];
329 [connection invalidate];
332 - (void)selectTab:(int)index
334 //NSLog(@"%s%d", _cmd, index);
337 NSData *data = [NSData dataWithBytes:&index length:sizeof(int)];
338 [self queueMessage:SelectTabMsgID data:data];
343 //NSLog(@"%s", _cmd);
345 NSMutableData *data = [NSMutableData data];
347 int idx = tabpage_index(curtab) - 1;
348 [data appendBytes:&idx length:sizeof(int)];
351 for (tp = first_tabpage; tp != NULL; tp = tp->tp_next) {
352 // This function puts the label of the tab in the global 'NameBuff'.
353 get_tabline_label(tp, FALSE);
354 int len = strlen((char*)NameBuff);
355 if (len <= 0) continue;
357 // Count the number of windows in the tabpage.
358 //win_T *wp = tp->tp_firstwin;
360 //for (wincount = 0; wp != NULL; wp = wp->w_next, ++wincount);
362 //[data appendBytes:&wincount length:sizeof(int)];
363 [data appendBytes:&len length:sizeof(int)];
364 [data appendBytes:NameBuff length:len];
367 [self queueMessage:UpdateTabBarMsgID data:data];
370 - (BOOL)tabBarVisible
372 return tabBarVisible;
375 - (void)showTabBar:(BOOL)enable
377 tabBarVisible = enable;
379 int msgid = enable ? ShowTabBarMsgID : HideTabBarMsgID;
380 [self queueMessage:msgid data:nil];
383 - (void)setRows:(int)rows columns:(int)cols
385 //NSLog(@"[VimTask] setRows:%d columns:%d", rows, cols);
387 int dim[] = { rows, cols };
388 NSData *data = [NSData dataWithBytes:&dim length:2*sizeof(int)];
390 [self queueMessage:SetTextDimensionsMsgID data:data];
393 - (void)setVimWindowTitle:(char *)title
395 NSMutableData *data = [NSMutableData data];
396 int len = strlen(title);
397 if (len <= 0) return;
399 [data appendBytes:&len length:sizeof(int)];
400 [data appendBytes:title length:len];
402 [self queueMessage:SetVimWindowTitleMsgID data:data];
405 - (char *)browseForFileInDirectory:(char *)dir title:(char *)title
408 //NSLog(@"browseForFileInDirectory:%s title:%s saving:%d", dir, title,
413 ? [NSString stringWithCString:dir encoding:NSUTF8StringEncoding]
416 ? [NSString stringWithCString:title encoding:NSUTF8StringEncoding]
419 [frontendProxy showSavePanelForDirectory:ds title:ts saving:saving];
421 // Wait until a reply is sent from MMVimController.
422 [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
423 beforeDate:[NSDate distantFuture]];
425 if (dialogReturn && [dialogReturn isKindOfClass:[NSString class]]) {
426 s = vim_strsave((char_u*)[dialogReturn UTF8String]);
429 [dialogReturn release]; dialogReturn = nil;
431 @catch (NSException *e) {
432 NSLog(@"Exception caught when showing save panel: \"%@\"", e);
438 - (oneway void)setDialogReturn:(in bycopy id)obj
440 // NOTE: This is called by
441 // - [MMVimController panelDidEnd:::], and
442 // - [MMVimController alertDidEnd:::],
443 // to indicate that a save/open panel or alert has finished.
445 if (obj != dialogReturn) {
446 [dialogReturn release];
447 dialogReturn = [obj retain];
451 - (int)presentDialogWithType:(int)type title:(char *)title message:(char *)msg
452 buttons:(char *)btns textField:(char *)txtfield
455 NSString *message = nil, *text = nil, *textFieldString = nil;
456 NSArray *buttons = nil;
457 int style = NSInformationalAlertStyle;
459 if (VIM_WARNING == type) style = NSWarningAlertStyle;
460 else if (VIM_ERROR == type) style = NSCriticalAlertStyle;
463 NSString *btnString = [NSString stringWithUTF8String:btns];
464 buttons = [btnString componentsSeparatedByString:@"\n"];
467 message = [NSString stringWithUTF8String:title];
469 text = [NSString stringWithUTF8String:msg];
471 // HACK! If there is a '\n\n' or '\n' sequence in the message, then
472 // make the part up to there into the title. We only do this
473 // because Vim has lots of dialogs without a title and they look
475 // TODO: Fix the actual dialog texts.
476 NSRange eolRange = [text rangeOfString:@"\n\n"];
477 if (NSNotFound == eolRange.location)
478 eolRange = [text rangeOfString:@"\n"];
479 if (NSNotFound != eolRange.location) {
480 message = [text substringToIndex:eolRange.location];
481 text = [text substringFromIndex:NSMaxRange(eolRange)];
486 textFieldString = [NSString stringWithUTF8String:txtfield];
489 [frontendProxy presentDialogWithStyle:style message:message
490 informativeText:text buttonTitles:buttons
491 textFieldString:textFieldString];
493 // Wait until a reply is sent from MMVimController.
494 [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
495 beforeDate:[NSDate distantFuture]];
497 if (dialogReturn && [dialogReturn isKindOfClass:[NSArray class]]
498 && [dialogReturn count]) {
499 retval = [[dialogReturn objectAtIndex:0] intValue];
500 if (txtfield && [dialogReturn count] > 1) {
501 NSString *retString = [dialogReturn objectAtIndex:1];
502 vim_strncpy((char_u*)txtfield, (char_u*)[retString UTF8String],
507 [dialogReturn release]; dialogReturn = nil;
509 @catch (NSException *e) {
510 NSLog(@"Exception caught while showing alert dialog: \"%@\"", e);
516 - (void)updateInsertionPoint
518 NSMutableData *data = [NSMutableData data];
520 int state = get_shape_idx(FALSE);
521 state = (state == SHAPE_IDX_I) || (state == SHAPE_IDX_CI);
523 [data appendBytes:&defaultForegroundColor length:sizeof(int)];
524 [data appendBytes:&gui.row length:sizeof(int)];
525 [data appendBytes:&gui.col length:sizeof(int)];
526 [data appendBytes:&state length:sizeof(int)];
528 [self queueMessage:UpdateInsertionPointMsgID data:data];
531 - (void)addMenuWithTag:(int)tag parent:(int)parentTag name:(char *)name
534 //NSLog(@"addMenuWithTag:%d parent:%d name:%s atIndex:%d", tag, parentTag,
537 int namelen = name ? strlen(name) : 0;
538 NSMutableData *data = [NSMutableData data];
540 [data appendBytes:&tag length:sizeof(int)];
541 [data appendBytes:&parentTag length:sizeof(int)];
542 [data appendBytes:&namelen length:sizeof(int)];
543 if (namelen > 0) [data appendBytes:name length:namelen];
544 [data appendBytes:&index length:sizeof(int)];
546 [self queueMessage:AddMenuMsgID data:data];
549 - (void)addMenuItemWithTag:(int)tag parent:(int)parentTag name:(char *)name
550 tip:(char *)tip icon:(char *)icon
551 keyEquivalent:(int)key modifiers:(int)mods
552 action:(NSString *)action atIndex:(int)index
554 //NSLog(@"addMenuItemWithTag:%d parent:%d name:%s tip:%s atIndex:%d", tag,
555 // parentTag, name, tip, index);
557 int namelen = name ? strlen(name) : 0;
558 int tiplen = tip ? strlen(tip) : 0;
559 int iconlen = icon ? strlen(icon) : 0;
560 int eventFlags = vimModMaskToEventModifierFlags(mods);
561 int actionlen = [action lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
562 NSMutableData *data = [NSMutableData data];
564 key = specialKeyToNSKey(key);
566 [data appendBytes:&tag length:sizeof(int)];
567 [data appendBytes:&parentTag length:sizeof(int)];
568 [data appendBytes:&namelen length:sizeof(int)];
569 if (namelen > 0) [data appendBytes:name length:namelen];
570 [data appendBytes:&tiplen length:sizeof(int)];
571 if (tiplen > 0) [data appendBytes:tip length:tiplen];
572 [data appendBytes:&iconlen length:sizeof(int)];
573 if (iconlen > 0) [data appendBytes:icon length:iconlen];
574 [data appendBytes:&actionlen length:sizeof(int)];
575 if (actionlen > 0) [data appendBytes:[action UTF8String] length:actionlen];
576 [data appendBytes:&index length:sizeof(int)];
577 [data appendBytes:&key length:sizeof(int)];
578 [data appendBytes:&eventFlags length:sizeof(int)];
580 [self queueMessage:AddMenuItemMsgID data:data];
583 - (void)removeMenuItemWithTag:(int)tag
585 NSMutableData *data = [NSMutableData data];
586 [data appendBytes:&tag length:sizeof(int)];
588 [self queueMessage:RemoveMenuItemMsgID data:data];
591 - (void)enableMenuItemWithTag:(int)tag state:(int)enabled
593 NSMutableData *data = [NSMutableData data];
595 [data appendBytes:&tag length:sizeof(int)];
596 [data appendBytes:&enabled length:sizeof(int)];
598 [self queueMessage:EnableMenuItemMsgID data:data];
601 - (void)showPopupMenuWithName:(char *)name atMouseLocation:(BOOL)mouse
603 int len = strlen(name);
604 int row = -1, col = -1;
606 if (len <= 0) return;
608 if (!mouse && curwin) {
609 row = curwin->w_wrow;
610 col = curwin->w_wcol;
613 NSMutableData *data = [NSMutableData data];
615 [data appendBytes:&row length:sizeof(int)];
616 [data appendBytes:&col length:sizeof(int)];
617 [data appendBytes:&len length:sizeof(int)];
618 [data appendBytes:name length:len];
620 [self queueMessage:ShowPopupMenuMsgID data:data];
623 - (void)showToolbar:(int)enable flags:(int)flags
625 NSMutableData *data = [NSMutableData data];
627 [data appendBytes:&enable length:sizeof(int)];
628 [data appendBytes:&flags length:sizeof(int)];
630 [self queueMessage:ShowToolbarMsgID data:data];
633 - (void)createScrollbarWithIdentifier:(long)ident type:(int)type
635 NSMutableData *data = [NSMutableData data];
637 [data appendBytes:&ident length:sizeof(long)];
638 [data appendBytes:&type length:sizeof(int)];
640 [self queueMessage:CreateScrollbarMsgID data:data];
643 - (void)destroyScrollbarWithIdentifier:(long)ident
645 NSMutableData *data = [NSMutableData data];
646 [data appendBytes:&ident length:sizeof(long)];
648 [self queueMessage:DestroyScrollbarMsgID data:data];
651 - (void)showScrollbarWithIdentifier:(long)ident state:(int)visible
653 NSMutableData *data = [NSMutableData data];
655 [data appendBytes:&ident length:sizeof(long)];
656 [data appendBytes:&visible length:sizeof(int)];
658 [self queueMessage:ShowScrollbarMsgID data:data];
661 - (void)setScrollbarPosition:(int)pos length:(int)len identifier:(long)ident
663 NSMutableData *data = [NSMutableData data];
665 [data appendBytes:&ident length:sizeof(long)];
666 [data appendBytes:&pos length:sizeof(int)];
667 [data appendBytes:&len length:sizeof(int)];
669 [self queueMessage:SetScrollbarPositionMsgID data:data];
672 - (void)setScrollbarThumbValue:(long)val size:(long)size max:(long)max
673 identifier:(long)ident
675 float fval = max-size+1 > 0 ? (float)val/(max-size+1) : 0;
676 float prop = (float)size/(max+1);
677 if (fval < 0) fval = 0;
678 else if (fval > 1.0f) fval = 1.0f;
679 if (prop < 0) prop = 0;
680 else if (prop > 1.0f) prop = 1.0f;
682 NSMutableData *data = [NSMutableData data];
684 [data appendBytes:&ident length:sizeof(long)];
685 [data appendBytes:&fval length:sizeof(float)];
686 [data appendBytes:&prop length:sizeof(float)];
688 [self queueMessage:SetScrollbarThumbMsgID data:data];
691 - (BOOL)setFontWithName:(char *)name
695 BOOL parseFailed = NO;
698 fontName = [[[NSString alloc] initWithCString:name
699 encoding:NSUTF8StringEncoding] autorelease];
700 NSArray *components = [fontName componentsSeparatedByString:@":"];
701 if ([components count] == 2) {
702 NSString *sizeString = [components lastObject];
703 if ([sizeString length] > 0
704 && [sizeString characterAtIndex:0] == 'h') {
705 sizeString = [sizeString substringFromIndex:1];
706 if ([sizeString length] > 0) {
707 size = [sizeString floatValue];
708 fontName = [components objectAtIndex:0];
713 } else if ([components count] > 2) {
717 fontName = [[NSFont userFixedPitchFontOfSize:0] displayName];
720 if (!parseFailed && [fontName length] > 0) {
721 if (size < 6 || size > 100) {
722 // Font size 0.0 tells NSFont to use the 'user default size'.
726 NSFont *font = [NSFont fontWithName:fontName size:size];
728 //NSLog(@"Setting font '%@' of size %.2f", fontName, size);
730 lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
732 NSMutableData *data = [NSMutableData data];
734 [data appendBytes:&size length:sizeof(float)];
735 [data appendBytes:&len length:sizeof(int)];
736 [data appendBytes:[fontName UTF8String] length:len];
738 [self queueMessage:SetFontMsgID data:data];
744 NSLog(@"WARNING: Cannot set font with name '%@' of size %.2f",
749 - (void)executeActionWithName:(NSString *)name
751 int len = [name lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
754 NSMutableData *data = [NSMutableData data];
756 [data appendBytes:&len length:sizeof(int)];
757 [data appendBytes:[name UTF8String] length:len];
759 [self queueMessage:ExecuteActionMsgID data:data];
763 - (void)setMouseShape:(int)shape
765 NSMutableData *data = [NSMutableData data];
766 [data appendBytes:&shape length:sizeof(int)];
767 [self queueMessage:SetMouseShapeMsgID data:data];
770 - (int)lookupColorWithKey:(NSString *)key
772 if (!(key && [key length] > 0))
775 // First of all try to lookup key in the color dictionary; note that all
776 // keys in this dictionary are lowercase with no whitespace.
778 NSString *stripKey = [[[[key lowercaseString]
779 stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]
780 componentsSeparatedByString:@" "]
781 componentsJoinedByString:@""];
783 if (stripKey && [stripKey length] > 0) {
784 id obj = [colorDict objectForKey:stripKey];
785 if (obj) return [obj intValue];
787 // The key was not in the dictionary; is it perhaps of the form
790 if ([stripKey length] > 1 && [stripKey characterAtIndex:0] == '#') {
791 NSScanner *scanner = [NSScanner scannerWithString:stripKey];
792 [scanner setScanLocation:1];
794 if ([scanner scanHexInt:&hex]) {
800 NSLog(@"WARNING: No color with key %@ found.", stripKey);
804 - (oneway void)processInput:(int)msgid data:(in NSData *)data
806 [lastFlushDate release];
807 lastFlushDate = [[NSDate date] retain];
809 // HACK! A focus message might get lost, but whenever we get here the GUI
811 if (!gui.in_focus && GotFocusMsgID != msgid && LostFocusMsgID != msgid)
812 gui_focus_change(TRUE);
814 [self handleMessage:msgid data:data];
818 - (oneway void)processInputAndData:(in NSArray *)messages
820 unsigned i, count = [messages count];
822 NSLog(@"WARNING: [messages count] is odd in %s", _cmd);
826 [lastFlushDate release];
827 lastFlushDate = [[NSDate date] retain];
829 for (i = 0; i < count; i += 2) {
830 int msgid = [[messages objectAtIndex:i] intValue];
831 id data = [messages objectAtIndex:i+1];
832 if ([data isEqual:[NSNull null]])
835 [self handleMessage:msgid data:data];
838 // HACK! A focus message might get lost, but whenever we get here the GUI
841 gui_focus_change(TRUE);
846 - (BOOL)checkForModifiedBuffers
849 for (buf = firstbuf; buf != NULL; buf = buf->b_next) {
850 if (bufIsChanged(buf)) {
858 - (BOOL)starRegisterToPasteboard:(byref NSPasteboard *)pboard
860 if (VIsual_active && (State & NORMAL) && clip_star.available) {
861 // If there is no pasteboard, return YES to indicate that there is text
866 clip_copy_selection();
868 // Get the text to put on the pasteboard.
869 long_u len = 0; char_u *str = 0;
870 int type = clip_convert_selection(&str, &len, &clip_star);
874 NSString *string = [[NSString alloc]
875 initWithBytes:str length:len encoding:NSUTF8StringEncoding];
877 NSArray *types = [NSArray arrayWithObject:NSStringPboardType];
878 [pboard declareTypes:types owner:nil];
879 BOOL ok = [pboard setString:string forType:NSStringPboardType];
894 @implementation MMBackend (Private)
896 - (void)handleMessage:(int)msgid data:(NSData *)data
898 if (KillTaskMsgID == msgid) {
899 //NSLog(@"VimTask received kill message; exiting now.");
900 // Set this flag here so that exit does not send TaskExitedMsgID back
901 // to MMVimController.
902 receivedKillTaskMsg = YES;
904 } else if (InsertTextMsgID == msgid) {
906 NSString *key = [[NSString alloc] initWithData:data
907 encoding:NSUTF8StringEncoding];
908 char_u *str = (char_u*)[key UTF8String];
909 int i, len = [key lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
912 char_u *conv_str = NULL;
913 if (input_conv.vc_type != CONV_NONE) {
914 conv_str = string_convert(&input_conv, str, &len);
920 for (i = 0; i < len; ++i) {
921 add_to_input_buf(str+i, 1);
923 // NOTE: If the converted string contains the byte CSI, then it
924 // must be followed by the bytes KS_EXTRA, KE_CSI or things
926 static char_u extra[2] = { KS_EXTRA, KE_CSI };
927 add_to_input_buf(extra, 2);
936 } else if (KeyDownMsgID == msgid || CmdKeyMsgID == msgid) {
938 const void *bytes = [data bytes];
939 int mods = *((int*)bytes); bytes += sizeof(int);
940 int len = *((int*)bytes); bytes += sizeof(int);
941 NSString *key = [[NSString alloc] initWithBytes:bytes length:len
942 encoding:NSUTF8StringEncoding];
943 mods = eventModifierFlagsToVimModMask(mods);
945 [self handleKeyDown:key modifiers:mods];
948 } else if (SelectTabMsgID == msgid) {
950 const void *bytes = [data bytes];
951 int idx = *((int*)bytes) + 1;
952 //NSLog(@"Selecting tab %d", idx);
953 send_tabline_event(idx);
954 } else if (CloseTabMsgID == msgid) {
956 const void *bytes = [data bytes];
957 int idx = *((int*)bytes) + 1;
958 //NSLog(@"Closing tab %d", idx);
959 send_tabline_menu_event(idx, TABLINE_MENU_CLOSE);
960 } else if (AddNewTabMsgID == msgid) {
961 //NSLog(@"Adding new tab");
962 send_tabline_menu_event(0, TABLINE_MENU_NEW);
963 } else if (DraggedTabMsgID == msgid) {
965 const void *bytes = [data bytes];
966 // NOTE! The destination index is 0 based, so do not add 1 to make it 1
968 int idx = *((int*)bytes);
971 } else if (ScrollWheelMsgID == msgid) {
973 const void *bytes = [data bytes];
975 int row = *((int*)bytes); bytes += sizeof(int);
976 int col = *((int*)bytes); bytes += sizeof(int);
977 int flags = *((int*)bytes); bytes += sizeof(int);
978 float dy = *((float*)bytes); bytes += sizeof(float);
980 int button = MOUSE_5;
981 if (dy > 0) button = MOUSE_4;
983 flags = eventModifierFlagsToVimMouseModMask(flags);
985 gui_send_mouse_event(button, col, row, NO, flags);
986 } else if (MouseDownMsgID == msgid) {
988 const void *bytes = [data bytes];
990 int row = *((int*)bytes); bytes += sizeof(int);
991 int col = *((int*)bytes); bytes += sizeof(int);
992 int button = *((int*)bytes); bytes += sizeof(int);
993 int flags = *((int*)bytes); bytes += sizeof(int);
994 int count = *((int*)bytes); bytes += sizeof(int);
996 button = eventButtonNumberToVimMouseButton(button);
997 flags = eventModifierFlagsToVimMouseModMask(flags);
999 gui_send_mouse_event(button, col, row, 0 != count, flags);
1000 } else if (MouseUpMsgID == msgid) {
1002 const void *bytes = [data bytes];
1004 int row = *((int*)bytes); bytes += sizeof(int);
1005 int col = *((int*)bytes); bytes += sizeof(int);
1006 int flags = *((int*)bytes); bytes += sizeof(int);
1008 flags = eventModifierFlagsToVimMouseModMask(flags);
1010 gui_send_mouse_event(MOUSE_RELEASE, col, row, NO, flags);
1011 } else if (MouseDraggedMsgID == msgid) {
1013 const void *bytes = [data bytes];
1015 int row = *((int*)bytes); bytes += sizeof(int);
1016 int col = *((int*)bytes); bytes += sizeof(int);
1017 int flags = *((int*)bytes); bytes += sizeof(int);
1019 flags = eventModifierFlagsToVimMouseModMask(flags);
1021 gui_send_mouse_event(MOUSE_DRAG, col, row, NO, flags);
1022 } else if (SetTextDimensionsMsgID == msgid) {
1024 const void *bytes = [data bytes];
1025 int rows = *((int*)bytes); bytes += sizeof(int);
1026 int cols = *((int*)bytes); bytes += sizeof(int);
1028 // NOTE! Vim doesn't call gui_mch_set_shellsize() after
1029 // gui_resize_shell(), so we have to manually set the rows and columns
1030 // here. (MacVim doesn't change the rows and columns to avoid
1031 // inconsistent states between Vim and MacVim.)
1032 [self setRows:rows columns:cols];
1034 //NSLog(@"[VimTask] Resizing shell to %dx%d.", cols, rows);
1035 gui_resize_shell(cols, rows);
1036 } else if (ExecuteMenuMsgID == msgid) {
1038 const void *bytes = [data bytes];
1039 int tag = *((int*)bytes); bytes += sizeof(int);
1041 vimmenu_T *menu = (vimmenu_T*)tag;
1042 // TODO! Make sure 'menu' is a valid menu pointer!
1046 } else if (ToggleToolbarMsgID == msgid) {
1047 char_u go[sizeof(GO_ALL)+2];
1052 p = vim_strchr(go, GO_TOOLBAR);
1056 char_u *end = go + len;
1062 go[len] = GO_TOOLBAR;
1066 set_option_value((char_u*)"guioptions", 0, go, 0);
1068 // Force screen redraw (does it have to be this complicated?).
1069 redraw_all_later(CLEAR);
1070 update_screen(NOT_VALID);
1073 gui_update_cursor(FALSE, FALSE);
1075 } else if (ScrollbarEventMsgID == msgid) {
1077 const void *bytes = [data bytes];
1078 long ident = *((long*)bytes); bytes += sizeof(long);
1079 int hitPart = *((int*)bytes); bytes += sizeof(int);
1080 float fval = *((float*)bytes); bytes += sizeof(float);
1081 scrollbar_T *sb = gui_find_scrollbar(ident);
1084 scrollbar_T *sb_info = sb->wp ? &sb->wp->w_scrollbars[0] : sb;
1085 long value = sb_info->value;
1086 long size = sb_info->size;
1087 long max = sb_info->max;
1088 BOOL isStillDragging = NO;
1089 BOOL updateKnob = YES;
1092 case NSScrollerDecrementPage:
1093 value -= (size > 2 ? size - 2 : 1);
1095 case NSScrollerIncrementPage:
1096 value += (size > 2 ? size - 2 : 1);
1098 case NSScrollerDecrementLine:
1101 case NSScrollerIncrementLine:
1104 case NSScrollerKnob:
1105 isStillDragging = YES;
1107 case NSScrollerKnobSlot:
1108 value = (long)(fval * (max - size + 1));
1115 //NSLog(@"value %d -> %d", sb_info->value, value);
1116 gui_drag_scrollbar(sb, value, isStillDragging);
1119 // Dragging the knob or option+clicking automatically updates
1120 // the knob position (on the actual NSScroller), so we only
1121 // need to set the knob position in the other cases.
1123 // Update both the left&right vertical scrollbars.
1124 long identLeft = sb->wp->w_scrollbars[SBAR_LEFT].ident;
1125 long identRight = sb->wp->w_scrollbars[SBAR_RIGHT].ident;
1126 [self setScrollbarThumbValue:value size:size max:max
1127 identifier:identLeft];
1128 [self setScrollbarThumbValue:value size:size max:max
1129 identifier:identRight];
1131 // Update the horizontal scrollbar.
1132 [self setScrollbarThumbValue:value size:size max:max
1137 } else if (SetFontMsgID == msgid) {
1139 const void *bytes = [data bytes];
1140 float pointSize = *((float*)bytes); bytes += sizeof(float);
1141 //unsigned len = *((unsigned*)bytes); bytes += sizeof(unsigned);
1142 bytes += sizeof(unsigned); // len not used
1144 NSMutableString *name = [NSMutableString stringWithUTF8String:bytes];
1145 [name appendString:[NSString stringWithFormat:@":h%.2f", pointSize]];
1147 set_option_value((char_u*)"gfn", 0, (char_u*)[name UTF8String], 0);
1149 // Force screen redraw (does it have to be this complicated?).
1150 redraw_all_later(CLEAR);
1151 update_screen(NOT_VALID);
1154 gui_update_cursor(FALSE, FALSE);
1156 } else if (VimShouldCloseMsgID == msgid) {
1158 } else if (DropFilesMsgID == msgid) {
1160 const void *bytes = [data bytes];
1161 int n = *((int*)bytes); bytes += sizeof(int);
1164 int row = *((int*)bytes); bytes += sizeof(int);
1165 int col = *((int*)bytes); bytes += sizeof(int);
1167 char_u **fnames = (char_u **)alloc(n * sizeof(char_u *));
1169 const void *end = [data bytes] + [data length];
1171 while (bytes < end && i < n) {
1172 int len = *((int*)bytes); bytes += sizeof(int);
1173 fnames[i++] = vim_strnsave((char_u*)bytes, len);
1177 // NOTE! This function will free 'fnames'.
1178 gui_handle_drop(col, row, 0, fnames, i < n ? i : n);
1181 // HACK! I'm not sure how to get Vim to open a list of files in tabs,
1182 // so instead I create a ':tab drop' command with all the files to open
1184 NSMutableString *cmd = (n > 1)
1185 ? [NSMutableString stringWithString:@":tab drop"]
1186 : [NSMutableString stringWithString:@":drop"];
1188 const void *end = [data bytes] + [data length];
1190 for (i = 0; i < n && bytes < end; ++i) {
1191 int len = *((int*)bytes); bytes += sizeof(int);
1192 NSMutableString *file =
1193 [NSMutableString stringWithUTF8String:bytes];
1194 [file replaceOccurrencesOfString:@" "
1197 range:NSMakeRange(0, [file length])];
1200 [cmd appendString:@" "];
1201 [cmd appendString:file];
1204 // By going to the last tabpage we ensure that the new tabs will appear
1205 // last (if this call is left out, the taborder becomes messy).
1208 do_cmdline_cmd((char_u*)[cmd UTF8String]);
1210 // Force screen redraw (does it have to be this complicated?).
1211 // (This code was taken from the end of gui_handle_drop().)
1212 update_screen(NOT_VALID);
1215 gui_update_cursor(FALSE, FALSE);
1219 } else if (DropStringMsgID == msgid) {
1221 char_u dropkey[3] = { CSI, KS_EXTRA, (char_u)KE_DROP };
1222 const void *bytes = [data bytes];
1223 int len = *((int*)bytes); bytes += sizeof(int);
1224 NSMutableString *string = [NSMutableString stringWithUTF8String:bytes];
1226 // Replace unrecognized end-of-line sequences with \x0a (line feed).
1227 NSRange range = { 0, [string length] };
1228 unsigned n = [string replaceOccurrencesOfString:@"\x0d\x0a"
1229 withString:@"\x0a" options:0
1232 n = [string replaceOccurrencesOfString:@"\x0d" withString:@"\x0a"
1233 options:0 range:range];
1236 len = [string lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
1237 dnd_yank_drag_data((char_u*)[string UTF8String], len);
1238 add_to_input_buf(dropkey, sizeof(dropkey));
1240 } else if (GotFocusMsgID == msgid) {
1242 gui_focus_change(YES);
1243 } else if (LostFocusMsgID == msgid) {
1245 gui_focus_change(NO);
1246 } else if (MouseMovedMsgID == msgid) {
1247 const void *bytes = [data bytes];
1248 int row = *((int*)bytes); bytes += sizeof(int);
1249 int col = *((int*)bytes); bytes += sizeof(int);
1251 gui_mouse_moved(col, row);
1252 } else if (SetMouseShapeMsgID == msgid) {
1253 const void *bytes = [data bytes];
1254 int shape = *((int*)bytes); bytes += sizeof(int);
1255 update_mouseshape(shape);
1257 NSLog(@"WARNING: Unknown message received (msgid=%d)", msgid);
1261 + (NSDictionary *)specialKeys
1263 static NSDictionary *specialKeys = nil;
1266 NSBundle *mainBundle = [NSBundle mainBundle];
1267 NSString *path = [mainBundle pathForResource:@"SpecialKeys"
1269 specialKeys = [[NSDictionary alloc] initWithContentsOfFile:path];
1275 - (void)handleKeyDown:(NSString *)key modifiers:(int)mods
1282 // Special keys (arrow keys, function keys, etc.) are stored in a plist so
1283 // that new keys can easily be added.
1284 NSString *specialString = [[MMBackend specialKeys]
1286 if (specialString && [specialString length] > 1) {
1287 //NSLog(@"special key: %@", specialString);
1288 int ikey = TO_SPECIAL([specialString characterAtIndex:0],
1289 [specialString characterAtIndex:1]);
1291 ikey = simplify_key(ikey, &mods);
1296 special[1] = K_SECOND(ikey);
1297 special[2] = K_THIRD(ikey);
1301 } else if ([key length] > 0) {
1302 chars = (char_u*)[key UTF8String];
1303 length = [key lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
1304 unichar c = [key characterAtIndex:0];
1306 //NSLog(@"non-special: %@ (hex=%x, mods=%d)", key,
1307 // [key characterAtIndex:0], mods);
1309 if (length == 1 && ((c == Ctrl_C && ctrl_c_interrupts)
1310 || (c == intr_char && intr_char != Ctrl_C))) {
1315 // HACK! In most circumstances the Ctrl and Shift modifiers should be
1316 // cleared since they are already added to the key by the AppKit.
1317 // Unfortunately, the only way to deal with when to clear the modifiers
1318 // or not seems to be to have hard-wired rules like this.
1319 if ( !((' ' == c) || (0xa0 == c) || (mods & MOD_MASK_CMD)) ) {
1320 mods &= ~MOD_MASK_SHIFT;
1321 mods &= ~MOD_MASK_CTRL;
1322 //NSLog(@"clear shift ctrl");
1325 // HACK! All Option+key presses go via 'insert text' messages, except
1326 // for <M-Space>. If the Alt flag is not cleared for <M-Space> it does
1327 // not work to map to it.
1328 if (0xa0 == c && !(mods & MOD_MASK_CMD)) {
1329 //NSLog(@"clear alt");
1330 mods &= ~MOD_MASK_ALT;
1334 if (chars && length > 0) {
1336 //NSLog(@"adding mods: %d", mods);
1338 modChars[1] = KS_MODIFIER;
1340 add_to_input_buf(modChars, 3);
1343 //NSLog(@"add to input buf: 0x%x", chars[0]);
1344 // TODO: Check for CSI bytes?
1345 add_to_input_buf(chars, length);
1349 - (void)queueMessage:(int)msgid data:(NSData *)data
1351 [queue addObject:[NSData dataWithBytes:&msgid length:sizeof(int)]];
1353 [queue addObject:data];
1355 [queue addObject:[NSData data]];
1358 - (void)connectionDidDie:(NSNotification *)notification
1360 // If the main connection to MacVim is lost this means that MacVim was
1361 // either quit (by the user chosing Quit on the MacVim menu), or it has
1362 // crashed. In either case our only option is to quit now.
1363 // TODO: Write backup file?
1365 //NSLog(@"A Vim process lots its connection to MacVim; quitting.");
1369 @end // MMBackend (Private)
1374 static int eventModifierFlagsToVimModMask(int modifierFlags)
1378 if (modifierFlags & NSShiftKeyMask)
1379 modMask |= MOD_MASK_SHIFT;
1380 if (modifierFlags & NSControlKeyMask)
1381 modMask |= MOD_MASK_CTRL;
1382 if (modifierFlags & NSAlternateKeyMask)
1383 modMask |= MOD_MASK_ALT;
1384 if (modifierFlags & NSCommandKeyMask)
1385 modMask |= MOD_MASK_CMD;
1390 static int vimModMaskToEventModifierFlags(int mods)
1394 if (mods & MOD_MASK_SHIFT)
1395 flags |= NSShiftKeyMask;
1396 if (mods & MOD_MASK_CTRL)
1397 flags |= NSControlKeyMask;
1398 if (mods & MOD_MASK_ALT)
1399 flags |= NSAlternateKeyMask;
1400 if (mods & MOD_MASK_CMD)
1401 flags |= NSCommandKeyMask;
1406 static int eventModifierFlagsToVimMouseModMask(int modifierFlags)
1410 if (modifierFlags & NSShiftKeyMask)
1411 modMask |= MOUSE_SHIFT;
1412 if (modifierFlags & NSControlKeyMask)
1413 modMask |= MOUSE_CTRL;
1414 if (modifierFlags & NSAlternateKeyMask)
1415 modMask |= MOUSE_ALT;
1420 static int eventButtonNumberToVimMouseButton(int buttonNumber)
1422 static int mouseButton[] = { MOUSE_LEFT, MOUSE_RIGHT, MOUSE_MIDDLE,
1423 MOUSE_X1, MOUSE_X2 };
1425 return mouseButton[buttonNumber < 5 ? buttonNumber : 0];
1428 static int specialKeyToNSKey(int key)
1430 if (!IS_SPECIAL(key))
1437 { K_UP, NSUpArrowFunctionKey },
1438 { K_DOWN, NSDownArrowFunctionKey },
1439 { K_LEFT, NSLeftArrowFunctionKey },
1440 { K_RIGHT, NSRightArrowFunctionKey },
1441 { K_F1, NSF1FunctionKey },
1442 { K_F2, NSF2FunctionKey },
1443 { K_F3, NSF3FunctionKey },
1444 { K_F4, NSF4FunctionKey },
1445 { K_F5, NSF5FunctionKey },
1446 { K_F6, NSF6FunctionKey },
1447 { K_F7, NSF7FunctionKey },
1448 { K_F8, NSF8FunctionKey },
1449 { K_F9, NSF9FunctionKey },
1450 { K_F10, NSF10FunctionKey },
1451 { K_F11, NSF11FunctionKey },
1452 { K_F12, NSF12FunctionKey },
1453 { K_F13, NSF13FunctionKey },
1454 { K_F14, NSF14FunctionKey },
1455 { K_F15, NSF15FunctionKey },
1456 { K_F16, NSF16FunctionKey },
1457 { K_F17, NSF17FunctionKey },
1458 { K_F18, NSF18FunctionKey },
1459 { K_F19, NSF19FunctionKey },
1460 { K_F20, NSF20FunctionKey },
1461 { K_F21, NSF21FunctionKey },
1462 { K_F22, NSF22FunctionKey },
1463 { K_F23, NSF23FunctionKey },
1464 { K_F24, NSF24FunctionKey },
1465 { K_F25, NSF25FunctionKey },
1466 { K_F26, NSF26FunctionKey },
1467 { K_F27, NSF27FunctionKey },
1468 { K_F28, NSF28FunctionKey },
1469 { K_F29, NSF29FunctionKey },
1470 { K_F30, NSF30FunctionKey },
1471 { K_F31, NSF31FunctionKey },
1472 { K_F32, NSF32FunctionKey },
1473 { K_F33, NSF33FunctionKey },
1474 { K_F34, NSF34FunctionKey },
1475 { K_F35, NSF35FunctionKey },
1476 { K_DEL, NSBackspaceCharacter },
1477 { K_BS, NSDeleteCharacter },
1478 { K_HOME, NSHomeFunctionKey },
1479 { K_END, NSEndFunctionKey },
1480 { K_PAGEUP, NSPageUpFunctionKey },
1481 { K_PAGEDOWN, NSPageDownFunctionKey }
1485 for (i = 0; i < sizeof(sp2ns)/sizeof(sp2ns[0]); ++i) {
1486 if (sp2ns[i].special == key)
1487 return sp2ns[i].nskey;