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