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.
13 * MMBackend communicates with the frontend (MacVim). It maintains a queue of
14 * output which is flushed to the frontend under controlled circumstances (so
15 * as to maintain a steady framerate). Input from the frontend is also handled
18 * The frontend communicates with the backend via the MMBackendProtocol. In
19 * particular, input is sent to the backend via processInput:data: and Vim
20 * state can be queried from the frontend with evaluateExpression:.
22 * It is very important to realize that all state is held by the backend, the
23 * frontend must either ask for state [MMBackend evaluateExpression:] or wait
24 * for the backend to update [MMAppController processInput:forIdentifier:].
26 * The client/server functionality of Vim is handled by the backend. It sets
27 * up a named NSConnection to which other Vim processes can connect.
34 // NOTE: Colors in MMBackend are stored as unsigned ints on the form 0xaarrggbb
35 // whereas colors in Vim are int without the alpha component. Also note that
36 // 'transp' is assumed to be a value between 0 and 100.
37 #define MM_COLOR(col) ((unsigned)( ((col)&0xffffff) | 0xff000000 ))
38 #define MM_COLOR_WITH_TRANSP(col,transp) \
39 ((unsigned)( ((col)&0xffffff) \
40 | ((((unsigned)((((100-(transp))*255)/100)+.5f))&0xff)<<24) ))
42 // Values for window layout (must match values in main.c).
43 #define WIN_HOR 1 // "-o" horizontally split windows
44 #define WIN_VER 2 // "-O" vertically split windows
45 #define WIN_TABS 3 // "-p" windows on tab pages
47 static unsigned MMServerMax = 1000;
49 // TODO: Move to separate file.
50 static int eventModifierFlagsToVimModMask(int modifierFlags);
51 static int eventModifierFlagsToVimMouseModMask(int modifierFlags);
52 static int eventButtonNumberToVimMouseButton(int buttonNumber);
55 vimmenu_T *menu_for_descriptor(NSArray *desc);
57 static id evalExprCocoa(NSString * expr, NSString ** errstr);
66 static NSString *MMSymlinkWarningString =
67 @"\n\n\tMost likely this is because you have symlinked directly to\n"
68 "\tthe Vim binary, which Cocoa does not allow. Please use an\n"
69 "\talias or the mvim shell script instead. If you have not used\n"
70 "\ta symlink, then your MacVim.app bundle is incomplete.\n\n";
73 extern GuiFont gui_mch_retain_font(GuiFont font);
77 @interface NSString (MMServerNameCompare)
78 - (NSComparisonResult)serverNameCompare:(NSString *)string;
84 @interface MMBackend (Private)
85 - (void)clearDrawData;
86 - (void)didChangeWholeLine;
87 - (void)waitForDialogReturn;
88 - (void)insertVimStateMessage;
89 - (void)processInputQueue;
90 - (void)handleInputEvent:(int)msgid data:(NSData *)data;
91 + (NSDictionary *)specialKeys;
92 - (void)handleInsertText:(NSString *)text;
93 - (void)handleKeyDown:(NSString *)key modifiers:(int)mods;
94 - (void)queueMessage:(int)msgid data:(NSData *)data;
95 - (void)connectionDidDie:(NSNotification *)notification;
96 - (void)blinkTimerFired:(NSTimer *)timer;
97 - (void)focusChange:(BOOL)on;
98 - (void)handleToggleToolbar;
99 - (void)handleScrollbarEvent:(NSData *)data;
100 - (void)handleSetFont:(NSData *)data;
101 - (void)handleDropFiles:(NSData *)data;
102 - (void)handleDropString:(NSData *)data;
103 - (void)startOdbEditWithArguments:(NSDictionary *)args;
104 - (void)handleXcodeMod:(NSData *)data;
105 - (void)handleOpenWithArguments:(NSDictionary *)args;
106 - (BOOL)checkForModifiedBuffers;
107 - (void)addInput:(NSString *)input;
108 - (BOOL)unusedEditor;
109 - (void)redrawScreen;
110 - (void)handleFindReplace:(NSDictionary *)args;
115 @interface MMBackend (ClientServer)
116 - (NSString *)connectionNameFromServerName:(NSString *)name;
117 - (NSConnection *)connectionForServerName:(NSString *)name;
118 - (NSConnection *)connectionForServerPort:(int)port;
119 - (void)serverConnectionDidDie:(NSNotification *)notification;
120 - (void)addClient:(NSDistantObject *)client;
121 - (NSString *)alternateServerNameForName:(NSString *)name;
126 @implementation MMBackend
128 + (MMBackend *)sharedInstance
130 static MMBackend *singleton = nil;
131 return singleton ? singleton : (singleton = [MMBackend new]);
137 if (!self) return nil;
139 outputQueue = [[NSMutableArray alloc] init];
140 inputQueue = [[NSMutableArray alloc] init];
141 drawData = [[NSMutableData alloc] initWithCapacity:1024];
142 connectionNameDict = [[NSMutableDictionary alloc] init];
143 clientProxyDict = [[NSMutableDictionary alloc] init];
144 serverReplyDict = [[NSMutableDictionary alloc] init];
146 NSBundle *mainBundle = [NSBundle mainBundle];
147 NSString *path = [mainBundle pathForResource:@"Colors" ofType:@"plist"];
149 colorDict = [[NSDictionary dictionaryWithContentsOfFile:path] retain];
151 path = [mainBundle pathForResource:@"SystemColors" ofType:@"plist"];
153 sysColorDict = [[NSDictionary dictionaryWithContentsOfFile:path]
156 path = [mainBundle pathForResource:@"Actions" ofType:@"plist"];
158 actionDict = [[NSDictionary dictionaryWithContentsOfFile:path] retain];
160 if (!(colorDict && sysColorDict && actionDict)) {
161 ASLogNotice(@"Failed to load dictionaries.%@", MMSymlinkWarningString);
171 [[NSNotificationCenter defaultCenter] removeObserver:self];
173 gui_mch_free_font(oldWideFont); oldWideFont = NOFONT;
174 [blinkTimer release]; blinkTimer = nil;
175 [alternateServerName release]; alternateServerName = nil;
176 [serverReplyDict release]; serverReplyDict = nil;
177 [clientProxyDict release]; clientProxyDict = nil;
178 [connectionNameDict release]; connectionNameDict = nil;
179 [inputQueue release]; inputQueue = nil;
180 [outputQueue release]; outputQueue = nil;
181 [drawData release]; drawData = nil;
182 [connection release]; connection = nil;
183 [actionDict release]; actionDict = nil;
184 [sysColorDict release]; sysColorDict = nil;
185 [colorDict release]; colorDict = nil;
190 - (void)setBackgroundColor:(int)color
192 backgroundColor = MM_COLOR_WITH_TRANSP(color,p_transp);
195 - (void)setForegroundColor:(int)color
197 foregroundColor = MM_COLOR(color);
200 - (void)setSpecialColor:(int)color
202 specialColor = MM_COLOR(color);
205 - (void)setDefaultColorsBackground:(int)bg foreground:(int)fg
207 defaultBackgroundColor = MM_COLOR_WITH_TRANSP(bg,p_transp);
208 defaultForegroundColor = MM_COLOR(fg);
210 NSMutableData *data = [NSMutableData data];
212 [data appendBytes:&defaultBackgroundColor length:sizeof(unsigned)];
213 [data appendBytes:&defaultForegroundColor length:sizeof(unsigned)];
215 [self queueMessage:SetDefaultColorsMsgID data:data];
218 - (NSConnection *)connection
221 // NOTE! If the name of the connection changes here it must also be
222 // updated in MMAppController.m.
223 NSString *name = [NSString stringWithFormat:@"%@-connection",
224 [[NSBundle mainBundle] bundlePath]];
226 connection = [NSConnection connectionWithRegisteredName:name host:nil];
230 // NOTE: 'connection' may be nil here.
234 - (NSDictionary *)actionDict
239 - (int)initialWindowLayout
241 return initialWindowLayout;
244 - (void)queueMessage:(int)msgid properties:(NSDictionary *)props
246 [self queueMessage:msgid data:[props dictionaryAsData]];
251 if (![self connection]) {
253 // This is a preloaded process and as such should not cause the
254 // MacVim to be opened. We probably got here as a result of the
255 // user quitting MacVim while the process was preloading, so exit
257 // (Don't use mch_exit() since it assumes the process has properly
262 NSBundle *mainBundle = [NSBundle mainBundle];
267 // Launch MacVim using Launch Services (NSWorkspace would be nicer, but
268 // the API to pass Apple Event parameters is broken on 10.4).
269 NSString *path = [mainBundle bundlePath];
270 status = FSPathMakeRef((const UInt8 *)[path UTF8String], &ref, NULL);
271 if (noErr == status) {
272 // Pass parameter to the 'Open' Apple Event that tells MacVim not
273 // to open an untitled window.
274 NSAppleEventDescriptor *desc =
275 [NSAppleEventDescriptor recordDescriptor];
276 [desc setParamDescriptor:
277 [NSAppleEventDescriptor descriptorWithBoolean:NO]
278 forKeyword:keyMMUntitledWindow];
280 LSLaunchFSRefSpec spec = { &ref, 0, NULL, [desc aeDesc],
281 kLSLaunchDefaults, NULL };
282 status = LSOpenFromRefSpec(&spec, NULL);
285 if (noErr != status) {
286 ASLogCrit(@"Failed to launch MacVim (path=%@).%@",
287 path, MMSymlinkWarningString);
291 // Launch MacVim using NSTask. For some reason the above code using
292 // Launch Services sometimes fails on LSOpenFromRefSpec() (when it
293 // fails, the dock icon starts bouncing and never stops). It seems
294 // like rebuilding the Launch Services database takes care of this
295 // problem, but the NSTask way seems more stable so stick with it.
297 // NOTE! Using NSTask to launch the GUI has the negative side-effect
298 // that the GUI won't be activated (or raised) so there is a hack in
299 // MMAppController which raises the app when a new window is opened.
300 NSMutableArray *args = [NSMutableArray arrayWithObjects:
301 [NSString stringWithFormat:@"-%@", MMNoWindowKey], @"yes", nil];
302 NSString *exeName = [[mainBundle infoDictionary]
303 objectForKey:@"CFBundleExecutable"];
304 NSString *path = [mainBundle pathForAuxiliaryExecutable:exeName];
306 ASLogCrit(@"Could not find MacVim executable in bundle.%@",
307 MMSymlinkWarningString);
311 [NSTask launchedTaskWithLaunchPath:path arguments:args];
314 // HACK! Poll the mach bootstrap server until it returns a valid
315 // connection to detect that MacVim has finished launching. Also set a
316 // time-out date so that we don't get stuck doing this forever.
317 NSDate *timeOutDate = [NSDate dateWithTimeIntervalSinceNow:10];
318 while (![self connection] &&
319 NSOrderedDescending == [timeOutDate compare:[NSDate date]])
320 [[NSRunLoop currentRunLoop]
321 runMode:NSDefaultRunLoopMode
322 beforeDate:[NSDate dateWithTimeIntervalSinceNow:.1]];
324 // NOTE: [self connection] will set 'connection' as a side-effect.
326 ASLogCrit(@"Timed-out waiting for GUI to launch.");
332 [[NSNotificationCenter defaultCenter] addObserver:self
333 selector:@selector(connectionDidDie:)
334 name:NSConnectionDidDieNotification object:connection];
336 appProxy = [[connection rootProxy] retain];
337 [appProxy setProtocolForProxy:@protocol(MMAppProtocol)];
339 // NOTE: We do not set any new timeout values for the connection to the
340 // frontend. This means that if the frontend is "stuck" (e.g. in a
341 // modal loop) then any calls to the frontend will block indefinitely
342 // (the default timeouts are huge).
344 int pid = [[NSProcessInfo processInfo] processIdentifier];
346 identifier = [appProxy connectBackend:self pid:pid];
349 @catch (NSException *ex) {
350 ASLogNotice(@"Connect backend failed: reason=%@", ex);
356 - (BOOL)openGUIWindow
358 [self queueMessage:OpenWindowMsgID data:nil];
364 int type = ClearAllDrawType;
366 // Any draw commands in queue are effectively obsolete since this clearAll
367 // will negate any effect they have, therefore we may as well clear the
369 [self clearDrawData];
371 [drawData appendBytes:&type length:sizeof(int)];
374 - (void)clearBlockFromRow:(int)row1 column:(int)col1
375 toRow:(int)row2 column:(int)col2
377 int type = ClearBlockDrawType;
379 [drawData appendBytes:&type length:sizeof(int)];
381 [drawData appendBytes:&defaultBackgroundColor length:sizeof(unsigned)];
382 [drawData appendBytes:&row1 length:sizeof(int)];
383 [drawData appendBytes:&col1 length:sizeof(int)];
384 [drawData appendBytes:&row2 length:sizeof(int)];
385 [drawData appendBytes:&col2 length:sizeof(int)];
388 - (void)deleteLinesFromRow:(int)row count:(int)count
389 scrollBottom:(int)bottom left:(int)left right:(int)right
391 int type = DeleteLinesDrawType;
393 [drawData appendBytes:&type length:sizeof(int)];
395 [drawData appendBytes:&defaultBackgroundColor length:sizeof(unsigned)];
396 [drawData appendBytes:&row length:sizeof(int)];
397 [drawData appendBytes:&count length:sizeof(int)];
398 [drawData appendBytes:&bottom length:sizeof(int)];
399 [drawData appendBytes:&left length:sizeof(int)];
400 [drawData appendBytes:&right length:sizeof(int)];
402 if (left == 0 && right == gui.num_cols-1)
403 [self didChangeWholeLine];
406 - (void)drawString:(char*)s length:(int)len row:(int)row column:(int)col
407 cells:(int)cells flags:(int)flags
409 if (len <= 0 || cells <= 0) return;
411 int type = DrawStringDrawType;
413 [drawData appendBytes:&type length:sizeof(int)];
415 [drawData appendBytes:&backgroundColor length:sizeof(unsigned)];
416 [drawData appendBytes:&foregroundColor length:sizeof(unsigned)];
417 [drawData appendBytes:&specialColor length:sizeof(unsigned)];
418 [drawData appendBytes:&row length:sizeof(int)];
419 [drawData appendBytes:&col length:sizeof(int)];
420 [drawData appendBytes:&cells length:sizeof(int)];
421 [drawData appendBytes:&flags length:sizeof(int)];
422 [drawData appendBytes:&len length:sizeof(int)];
423 [drawData appendBytes:s length:len];
426 - (void)insertLinesFromRow:(int)row count:(int)count
427 scrollBottom:(int)bottom left:(int)left right:(int)right
429 int type = InsertLinesDrawType;
431 [drawData appendBytes:&type length:sizeof(int)];
433 [drawData appendBytes:&defaultBackgroundColor length:sizeof(unsigned)];
434 [drawData appendBytes:&row length:sizeof(int)];
435 [drawData appendBytes:&count length:sizeof(int)];
436 [drawData appendBytes:&bottom length:sizeof(int)];
437 [drawData appendBytes:&left length:sizeof(int)];
438 [drawData appendBytes:&right length:sizeof(int)];
440 if (left == 0 && right == gui.num_cols-1)
441 [self didChangeWholeLine];
444 - (void)drawCursorAtRow:(int)row column:(int)col shape:(int)shape
445 fraction:(int)percent color:(int)color
447 int type = DrawCursorDrawType;
448 unsigned uc = MM_COLOR(color);
450 [drawData appendBytes:&type length:sizeof(int)];
452 [drawData appendBytes:&uc length:sizeof(unsigned)];
453 [drawData appendBytes:&row length:sizeof(int)];
454 [drawData appendBytes:&col length:sizeof(int)];
455 [drawData appendBytes:&shape length:sizeof(int)];
456 [drawData appendBytes:&percent length:sizeof(int)];
459 - (void)drawInvertedRectAtRow:(int)row column:(int)col numRows:(int)nr
460 numColumns:(int)nc invert:(int)invert
462 int type = DrawInvertedRectDrawType;
463 [drawData appendBytes:&type length:sizeof(int)];
465 [drawData appendBytes:&row length:sizeof(int)];
466 [drawData appendBytes:&col length:sizeof(int)];
467 [drawData appendBytes:&nr length:sizeof(int)];
468 [drawData appendBytes:&nc length:sizeof(int)];
469 [drawData appendBytes:&invert length:sizeof(int)];
474 // Keep running the run-loop until there is no more input to process.
475 while (CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0, true)
476 == kCFRunLoopRunHandledSource)
480 - (void)flushQueue:(BOOL)force
482 // NOTE: This variable allows for better control over when the queue is
483 // flushed. It can be set to YES at the beginning of a sequence of calls
484 // that may potentially add items to the queue, and then restored back to
486 if (flushDisabled) return;
488 if ([drawData length] > 0) {
489 // HACK! Detect changes to 'guifontwide'.
490 if (gui.wide_font != oldWideFont) {
491 gui_mch_free_font(oldWideFont);
492 oldWideFont = gui_mch_retain_font(gui.wide_font);
493 [self setFont:oldWideFont wide:YES];
496 int type = SetCursorPosDrawType;
497 [drawData appendBytes:&type length:sizeof(type)];
498 [drawData appendBytes:&gui.row length:sizeof(gui.row)];
499 [drawData appendBytes:&gui.col length:sizeof(gui.col)];
501 [self queueMessage:BatchDrawMsgID data:[drawData copy]];
502 [self clearDrawData];
505 if ([outputQueue count] > 0) {
506 [self insertVimStateMessage];
509 ASLogDebug(@"Flushing queue: %@",
510 debugStringForMessageQueue(outputQueue));
511 [appProxy processInput:outputQueue forIdentifier:identifier];
513 @catch (NSException *ex) {
514 ASLogNotice(@"processInput:forIdentifer failed: reason=%@", ex);
515 if (![connection isValid]) {
516 ASLogNotice(@"Connection is invalid, exit now!");
517 ASLogDebug(@"waitForAck=%d got_int=%d", waitForAck, got_int);
522 [outputQueue removeAllObjects];
526 - (BOOL)waitForInput:(int)milliseconds
528 // Return NO if we timed out waiting for input, otherwise return YES.
529 BOOL inputReceived = NO;
531 // Only start the run loop if the input queue is empty, otherwise process
532 // the input first so that the input on queue isn't delayed.
533 if ([inputQueue count]) {
536 // Wait for the specified amount of time, unless 'milliseconds' is
537 // negative in which case we wait "forever" (1e6 seconds translates to
538 // approximately 11 days).
539 CFTimeInterval dt = (milliseconds >= 0 ? .001*milliseconds : 1e6);
541 while (CFRunLoopRunInMode(kCFRunLoopDefaultMode, dt, true)
542 == kCFRunLoopRunHandledSource) {
543 // In order to ensure that all input on the run-loop has been
544 // processed we set the timeout to 0 and keep processing until the
545 // run-loop times out.
551 // The above calls may have placed messages on the input queue so process
552 // it now. This call may enter a blocking loop.
553 if ([inputQueue count] > 0)
554 [self processInputQueue];
556 return inputReceived;
561 // NOTE: This is called if mch_exit() is called. Since we assume here that
562 // the process has started properly, be sure to use exit() instead of
563 // mch_exit() to prematurely terminate a process (or set 'isTerminating'
566 // Make sure no connectionDidDie: notification is received now that we are
568 [[NSNotificationCenter defaultCenter] removeObserver:self];
570 // The 'isTerminating' flag indicates that the frontend is also exiting so
571 // there is no need to flush any more output since the frontend won't look
573 if (!isTerminating && [connection isValid]) {
575 // Flush the entire queue in case a VimLeave autocommand added
576 // something to the queue.
577 [self queueMessage:CloseWindowMsgID data:nil];
578 ASLogDebug(@"Flush output queue before exit: %@",
579 debugStringForMessageQueue(outputQueue));
580 [appProxy processInput:outputQueue forIdentifier:identifier];
582 @catch (NSException *ex) {
583 ASLogNotice(@"CloseWindowMsgID send failed: reason=%@", ex);
586 // NOTE: If Cmd-w was pressed to close the window the menu is briefly
587 // highlighted and during this pause the frontend won't receive any DO
588 // messages. If the Vim process exits before this highlighting has
589 // finished Cocoa will emit the following error message:
590 // *** -[NSMachPort handlePortMessage:]: dropping incoming DO message
591 // because the connection or ports are invalid
592 // To avoid this warning we delay here. If the warning still appears
593 // this delay may need to be increased.
597 #ifdef MAC_CLIENTSERVER
598 // The default connection is used for the client/server code.
599 [[NSConnection defaultConnection] setRootObject:nil];
600 [[NSConnection defaultConnection] invalidate];
604 - (void)selectTab:(int)index
607 NSData *data = [NSData dataWithBytes:&index length:sizeof(int)];
608 [self queueMessage:SelectTabMsgID data:data];
613 NSMutableData *data = [NSMutableData data];
615 int idx = tabpage_index(curtab) - 1;
616 [data appendBytes:&idx length:sizeof(int)];
619 for (tp = first_tabpage; tp != NULL; tp = tp->tp_next) {
620 // Count the number of windows in the tabpage.
621 //win_T *wp = tp->tp_firstwin;
623 //for (wincount = 0; wp != NULL; wp = wp->w_next, ++wincount);
624 //[data appendBytes:&wincount length:sizeof(int)];
626 int tabProp = MMTabInfoCount;
627 [data appendBytes:&tabProp length:sizeof(int)];
628 for (tabProp = MMTabLabel; tabProp < MMTabInfoCount; ++tabProp) {
629 // This function puts the label of the tab in the global 'NameBuff'.
630 get_tabline_label(tp, (tabProp == MMTabToolTip));
631 NSString *s = [NSString stringWithVimString:NameBuff];
632 int len = [s lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
636 [data appendBytes:&len length:sizeof(int)];
638 [data appendBytes:[s UTF8String] length:len];
642 [self queueMessage:UpdateTabBarMsgID data:data];
645 - (BOOL)tabBarVisible
647 return tabBarVisible;
650 - (void)showTabBar:(BOOL)enable
652 tabBarVisible = enable;
654 int msgid = enable ? ShowTabBarMsgID : HideTabBarMsgID;
655 [self queueMessage:msgid data:nil];
658 - (void)setRows:(int)rows columns:(int)cols
660 int dim[] = { rows, cols };
661 NSData *data = [NSData dataWithBytes:&dim length:2*sizeof(int)];
663 [self queueMessage:SetTextDimensionsMsgID data:data];
666 - (void)setWindowTitle:(char *)title
668 NSMutableData *data = [NSMutableData data];
669 int len = strlen(title);
670 if (len <= 0) return;
672 [data appendBytes:&len length:sizeof(int)];
673 [data appendBytes:title length:len];
675 [self queueMessage:SetWindowTitleMsgID data:data];
678 - (void)setDocumentFilename:(char *)filename
680 NSMutableData *data = [NSMutableData data];
681 int len = filename ? strlen(filename) : 0;
683 [data appendBytes:&len length:sizeof(int)];
685 [data appendBytes:filename length:len];
687 [self queueMessage:SetDocumentFilenameMsgID data:data];
690 - (char *)browseForFileWithAttributes:(NSDictionary *)attr
694 [self queueMessage:BrowseForFileMsgID properties:attr];
695 [self flushQueue:YES];
698 [self waitForDialogReturn];
700 if (dialogReturn && [dialogReturn isKindOfClass:[NSString class]])
701 s = [dialogReturn vimStringSave];
703 [dialogReturn release]; dialogReturn = nil;
705 @catch (NSException *ex) {
706 ASLogNotice(@"Exception: reason=%@", ex);
712 - (oneway void)setDialogReturn:(in bycopy id)obj
714 ASLogDebug(@"%@", obj);
716 // NOTE: This is called by
717 // - [MMVimController panelDidEnd:::], and
718 // - [MMVimController alertDidEnd:::],
719 // to indicate that a save/open panel or alert has finished.
721 // We want to distinguish between "no dialog return yet" and "dialog
722 // returned nothing". The former can be tested with dialogReturn == nil,
723 // the latter with dialogReturn == [NSNull null].
724 if (!obj) obj = [NSNull null];
726 if (obj != dialogReturn) {
727 [dialogReturn release];
728 dialogReturn = [obj retain];
732 - (int)showDialogWithAttributes:(NSDictionary *)attr textField:(char *)txtfield
736 [self queueMessage:ShowDialogMsgID properties:attr];
737 [self flushQueue:YES];
740 [self waitForDialogReturn];
742 if (dialogReturn && [dialogReturn isKindOfClass:[NSArray class]]
743 && [dialogReturn count]) {
744 retval = [[dialogReturn objectAtIndex:0] intValue];
745 if (txtfield && [dialogReturn count] > 1) {
746 NSString *retString = [dialogReturn objectAtIndex:1];
747 char_u *ret = (char_u*)[retString UTF8String];
749 ret = CONVERT_FROM_UTF8(ret);
751 vim_strncpy((char_u*)txtfield, ret, IOSIZE - 1);
753 CONVERT_FROM_UTF8_FREE(ret);
758 [dialogReturn release]; dialogReturn = nil;
760 @catch (NSException *ex) {
761 ASLogNotice(@"Exception: reason=%@", ex);
767 - (void)showToolbar:(int)enable flags:(int)flags
769 NSMutableData *data = [NSMutableData data];
771 [data appendBytes:&enable length:sizeof(int)];
772 [data appendBytes:&flags length:sizeof(int)];
774 [self queueMessage:ShowToolbarMsgID data:data];
777 - (void)createScrollbarWithIdentifier:(long)ident type:(int)type
779 NSMutableData *data = [NSMutableData data];
781 [data appendBytes:&ident length:sizeof(long)];
782 [data appendBytes:&type length:sizeof(int)];
784 [self queueMessage:CreateScrollbarMsgID data:data];
787 - (void)destroyScrollbarWithIdentifier:(long)ident
789 NSMutableData *data = [NSMutableData data];
790 [data appendBytes:&ident length:sizeof(long)];
792 [self queueMessage:DestroyScrollbarMsgID data:data];
795 - (void)showScrollbarWithIdentifier:(long)ident state:(int)visible
797 NSMutableData *data = [NSMutableData data];
799 [data appendBytes:&ident length:sizeof(long)];
800 [data appendBytes:&visible length:sizeof(int)];
802 [self queueMessage:ShowScrollbarMsgID data:data];
805 - (void)setScrollbarPosition:(int)pos length:(int)len identifier:(long)ident
807 NSMutableData *data = [NSMutableData data];
809 [data appendBytes:&ident length:sizeof(long)];
810 [data appendBytes:&pos length:sizeof(int)];
811 [data appendBytes:&len length:sizeof(int)];
813 [self queueMessage:SetScrollbarPositionMsgID data:data];
816 - (void)setScrollbarThumbValue:(long)val size:(long)size max:(long)max
817 identifier:(long)ident
819 float fval = max-size+1 > 0 ? (float)val/(max-size+1) : 0;
820 float prop = (float)size/(max+1);
821 if (fval < 0) fval = 0;
822 else if (fval > 1.0f) fval = 1.0f;
823 if (prop < 0) prop = 0;
824 else if (prop > 1.0f) prop = 1.0f;
826 NSMutableData *data = [NSMutableData data];
828 [data appendBytes:&ident length:sizeof(long)];
829 [data appendBytes:&fval length:sizeof(float)];
830 [data appendBytes:&prop length:sizeof(float)];
832 [self queueMessage:SetScrollbarThumbMsgID data:data];
835 - (void)setFont:(GuiFont)font wide:(BOOL)wide
837 NSString *fontName = (NSString *)font;
839 NSArray *components = [fontName componentsSeparatedByString:@":"];
840 if ([components count] == 2) {
841 size = [[components lastObject] floatValue];
842 fontName = [components objectAtIndex:0];
845 int len = [fontName lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
846 NSMutableData *data = [NSMutableData data];
847 [data appendBytes:&size length:sizeof(float)];
848 [data appendBytes:&len length:sizeof(int)];
851 [data appendBytes:[fontName UTF8String] length:len];
853 return; // Only the wide font can be set to nothing
855 [self queueMessage:(wide ? SetWideFontMsgID : SetFontMsgID) data:data];
858 - (void)executeActionWithName:(NSString *)name
860 int len = [name lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
863 NSMutableData *data = [NSMutableData data];
865 [data appendBytes:&len length:sizeof(int)];
866 [data appendBytes:[name UTF8String] length:len];
868 [self queueMessage:ExecuteActionMsgID data:data];
872 - (void)setMouseShape:(int)shape
874 NSMutableData *data = [NSMutableData data];
875 [data appendBytes:&shape length:sizeof(int)];
876 [self queueMessage:SetMouseShapeMsgID data:data];
879 - (void)setBlinkWait:(int)wait on:(int)on off:(int)off
881 // Vim specifies times in milliseconds, whereas Cocoa wants them in
883 blinkWaitInterval = .001f*wait;
884 blinkOnInterval = .001f*on;
885 blinkOffInterval = .001f*off;
891 [blinkTimer invalidate];
892 [blinkTimer release];
896 if (blinkWaitInterval > 0 && blinkOnInterval > 0 && blinkOffInterval > 0
898 blinkState = MMBlinkStateOn;
900 [[NSTimer scheduledTimerWithTimeInterval:blinkWaitInterval
902 selector:@selector(blinkTimerFired:)
903 userInfo:nil repeats:NO] retain];
904 gui_update_cursor(TRUE, FALSE);
905 [self flushQueue:YES];
911 if (MMBlinkStateOff == blinkState) {
912 gui_update_cursor(TRUE, FALSE);
913 [self flushQueue:YES];
916 blinkState = MMBlinkStateNone;
919 - (void)adjustLinespace:(int)linespace
921 NSMutableData *data = [NSMutableData data];
922 [data appendBytes:&linespace length:sizeof(int)];
923 [self queueMessage:AdjustLinespaceMsgID data:data];
928 [self queueMessage:ActivateMsgID data:nil];
931 - (void)setPreEditRow:(int)row column:(int)col
933 NSMutableData *data = [NSMutableData data];
934 [data appendBytes:&row length:sizeof(int)];
935 [data appendBytes:&col length:sizeof(int)];
936 [self queueMessage:SetPreEditPositionMsgID data:data];
939 - (int)lookupColorWithKey:(NSString *)key
941 if (!(key && [key length] > 0))
944 NSString *stripKey = [[[[key lowercaseString]
945 stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]
946 componentsSeparatedByString:@" "]
947 componentsJoinedByString:@""];
949 if (stripKey && [stripKey length] > 0) {
950 // First of all try to lookup key in the color dictionary; note that
951 // all keys in this dictionary are lowercase with no whitespace.
952 id obj = [colorDict objectForKey:stripKey];
953 if (obj) return [obj intValue];
955 // The key was not in the dictionary; is it perhaps of the form
957 if ([stripKey length] > 1 && [stripKey characterAtIndex:0] == '#') {
958 NSScanner *scanner = [NSScanner scannerWithString:stripKey];
959 [scanner setScanLocation:1];
961 if ([scanner scanHexInt:&hex]) {
966 // As a last resort, check if it is one of the system defined colors.
967 // The keys in this dictionary are also lowercase with no whitespace.
968 obj = [sysColorDict objectForKey:stripKey];
970 NSColor *col = [NSColor performSelector:NSSelectorFromString(obj)];
973 col = [col colorUsingColorSpaceName:NSCalibratedRGBColorSpace];
974 [col getRed:&r green:&g blue:&b alpha:&a];
975 return (((int)(r*255+.5f) & 0xff) << 16)
976 + (((int)(g*255+.5f) & 0xff) << 8)
977 + ((int)(b*255+.5f) & 0xff);
982 ASLogNotice(@"No color with key %@ found.", stripKey);
986 - (BOOL)hasSpecialKeyWithValue:(NSString *)value
988 NSEnumerator *e = [[MMBackend specialKeys] objectEnumerator];
991 while ((obj = [e nextObject])) {
992 if ([value isEqual:obj])
999 - (void)enterFullscreen:(int)fuoptions background:(int)bg
1001 NSMutableData *data = [NSMutableData data];
1002 [data appendBytes:&fuoptions length:sizeof(int)];
1004 [data appendBytes:&bg length:sizeof(int)];
1005 [self queueMessage:EnterFullscreenMsgID data:data];
1008 - (void)leaveFullscreen
1010 [self queueMessage:LeaveFullscreenMsgID data:nil];
1013 - (void)setFullscreenBackgroundColor:(int)color
1015 NSMutableData *data = [NSMutableData data];
1016 color = MM_COLOR(color);
1017 [data appendBytes:&color length:sizeof(int)];
1019 [self queueMessage:SetFullscreenColorMsgID data:data];
1022 - (void)setAntialias:(BOOL)antialias
1024 int msgid = antialias ? EnableAntialiasMsgID : DisableAntialiasMsgID;
1026 [self queueMessage:msgid data:nil];
1029 - (void)updateModifiedFlag
1031 // Notify MacVim if _any_ buffer has changed from unmodified to modified or
1033 int msgid = [self checkForModifiedBuffers]
1034 ? BuffersModifiedMsgID : BuffersNotModifiedMsgID;
1036 [self queueMessage:msgid data:nil];
1039 - (oneway void)processInput:(int)msgid data:(in bycopy NSData *)data
1041 // Look for Cmd-. and Ctrl-C immediately instead of waiting until the input
1042 // queue is processed since that only happens in waitForInput: (and Vim
1043 // regularly checks for Ctrl-C in between waiting for input).
1044 // Similarly, TerminateNowMsgID must be checked immediately otherwise code
1045 // which waits on the run loop will fail to detect this message (e.g. in
1046 // waitForConnectionAcknowledgement).
1048 if (InsertTextMsgID == msgid && data != nil) {
1049 const void *bytes = [data bytes];
1050 bytes += sizeof(int);
1051 int len = *((int*)bytes); bytes += sizeof(int);
1053 char_u *str = (char_u*)bytes;
1054 if ((str[0] == Ctrl_C && ctrl_c_interrupts) ||
1055 (str[0] == intr_char && intr_char != Ctrl_C)) {
1057 [inputQueue removeAllObjects];
1061 } else if (TerminateNowMsgID == msgid) {
1062 // Terminate immediately (the frontend is about to quit or this process
1063 // was aborted). Don't preserve modified files since the user would
1064 // already have been presented with a dialog warning if there were any
1065 // modified files when we get here.
1066 isTerminating = YES;
1071 // Remove all previous instances of this message from the input queue, else
1072 // the input queue may fill up as a result of Vim not being able to keep up
1073 // with the speed at which new messages are received.
1074 // Keyboard input is never dropped, unless the input represents and
1075 // auto-repeated key.
1077 BOOL isKeyRepeat = NO;
1078 BOOL isKeyboardInput = NO;
1080 if (data && (InsertTextMsgID == msgid || KeyDownMsgID == msgid ||
1081 CmdKeyMsgID == msgid)) {
1082 isKeyboardInput = YES;
1084 // The lowest bit of the first int is set if this key is a repeat.
1085 int flags = *((int*)[data bytes]);
1090 // Keyboard input is not removed from the queue; repeats are ignored if
1091 // there already is keyboard input on the input queue.
1092 if (isKeyRepeat || !isKeyboardInput) {
1093 int i, count = [inputQueue count];
1094 for (i = 1; i < count; i+=2) {
1095 if ([[inputQueue objectAtIndex:i-1] intValue] == msgid) {
1099 [inputQueue removeObjectAtIndex:i];
1100 [inputQueue removeObjectAtIndex:i-1];
1106 [inputQueue addObject:[NSNumber numberWithInt:msgid]];
1107 [inputQueue addObject:(data ? (id)data : [NSNull null])];
1110 - (id)evaluateExpressionCocoa:(in bycopy NSString *)expr
1111 errorString:(out bycopy NSString **)errstr
1113 return evalExprCocoa(expr, errstr);
1117 - (NSString *)evaluateExpression:(in bycopy NSString *)expr
1119 NSString *eval = nil;
1120 char_u *s = (char_u*)[expr UTF8String];
1123 s = CONVERT_FROM_UTF8(s);
1126 char_u *res = eval_client_expr_to_string(s);
1129 CONVERT_FROM_UTF8_FREE(s);
1135 s = CONVERT_TO_UTF8(s);
1137 eval = [NSString stringWithUTF8String:(char*)s];
1139 CONVERT_TO_UTF8_FREE(s);
1147 - (BOOL)starRegisterToPasteboard:(byref NSPasteboard *)pboard
1149 // TODO: This method should share code with clip_mch_request_selection().
1151 if (VIsual_active && (State & NORMAL) && clip_star.available) {
1152 // If there is no pasteboard, return YES to indicate that there is text
1157 clip_copy_selection();
1159 // Get the text to put on the pasteboard.
1160 long_u llen = 0; char_u *str = 0;
1161 int type = clip_convert_selection(&str, &llen, &clip_star);
1165 // TODO: Avoid overflow.
1166 int len = (int)llen;
1168 if (output_conv.vc_type != CONV_NONE) {
1169 char_u *conv_str = string_convert(&output_conv, str, &len);
1177 NSString *string = [[NSString alloc]
1178 initWithBytes:str length:len encoding:NSUTF8StringEncoding];
1180 NSArray *types = [NSArray arrayWithObject:NSStringPboardType];
1181 [pboard declareTypes:types owner:nil];
1182 BOOL ok = [pboard setString:string forType:NSStringPboardType];
1193 - (oneway void)addReply:(in bycopy NSString *)reply
1194 server:(in byref id <MMVimServerProtocol>)server
1196 ASLogDebug(@"reply=%@ server=%@", reply, (id)server);
1198 // Replies might come at any time and in any order so we keep them in an
1199 // array inside a dictionary with the send port used as key.
1201 NSConnection *conn = [(NSDistantObject*)server connectionForProxy];
1202 // HACK! Assume connection uses mach ports.
1203 int port = [(NSMachPort*)[conn sendPort] machPort];
1204 NSNumber *key = [NSNumber numberWithInt:port];
1206 NSMutableArray *replies = [serverReplyDict objectForKey:key];
1208 replies = [NSMutableArray array];
1209 [serverReplyDict setObject:replies forKey:key];
1212 [replies addObject:reply];
1215 - (void)addInput:(in bycopy NSString *)input
1216 client:(in byref id <MMVimClientProtocol>)client
1218 ASLogDebug(@"input=%@ client=%@", input, (id)client);
1220 // NOTE: We don't call addInput: here because it differs from
1221 // server_to_input_buf() in that it always sets the 'silent' flag and we
1222 // don't want the MacVim client/server code to behave differently from
1224 char_u *s = [input vimStringSave];
1225 server_to_input_buf(s);
1228 [self addClient:(id)client];
1231 - (NSString *)evaluateExpression:(in bycopy NSString *)expr
1232 client:(in byref id <MMVimClientProtocol>)client
1234 [self addClient:(id)client];
1235 return [self evaluateExpression:expr];
1238 - (void)registerServerWithName:(NSString *)name
1240 NSString *svrName = name;
1241 NSConnection *svrConn = [NSConnection defaultConnection];
1244 for (i = 0; i < MMServerMax; ++i) {
1245 NSString *connName = [self connectionNameFromServerName:svrName];
1247 if ([svrConn registerName:connName]) {
1248 ASLogInfo(@"Registered server with name: %@", svrName);
1250 // TODO: Set request/reply time-outs to something else?
1252 // Don't wait for requests (time-out means that the message is
1254 [svrConn setRequestTimeout:0];
1255 //[svrConn setReplyTimeout:MMReplyTimeout];
1256 [svrConn setRootObject:self];
1258 // NOTE: 'serverName' is a global variable
1259 serverName = [svrName vimStringSave];
1261 set_vim_var_string(VV_SEND_SERVER, serverName, -1);
1264 need_maketitle = TRUE;
1266 [self queueMessage:SetServerNameMsgID data:
1267 [svrName dataUsingEncoding:NSUTF8StringEncoding]];
1271 svrName = [NSString stringWithFormat:@"%@%d", name, i+1];
1275 - (BOOL)sendToServer:(NSString *)name string:(NSString *)string
1276 reply:(char_u **)reply port:(int *)port expression:(BOOL)expr
1279 // NOTE: If 'name' equals 'serverName' then the request is local (client
1280 // and server are the same). This case is not handled separately, so a
1281 // connection will be set up anyway (this simplifies the code).
1283 NSConnection *conn = [self connectionForServerName:name];
1286 char_u *s = (char_u*)[name UTF8String];
1288 s = CONVERT_FROM_UTF8(s);
1290 EMSG2(_(e_noserver), s);
1292 CONVERT_FROM_UTF8_FREE(s);
1299 // HACK! Assume connection uses mach ports.
1300 *port = [(NSMachPort*)[conn sendPort] machPort];
1303 id proxy = [conn rootProxy];
1304 [proxy setProtocolForProxy:@protocol(MMVimServerProtocol)];
1308 NSString *eval = [proxy evaluateExpression:string client:self];
1311 *reply = [eval vimStringSave];
1313 *reply = vim_strsave((char_u*)_(e_invexprmsg));
1320 [proxy addInput:string client:self];
1323 @catch (NSException *ex) {
1324 ASLogNotice(@"Exception: reason=%@", ex);
1331 - (NSArray *)serverList
1333 NSArray *list = nil;
1335 if ([self connection]) {
1336 id proxy = [connection rootProxy];
1337 [proxy setProtocolForProxy:@protocol(MMAppProtocol)];
1340 list = [proxy serverList];
1342 @catch (NSException *ex) {
1343 ASLogNotice(@"serverList failed: reason=%@", ex);
1346 EMSG(_("E???: No connection to MacVim, server listing not possible."));
1352 - (NSString *)peekForReplyOnPort:(int)port
1354 ASLogDebug(@"port=%d", port);
1356 NSNumber *key = [NSNumber numberWithInt:port];
1357 NSMutableArray *replies = [serverReplyDict objectForKey:key];
1358 if (replies && [replies count]) {
1359 ASLogDebug(@" %d replies, topmost is: %@", [replies count],
1360 [replies objectAtIndex:0]);
1361 return [replies objectAtIndex:0];
1364 ASLogDebug(@" No replies");
1368 - (NSString *)waitForReplyOnPort:(int)port
1370 ASLogDebug(@"port=%d", port);
1372 NSConnection *conn = [self connectionForServerPort:port];
1376 NSNumber *key = [NSNumber numberWithInt:port];
1377 NSMutableArray *replies = nil;
1378 NSString *reply = nil;
1380 // Wait for reply as long as the connection to the server is valid (unless
1381 // user interrupts wait with Ctrl-C).
1382 while (!got_int && [conn isValid] &&
1383 !(replies = [serverReplyDict objectForKey:key])) {
1384 [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
1385 beforeDate:[NSDate distantFuture]];
1389 if ([replies count] > 0) {
1390 reply = [[replies objectAtIndex:0] retain];
1391 ASLogDebug(@" Got reply: %@", reply);
1392 [replies removeObjectAtIndex:0];
1393 [reply autorelease];
1396 if ([replies count] == 0)
1397 [serverReplyDict removeObjectForKey:key];
1403 - (BOOL)sendReply:(NSString *)reply toPort:(int)port
1405 id client = [clientProxyDict objectForKey:[NSNumber numberWithInt:port]];
1408 ASLogDebug(@"reply=%@ port=%d", reply, port);
1409 [client addReply:reply server:self];
1412 @catch (NSException *ex) {
1413 ASLogNotice(@"addReply:server: failed: reason=%@", ex);
1416 EMSG2(_("E???: server2client failed; no client with id 0x%x"), port);
1427 - (void)setWaitForAck:(BOOL)yn
1432 - (void)waitForConnectionAcknowledgement
1434 if (!waitForAck) return;
1436 while (waitForAck && !got_int && [connection isValid]) {
1437 [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
1438 beforeDate:[NSDate distantFuture]];
1439 ASLogDebug(@" waitForAck=%d got_int=%d isValid=%d",
1440 waitForAck, got_int, [connection isValid]);
1444 ASLogDebug(@"Never received a connection acknowledgement");
1445 [[NSNotificationCenter defaultCenter] removeObserver:self];
1446 [appProxy release]; appProxy = nil;
1448 // NOTE: We intentionally do not call mch_exit() since this in turn
1449 // will lead to -[MMBackend exit] getting called which we want to
1454 ASLogInfo(@"Connection acknowledgement received");
1455 [self processInputQueue];
1458 - (oneway void)acknowledgeConnection
1469 - (void)setImState:(BOOL)activated
1471 imState = activated;
1474 static void netbeansReadCallback(CFSocketRef s,
1475 CFSocketCallBackType callbackType,
1480 // NetBeans socket is readable.
1481 [[MMBackend sharedInstance] messageFromNetbeans];
1484 - (void)messageFromNetbeans
1486 [inputQueue addObject:[NSNumber numberWithInt:NetBeansMsgID]];
1487 [inputQueue addObject:[NSNull null]];
1490 - (void)setNetbeansSocket:(int)socket
1492 if (netbeansSocket) {
1493 CFRelease(netbeansSocket);
1494 netbeansSocket = NULL;
1497 if (netbeansRunLoopSource) {
1498 CFRunLoopSourceInvalidate(netbeansRunLoopSource);
1499 netbeansRunLoopSource = NULL;
1505 // Tell CFRunLoop that we are interested in NetBeans socket input.
1506 netbeansSocket = CFSocketCreateWithNative(kCFAllocatorDefault,
1508 kCFSocketReadCallBack,
1509 &netbeansReadCallback,
1511 netbeansRunLoopSource = CFSocketCreateRunLoopSource(NULL,
1514 CFRunLoopAddSource(CFRunLoopGetCurrent(),
1515 netbeansRunLoopSource,
1516 kCFRunLoopCommonModes);
1523 @implementation MMBackend (Private)
1525 - (void)clearDrawData
1527 [drawData setLength:0];
1528 numWholeLineChanges = offsetForDrawDataPrune = 0;
1531 - (void)didChangeWholeLine
1533 // It may happen that draw queue is filled up with lots of changes that
1534 // affect a whole row. If the number of such changes equals twice the
1535 // number of visible rows then we can prune some commands off the queue.
1537 // NOTE: If we don't perform this pruning the draw queue may grow
1538 // indefinitely if Vim were to repeatedly send draw commands without ever
1539 // waiting for new input (that's when the draw queue is flushed). The one
1540 // instance I know where this can happen is when a command is executed in
1541 // the shell (think ":grep" with thousands of matches).
1543 ++numWholeLineChanges;
1544 if (numWholeLineChanges == gui.num_rows) {
1545 // Remember the offset to prune up to.
1546 offsetForDrawDataPrune = [drawData length];
1547 } else if (numWholeLineChanges == 2*gui.num_rows) {
1548 // Delete all the unnecessary draw commands.
1549 NSMutableData *d = [[NSMutableData alloc]
1550 initWithBytes:[drawData bytes] + offsetForDrawDataPrune
1551 length:[drawData length] - offsetForDrawDataPrune];
1552 offsetForDrawDataPrune = [d length];
1553 numWholeLineChanges -= gui.num_rows;
1559 - (void)waitForDialogReturn
1561 // Keep processing the run loop until a dialog returns. To avoid getting
1562 // stuck in an endless loop (could happen if the setDialogReturn: message
1563 // was lost) we also do some paranoia checks.
1565 // Note that in Cocoa the user can still resize windows and select menu
1566 // items while a sheet is being displayed, so we can't just wait for the
1567 // first message to arrive and assume that is the setDialogReturn: call.
1569 while (nil == dialogReturn && !got_int && [connection isValid])
1570 [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
1571 beforeDate:[NSDate distantFuture]];
1573 // Search for any resize messages on the input queue. All other messages
1574 // on the input queue are dropped. The reason why we single out resize
1575 // messages is because the user may have resized the window while a sheet
1577 int i, count = [inputQueue count];
1579 id textDimData = nil;
1581 for (i = count-2; i >= 0; i -= 2) {
1582 int msgid = [[inputQueue objectAtIndex:i] intValue];
1583 if (SetTextDimensionsMsgID == msgid) {
1584 textDimData = [[inputQueue objectAtIndex:i+1] retain];
1590 [inputQueue removeAllObjects];
1593 [inputQueue addObject:
1594 [NSNumber numberWithInt:SetTextDimensionsMsgID]];
1595 [inputQueue addObject:textDimData];
1596 [textDimData release];
1601 - (void)insertVimStateMessage
1603 // NOTE: This is the place to add Vim state that needs to be accessed from
1604 // MacVim. Do not add state that could potentially require lots of memory
1605 // since this message gets sent each time the output queue is forcibly
1606 // flushed (e.g. storing the currently selected text would be a bad idea).
1607 // We take this approach of "pushing" the state to MacVim to avoid having
1608 // to make synchronous calls from MacVim to Vim in order to get state.
1610 BOOL mmta = curbuf ? curbuf->b_p_mmta : NO;
1611 int numTabs = tabpage_index(NULL) - 1;
1615 NSDictionary *vimState = [NSDictionary dictionaryWithObjectsAndKeys:
1616 [[NSFileManager defaultManager] currentDirectoryPath], @"pwd",
1617 [NSNumber numberWithInt:p_mh], @"p_mh",
1618 [NSNumber numberWithBool:[self unusedEditor]], @"unusedEditor",
1619 [NSNumber numberWithBool:mmta], @"p_mmta",
1620 [NSNumber numberWithInt:numTabs], @"numTabs",
1623 // Put the state before all other messages.
1624 int msgid = SetVimStateMsgID;
1625 [outputQueue insertObject:[vimState dictionaryAsData] atIndex:0];
1626 [outputQueue insertObject:[NSData dataWithBytes:&msgid length:sizeof(int)]
1630 - (void)processInputQueue
1632 if ([inputQueue count] == 0) return;
1634 // NOTE: One of the input events may cause this method to be called
1635 // recursively, so copy the input queue to a local variable and clear the
1636 // queue before starting to process input events (otherwise we could get
1637 // stuck in an endless loop).
1638 NSArray *q = [inputQueue copy];
1639 unsigned i, count = [q count];
1641 [inputQueue removeAllObjects];
1643 for (i = 1; i < count; i+=2) {
1644 int msgid = [[q objectAtIndex:i-1] intValue];
1645 id data = [q objectAtIndex:i];
1646 if ([data isEqual:[NSNull null]])
1649 ASLogDebug(@"(%d) %s", i, MessageStrings[msgid]);
1650 [self handleInputEvent:msgid data:data];
1656 - (void)handleInputEvent:(int)msgid data:(NSData *)data
1658 if (InsertTextMsgID == msgid || KeyDownMsgID == msgid ||
1659 CmdKeyMsgID == msgid) {
1661 const void *bytes = [data bytes];
1662 int mods = *((int*)bytes); bytes += sizeof(int);
1663 int len = *((int*)bytes); bytes += sizeof(int);
1664 NSString *key = [[NSString alloc] initWithBytes:bytes
1666 encoding:NSUTF8StringEncoding];
1667 mods = eventModifierFlagsToVimModMask(mods);
1669 if (InsertTextMsgID == msgid)
1670 [self handleInsertText:key];
1672 [self handleKeyDown:key modifiers:mods];
1675 } else if (ScrollWheelMsgID == msgid) {
1677 const void *bytes = [data bytes];
1679 int row = *((int*)bytes); bytes += sizeof(int);
1680 int col = *((int*)bytes); bytes += sizeof(int);
1681 int flags = *((int*)bytes); bytes += sizeof(int);
1682 float dy = *((float*)bytes); bytes += sizeof(float);
1684 int button = MOUSE_5;
1685 if (dy > 0) button = MOUSE_4;
1687 flags = eventModifierFlagsToVimMouseModMask(flags);
1689 int numLines = (int)round(dy);
1690 if (numLines < 0) numLines = -numLines;
1691 if (numLines == 0) numLines = 1;
1693 #ifdef FEAT_GUI_SCROLL_WHEEL_FORCE
1694 gui.scroll_wheel_force = numLines;
1697 gui_send_mouse_event(button, col, row, NO, flags);
1698 } else if (MouseDownMsgID == msgid) {
1700 const void *bytes = [data bytes];
1702 int row = *((int*)bytes); bytes += sizeof(int);
1703 int col = *((int*)bytes); bytes += sizeof(int);
1704 int button = *((int*)bytes); bytes += sizeof(int);
1705 int flags = *((int*)bytes); bytes += sizeof(int);
1706 int count = *((int*)bytes); bytes += sizeof(int);
1708 button = eventButtonNumberToVimMouseButton(button);
1710 flags = eventModifierFlagsToVimMouseModMask(flags);
1711 gui_send_mouse_event(button, col, row, count>1, flags);
1713 } else if (MouseUpMsgID == msgid) {
1715 const void *bytes = [data bytes];
1717 int row = *((int*)bytes); bytes += sizeof(int);
1718 int col = *((int*)bytes); bytes += sizeof(int);
1719 int flags = *((int*)bytes); bytes += sizeof(int);
1721 flags = eventModifierFlagsToVimMouseModMask(flags);
1723 gui_send_mouse_event(MOUSE_RELEASE, col, row, NO, flags);
1724 } else if (MouseDraggedMsgID == msgid) {
1726 const void *bytes = [data bytes];
1728 int row = *((int*)bytes); bytes += sizeof(int);
1729 int col = *((int*)bytes); bytes += sizeof(int);
1730 int flags = *((int*)bytes); bytes += sizeof(int);
1732 flags = eventModifierFlagsToVimMouseModMask(flags);
1734 gui_send_mouse_event(MOUSE_DRAG, col, row, NO, flags);
1735 } else if (MouseMovedMsgID == msgid) {
1736 const void *bytes = [data bytes];
1737 int row = *((int*)bytes); bytes += sizeof(int);
1738 int col = *((int*)bytes); bytes += sizeof(int);
1740 gui_mouse_moved(col, row);
1741 } else if (AddInputMsgID == msgid) {
1742 NSString *string = [[NSString alloc] initWithData:data
1743 encoding:NSUTF8StringEncoding];
1745 [self addInput:string];
1748 } else if (SelectTabMsgID == msgid) {
1750 const void *bytes = [data bytes];
1751 int idx = *((int*)bytes) + 1;
1752 send_tabline_event(idx);
1753 } else if (CloseTabMsgID == msgid) {
1755 const void *bytes = [data bytes];
1756 int idx = *((int*)bytes) + 1;
1757 send_tabline_menu_event(idx, TABLINE_MENU_CLOSE);
1758 } else if (AddNewTabMsgID == msgid) {
1759 send_tabline_menu_event(0, TABLINE_MENU_NEW);
1760 } else if (DraggedTabMsgID == msgid) {
1762 const void *bytes = [data bytes];
1763 // NOTE! The destination index is 0 based, so do not add 1 to make it 1
1765 int idx = *((int*)bytes);
1768 } else if (SetTextDimensionsMsgID == msgid || LiveResizeMsgID == msgid
1769 || SetTextRowsMsgID == msgid || SetTextColumnsMsgID == msgid) {
1771 const void *bytes = [data bytes];
1773 if (SetTextColumnsMsgID != msgid) {
1774 rows = *((int*)bytes); bytes += sizeof(int);
1777 if (SetTextRowsMsgID != msgid) {
1778 cols = *((int*)bytes); bytes += sizeof(int);
1782 if (SetTextRowsMsgID == msgid || SetTextColumnsMsgID == msgid) {
1783 int dim[2] = { rows, cols };
1784 d = [NSData dataWithBytes:dim length:2*sizeof(int)];
1785 msgid = SetTextDimensionsReplyMsgID;
1788 if (SetTextDimensionsMsgID == msgid)
1789 msgid = SetTextDimensionsReplyMsgID;
1791 // NOTE! Vim doesn't call gui_mch_set_shellsize() after
1792 // gui_resize_shell(), so we have to manually set the rows and columns
1793 // here since MacVim doesn't change the rows and columns to avoid
1794 // inconsistent states between Vim and MacVim. The message sent back
1795 // indicates that it is a reply to a message that originated in MacVim
1796 // since we need to be able to determine where a message originated.
1797 [self queueMessage:msgid data:d];
1799 gui_resize_shell(cols, rows);
1800 } else if (ExecuteMenuMsgID == msgid) {
1801 NSDictionary *attrs = [NSDictionary dictionaryWithData:data];
1803 NSArray *desc = [attrs objectForKey:@"descriptor"];
1804 vimmenu_T *menu = menu_for_descriptor(desc);
1808 } else if (ToggleToolbarMsgID == msgid) {
1809 [self handleToggleToolbar];
1810 } else if (ScrollbarEventMsgID == msgid) {
1811 [self handleScrollbarEvent:data];
1812 } else if (SetFontMsgID == msgid) {
1813 [self handleSetFont:data];
1814 } else if (VimShouldCloseMsgID == msgid) {
1816 } else if (DropFilesMsgID == msgid) {
1817 [self handleDropFiles:data];
1818 } else if (DropStringMsgID == msgid) {
1819 [self handleDropString:data];
1820 } else if (GotFocusMsgID == msgid) {
1822 [self focusChange:YES];
1823 } else if (LostFocusMsgID == msgid) {
1825 [self focusChange:NO];
1826 } else if (SetMouseShapeMsgID == msgid) {
1827 const void *bytes = [data bytes];
1828 int shape = *((int*)bytes); bytes += sizeof(int);
1829 update_mouseshape(shape);
1830 } else if (XcodeModMsgID == msgid) {
1831 [self handleXcodeMod:data];
1832 } else if (OpenWithArgumentsMsgID == msgid) {
1833 [self handleOpenWithArguments:[NSDictionary dictionaryWithData:data]];
1834 } else if (FindReplaceMsgID == msgid) {
1835 [self handleFindReplace:[NSDictionary dictionaryWithData:data]];
1836 } else if (ActivatedImMsgID == msgid) {
1837 [self setImState:YES];
1838 } else if (DeactivatedImMsgID == msgid) {
1839 [self setImState:NO];
1840 } else if (NetBeansMsgID == msgid) {
1841 #ifdef FEAT_NETBEANS_INTG
1842 messageFromNetbeansMacVim();
1845 ASLogWarn(@"Unknown message received (msgid=%d)", msgid);
1849 + (NSDictionary *)specialKeys
1851 static NSDictionary *specialKeys = nil;
1854 NSBundle *mainBundle = [NSBundle mainBundle];
1855 NSString *path = [mainBundle pathForResource:@"SpecialKeys"
1857 specialKeys = [[NSDictionary alloc] initWithContentsOfFile:path];
1863 - (void)handleInsertText:(NSString *)text
1867 char_u *str = (char_u*)[text UTF8String];
1868 int i, len = [text lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
1871 char_u *conv_str = NULL;
1872 if (input_conv.vc_type != CONV_NONE) {
1873 conv_str = string_convert(&input_conv, str, &len);
1879 for (i = 0; i < len; ++i) {
1880 add_to_input_buf(str+i, 1);
1881 if (CSI == str[i]) {
1882 // NOTE: If the converted string contains the byte CSI, then it
1883 // must be followed by the bytes KS_EXTRA, KE_CSI or things
1885 static char_u extra[2] = { KS_EXTRA, KE_CSI };
1886 add_to_input_buf(extra, 2);
1896 - (void)handleKeyDown:(NSString *)key modifiers:(int)mods
1898 // TODO: This code is a horrible mess -- clean up!
1901 char_u *chars = (char_u*)[key UTF8String];
1903 char_u *conv_str = NULL;
1905 int length = [key lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
1907 // Special keys (arrow keys, function keys, etc.) are stored in a plist so
1908 // that new keys can easily be added.
1909 NSString *specialString = [[MMBackend specialKeys]
1911 if (specialString && [specialString length] > 1) {
1912 //ASLogDebug(@"special key: %@", specialString);
1913 int ikey = TO_SPECIAL([specialString characterAtIndex:0],
1914 [specialString characterAtIndex:1]);
1916 ikey = simplify_key(ikey, &mods);
1921 special[1] = K_SECOND(ikey);
1922 special[2] = K_THIRD(ikey);
1926 } else if (1 == length && TAB == chars[0]) {
1927 // Tab is a trouble child:
1928 // - <Tab> is added to the input buffer as is
1929 // - <S-Tab> is translated to, {CSI,'k','B'} (i.e. 'Back-tab')
1930 // - <M-Tab> should be 0x80|TAB but this is not valid utf-8 so it needs
1931 // to be converted to utf-8
1932 // - <S-M-Tab> is translated to <S-Tab> with ALT modifier
1933 // - <C-Tab> is reserved by Mac OS X
1934 // - <D-Tab> is reserved by Mac OS X
1939 if (mods & MOD_MASK_SHIFT) {
1940 mods &= ~MOD_MASK_SHIFT;
1942 special[1] = K_SECOND(K_S_TAB);
1943 special[2] = K_THIRD(K_S_TAB);
1945 } else if (mods & MOD_MASK_ALT) {
1946 int mtab = 0x80 | TAB;
1950 special[0] = (mtab >> 6) + 0xc0;
1951 special[1] = mtab & 0xbf;
1959 mods &= ~MOD_MASK_ALT;
1961 } else if (1 == length && chars[0] < 0x80 && (mods & MOD_MASK_ALT)) {
1962 // META key is treated separately. This code was taken from gui_w48.c
1963 // and gui_gtk_x11.c.
1965 int ch = simplify_key(chars[0], &mods);
1967 // Remove the SHIFT modifier for keys where it's already included,
1968 // e.g., '(' and '*'
1969 if (ch < 0x100 && !isalpha(ch) && isprint(ch))
1970 mods &= ~MOD_MASK_SHIFT;
1972 // Interpret the ALT key as making the key META, include SHIFT, etc.
1973 ch = extract_modifiers(ch, &mods);
1979 string[len++] = CSI;
1980 string[len++] = KS_MODIFIER;
1981 string[len++] = mods;
1984 if (IS_SPECIAL(ch)) {
1985 string[len++] = CSI;
1986 string[len++] = K_SECOND(ch);
1987 string[len++] = K_THIRD(ch);
1991 // TODO: What if 'enc' is not "utf-8"?
1992 if (enc_utf8 && (ch & 0x80)) { // convert to utf-8
1993 string[len++] = ch & 0xbf;
1994 string[len-2] = ((unsigned)ch >> 6) + 0xc0;
1995 if (string[len-1] == CSI) {
1996 string[len++] = KS_EXTRA;
1997 string[len++] = (int)KE_CSI;
2003 add_to_input_buf(string, len);
2005 } else if (length > 0) {
2006 unichar c = [key characterAtIndex:0];
2007 //ASLogDebug(@"non-special: %@ (hex=%x, mods=%d)", key,
2008 // [key characterAtIndex:0], mods);
2010 // HACK! In most circumstances the Ctrl and Shift modifiers should be
2011 // cleared since they are already added to the key by the AppKit.
2012 // Unfortunately, the only way to deal with when to clear the modifiers
2013 // or not seems to be to have hard-wired rules like this.
2014 if ( !((' ' == c) || (0xa0 == c) || (mods & MOD_MASK_CMD)
2015 || 0x9 == c || 0xd == c || ESC == c) ) {
2016 mods &= ~MOD_MASK_SHIFT;
2017 mods &= ~MOD_MASK_CTRL;
2018 //ASLogDebug(@"clear shift ctrl");
2022 if (input_conv.vc_type != CONV_NONE) {
2023 conv_str = string_convert(&input_conv, chars, &length);
2030 if (chars && length > 0) {
2032 //ASLogDebug(@"adding mods: %d", mods);
2034 modChars[1] = KS_MODIFIER;
2036 add_to_input_buf(modChars, 3);
2039 //ASLogDebug(@"add to input buf: 0x%x", chars[0]);
2040 // TODO: Check for CSI bytes?
2041 add_to_input_buf(chars, length);
2050 - (void)queueMessage:(int)msgid data:(NSData *)data
2052 [outputQueue addObject:[NSData dataWithBytes:&msgid length:sizeof(int)]];
2054 [outputQueue addObject:data];
2056 [outputQueue addObject:[NSData data]];
2059 - (void)connectionDidDie:(NSNotification *)notification
2061 // If the main connection to MacVim is lost this means that either MacVim
2062 // has crashed or this process did not receive its termination message
2063 // properly (e.g. if the TerminateNowMsgID was dropped).
2065 // NOTE: This is not called if a Vim controller invalidates its connection.
2067 ASLogNotice(@"Main connection was lost before process had a chance "
2068 "to terminate; preserving swap files.");
2069 getout_preserve_modified(1);
2072 - (void)blinkTimerFired:(NSTimer *)timer
2074 NSTimeInterval timeInterval = 0;
2076 [blinkTimer release];
2079 if (MMBlinkStateOn == blinkState) {
2080 gui_undraw_cursor();
2081 blinkState = MMBlinkStateOff;
2082 timeInterval = blinkOffInterval;
2083 } else if (MMBlinkStateOff == blinkState) {
2084 gui_update_cursor(TRUE, FALSE);
2085 blinkState = MMBlinkStateOn;
2086 timeInterval = blinkOnInterval;
2089 if (timeInterval > 0) {
2091 [[NSTimer scheduledTimerWithTimeInterval:timeInterval target:self
2092 selector:@selector(blinkTimerFired:)
2093 userInfo:nil repeats:NO] retain];
2094 [self flushQueue:YES];
2098 - (void)focusChange:(BOOL)on
2100 gui_focus_change(on);
2103 - (void)handleToggleToolbar
2105 // If 'go' contains 'T', then remove it, else add it.
2107 char_u go[sizeof(GO_ALL)+2];
2112 p = vim_strchr(go, GO_TOOLBAR);
2116 char_u *end = go + len;
2122 go[len] = GO_TOOLBAR;
2126 set_option_value((char_u*)"guioptions", 0, go, 0);
2128 [self redrawScreen];
2131 - (void)handleScrollbarEvent:(NSData *)data
2135 const void *bytes = [data bytes];
2136 long ident = *((long*)bytes); bytes += sizeof(long);
2137 int hitPart = *((int*)bytes); bytes += sizeof(int);
2138 float fval = *((float*)bytes); bytes += sizeof(float);
2139 scrollbar_T *sb = gui_find_scrollbar(ident);
2142 scrollbar_T *sb_info = sb->wp ? &sb->wp->w_scrollbars[0] : sb;
2143 long value = sb_info->value;
2144 long size = sb_info->size;
2145 long max = sb_info->max;
2146 BOOL isStillDragging = NO;
2147 BOOL updateKnob = YES;
2150 case NSScrollerDecrementPage:
2151 value -= (size > 2 ? size - 2 : 1);
2153 case NSScrollerIncrementPage:
2154 value += (size > 2 ? size - 2 : 1);
2156 case NSScrollerDecrementLine:
2159 case NSScrollerIncrementLine:
2162 case NSScrollerKnob:
2163 isStillDragging = YES;
2165 case NSScrollerKnobSlot:
2166 value = (long)(fval * (max - size + 1));
2173 gui_drag_scrollbar(sb, value, isStillDragging);
2176 // Dragging the knob or option+clicking automatically updates
2177 // the knob position (on the actual NSScroller), so we only
2178 // need to set the knob position in the other cases.
2180 // Update both the left&right vertical scrollbars.
2181 long identLeft = sb->wp->w_scrollbars[SBAR_LEFT].ident;
2182 long identRight = sb->wp->w_scrollbars[SBAR_RIGHT].ident;
2183 [self setScrollbarThumbValue:value size:size max:max
2184 identifier:identLeft];
2185 [self setScrollbarThumbValue:value size:size max:max
2186 identifier:identRight];
2188 // Update the horizontal scrollbar.
2189 [self setScrollbarThumbValue:value size:size max:max
2196 - (void)handleSetFont:(NSData *)data
2200 const void *bytes = [data bytes];
2201 int pointSize = (int)*((float*)bytes); bytes += sizeof(float);
2203 unsigned len = *((unsigned*)bytes); bytes += sizeof(unsigned);
2204 NSMutableString *name = [NSMutableString stringWithUTF8String:bytes];
2207 [name appendString:[NSString stringWithFormat:@":h%d", pointSize]];
2208 char_u *s = (char_u*)[name UTF8String];
2210 unsigned wlen = *((unsigned*)bytes); bytes += sizeof(unsigned);
2213 NSMutableString *wname = [NSMutableString stringWithUTF8String:bytes];
2216 [wname appendString:[NSString stringWithFormat:@":h%d", pointSize]];
2217 ws = (char_u*)[wname UTF8String];
2221 s = CONVERT_FROM_UTF8(s);
2223 ws = CONVERT_FROM_UTF8(ws);
2227 set_option_value((char_u*)"guifont", 0, s, 0);
2229 if (ws && gui.wide_font != NOFONT) {
2230 // NOTE: This message is sent on Cmd-+/Cmd-- and as such should only
2231 // change the wide font if 'gfw' is non-empty (the frontend always has
2232 // some wide font set, even if 'gfw' is empty).
2233 set_option_value((char_u*)"guifontwide", 0, ws, 0);
2238 CONVERT_FROM_UTF8_FREE(ws);
2240 CONVERT_FROM_UTF8_FREE(s);
2243 [self redrawScreen];
2246 - (void)handleDropFiles:(NSData *)data
2248 // TODO: Get rid of this method; instead use Vim script directly. At the
2249 // moment I know how to do this to open files in tabs, but I'm not sure how
2250 // to add the filenames to the command line when in command line mode.
2254 NSMutableDictionary *args = [NSMutableDictionary dictionaryWithData:data];
2257 id obj = [args objectForKey:@"forceOpen"];
2258 BOOL forceOpen = YES;
2260 forceOpen = [obj boolValue];
2262 NSArray *filenames = [args objectForKey:@"filenames"];
2263 if (!(filenames && [filenames count] > 0)) return;
2266 if (!forceOpen && (State & CMDLINE)) {
2267 // HACK! If Vim is in command line mode then the files names
2268 // should be added to the command line, instead of opening the
2269 // files in tabs (unless forceOpen is set). This is taken care of by
2270 // gui_handle_drop().
2271 int n = [filenames count];
2272 char_u **fnames = (char_u **)alloc(n * sizeof(char_u *));
2275 for (i = 0; i < n; ++i)
2276 fnames[i] = [[filenames objectAtIndex:i] vimStringSave];
2278 // NOTE! This function will free 'fnames'.
2279 // HACK! It is assumed that the 'x' and 'y' arguments are
2280 // unused when in command line mode.
2281 gui_handle_drop(0, 0, 0, fnames, n);
2286 [self handleOpenWithArguments:args];
2290 - (void)handleDropString:(NSData *)data
2295 char_u dropkey[3] = { CSI, KS_EXTRA, (char_u)KE_DROP };
2296 const void *bytes = [data bytes];
2297 int len = *((int*)bytes); bytes += sizeof(int);
2298 NSMutableString *string = [NSMutableString stringWithUTF8String:bytes];
2300 // Replace unrecognized end-of-line sequences with \x0a (line feed).
2301 NSRange range = { 0, [string length] };
2302 unsigned n = [string replaceOccurrencesOfString:@"\x0d\x0a"
2303 withString:@"\x0a" options:0
2306 n = [string replaceOccurrencesOfString:@"\x0d" withString:@"\x0a"
2307 options:0 range:range];
2310 len = [string lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
2311 char_u *s = (char_u*)[string UTF8String];
2313 if (input_conv.vc_type != CONV_NONE)
2314 s = string_convert(&input_conv, s, &len);
2316 dnd_yank_drag_data(s, len);
2318 if (input_conv.vc_type != CONV_NONE)
2321 add_to_input_buf(dropkey, sizeof(dropkey));
2325 - (void)startOdbEditWithArguments:(NSDictionary *)args
2327 #ifdef FEAT_ODB_EDITOR
2328 id obj = [args objectForKey:@"remoteID"];
2331 OSType serverID = [obj unsignedIntValue];
2332 NSString *remotePath = [args objectForKey:@"remotePath"];
2334 NSAppleEventDescriptor *token = nil;
2335 NSData *tokenData = [args objectForKey:@"remoteTokenData"];
2336 obj = [args objectForKey:@"remoteTokenDescType"];
2337 if (tokenData && obj) {
2338 DescType tokenType = [obj unsignedLongValue];
2339 token = [NSAppleEventDescriptor descriptorWithDescriptorType:tokenType
2343 NSArray *filenames = [args objectForKey:@"filenames"];
2344 unsigned i, numFiles = [filenames count];
2345 for (i = 0; i < numFiles; ++i) {
2346 NSString *filename = [filenames objectAtIndex:i];
2347 char_u *s = [filename vimStringSave];
2348 buf_T *buf = buflist_findname(s);
2352 if (buf->b_odb_token) {
2353 [(NSAppleEventDescriptor*)(buf->b_odb_token) release];
2354 buf->b_odb_token = NULL;
2357 if (buf->b_odb_fname) {
2358 vim_free(buf->b_odb_fname);
2359 buf->b_odb_fname = NULL;
2362 buf->b_odb_server_id = serverID;
2365 buf->b_odb_token = [token retain];
2367 buf->b_odb_fname = [remotePath vimStringSave];
2369 ASLogWarn(@"Could not find buffer '%@' for ODB editing.", filename);
2372 #endif // FEAT_ODB_EDITOR
2375 - (void)handleXcodeMod:(NSData *)data
2378 const void *bytes = [data bytes];
2379 DescType type = *((DescType*)bytes); bytes += sizeof(DescType);
2380 unsigned len = *((unsigned*)bytes); bytes += sizeof(unsigned);
2384 NSAppleEventDescriptor *replyEvent = [NSAppleEventDescriptor
2385 descriptorWithDescriptorType:type
2391 - (void)handleOpenWithArguments:(NSDictionary *)args
2393 // ARGUMENT: DESCRIPTION:
2394 // -------------------------------------------------------------
2395 // filenames list of filenames
2396 // dontOpen don't open files specified in above argument
2397 // layout which layout to use to open files
2398 // selectionRange range of lines to select
2399 // searchText string to search for
2400 // cursorLine line to position the cursor on
2401 // cursorColumn column to position the cursor on
2402 // (only valid when "cursorLine" is set)
2403 // remoteID ODB parameter
2404 // remotePath ODB parameter
2405 // remoteTokenDescType ODB parameter
2406 // remoteTokenData ODB parameter
2408 ASLogDebug(@"args=%@ (starting=%d)", args, starting);
2410 NSArray *filenames = [args objectForKey:@"filenames"];
2411 int i, numFiles = filenames ? [filenames count] : 0;
2412 BOOL openFiles = ![[args objectForKey:@"dontOpen"] boolValue];
2413 int layout = [[args objectForKey:@"layout"] intValue];
2415 // Change to directory of first file to open if this is an "unused" editor
2416 // (but do not do this if editing remotely).
2417 if (openFiles && numFiles > 0 && ![args objectForKey:@"remoteID"]
2418 && (starting || [self unusedEditor]) ) {
2419 char_u *s = [[filenames objectAtIndex:0] vimStringSave];
2421 mch_chdir((char*)s);
2429 // When Vim is starting we simply add the files to be opened to the
2430 // global arglist and Vim will take care of opening them for us.
2431 if (openFiles && numFiles > 0) {
2432 for (i = 0; i < numFiles; i++) {
2433 NSString *fname = [filenames objectAtIndex:i];
2436 if (ga_grow(&global_alist.al_ga, 1) == FAIL
2437 || (p = [fname vimStringSave]) == NULL)
2438 exit(2); // See comment in -[MMBackend exit]
2440 alist_add(&global_alist, p, 2);
2443 // Vim will take care of arranging the files added to the arglist
2444 // in windows or tabs; all we must do is to specify which layout to
2446 initialWindowLayout = layout;
2449 // When Vim is already open we resort to some trickery to open the
2450 // files with the specified layout.
2452 // TODO: Figure out a better way to handle this?
2453 if (openFiles && numFiles > 0) {
2454 BOOL oneWindowInTab = topframe ? YES
2455 : (topframe->fr_layout == FR_LEAF);
2456 BOOL bufChanged = NO;
2457 BOOL bufHasFilename = NO;
2459 bufChanged = curbufIsChanged();
2460 bufHasFilename = curbuf->b_ffname != NULL;
2463 // Temporarily disable flushing since the following code may
2464 // potentially cause multiple redraws.
2465 flushDisabled = YES;
2467 BOOL onlyOneTab = (first_tabpage->tp_next == NULL);
2468 if (WIN_TABS == layout && !onlyOneTab) {
2469 // By going to the last tabpage we ensure that the new tabs
2470 // will appear last (if this call is left out, the taborder
2475 // Make sure we're in normal mode first.
2476 [self addInput:@"<C-\\><C-N>"];
2479 // With "split layout" we open a new tab before opening
2480 // multiple files if the current tab has more than one window
2481 // or if there is exactly one window but whose buffer has a
2482 // filename. (The :drop command ensures modified buffers get
2483 // their own window.)
2484 if ((WIN_HOR == layout || WIN_VER == layout) &&
2485 (!oneWindowInTab || bufHasFilename))
2486 [self addInput:@":tabnew<CR>"];
2488 // The files are opened by constructing a ":drop ..." command
2489 // and executing it.
2490 NSMutableString *cmd = (WIN_TABS == layout)
2491 ? [NSMutableString stringWithString:@":tab drop"]
2492 : [NSMutableString stringWithString:@":drop"];
2494 for (i = 0; i < numFiles; ++i) {
2495 NSString *file = [filenames objectAtIndex:i];
2496 file = [file stringByEscapingSpecialFilenameCharacters];
2497 [cmd appendString:@" "];
2498 [cmd appendString:file];
2501 // Temporarily clear 'suffixes' so that the files are opened in
2502 // the same order as they appear in the "filenames" array.
2503 [self addInput:@":let mvim_oldsu=&su|set su=<CR>"];
2505 [self addInput:cmd];
2507 // Split the view into multiple windows if requested.
2508 if (WIN_HOR == layout)
2509 [self addInput:@"|sall"];
2510 else if (WIN_VER == layout)
2511 [self addInput:@"|vert sall"];
2513 // Restore the old value of 'suffixes'.
2514 [self addInput:@"|let &su=mvim_oldsu|unlet mvim_oldsu<CR>"];
2516 // When opening one file we try to reuse the current window,
2517 // but not if its buffer is modified or has a filename.
2518 // However, the 'arglist' layout always opens the file in the
2520 NSString *file = [[filenames lastObject]
2521 stringByEscapingSpecialFilenameCharacters];
2523 if (WIN_HOR == layout) {
2524 if (!(bufHasFilename || bufChanged))
2525 cmd = [NSString stringWithFormat:@":e %@", file];
2527 cmd = [NSString stringWithFormat:@":sp %@", file];
2528 } else if (WIN_VER == layout) {
2529 if (!(bufHasFilename || bufChanged))
2530 cmd = [NSString stringWithFormat:@":e %@", file];
2532 cmd = [NSString stringWithFormat:@":vsp %@", file];
2533 } else if (WIN_TABS == layout) {
2534 if (oneWindowInTab && !(bufHasFilename || bufChanged))
2535 cmd = [NSString stringWithFormat:@":e %@", file];
2537 cmd = [NSString stringWithFormat:@":tabe %@", file];
2539 // (The :drop command will split if there is a modified
2541 cmd = [NSString stringWithFormat:@":drop %@", file];
2544 [self addInput:cmd];
2545 [self addInput:@"<CR>"];
2548 // Force screen redraw (does it have to be this complicated?).
2549 // (This code was taken from the end of gui_handle_drop().)
2550 update_screen(NOT_VALID);
2553 gui_update_cursor(FALSE, FALSE);
2560 if ([args objectForKey:@"remoteID"]) {
2561 // NOTE: We have to delay processing any ODB related arguments since
2562 // the file(s) may not be opened until the input buffer is processed.
2563 [self performSelector:@selector(startOdbEditWithArguments:)
2568 NSString *lineString = [args objectForKey:@"cursorLine"];
2569 if (lineString && [lineString intValue] > 0) {
2570 NSString *columnString = [args objectForKey:@"cursorColumn"];
2571 if (!(columnString && [columnString intValue] > 0))
2572 columnString = @"1";
2574 NSString *cmd = [NSString stringWithFormat:@"<C-\\><C-N>:cal "
2575 "cursor(%@,%@)|norm! zz<CR>:f<CR>", lineString, columnString];
2576 [self addInput:cmd];
2579 NSString *rangeString = [args objectForKey:@"selectionRange"];
2581 // Build a command line string that will select the given range of
2582 // lines. If range.length == 0, then position the cursor on the given
2583 // line but do not select.
2584 NSRange range = NSRangeFromString(rangeString);
2586 if (range.length > 0) {
2587 cmd = [NSString stringWithFormat:@"<C-\\><C-N>%dGV%dGz.0",
2588 NSMaxRange(range), range.location];
2590 cmd = [NSString stringWithFormat:@"<C-\\><C-N>%dGz.0",
2594 [self addInput:cmd];
2597 NSString *searchText = [args objectForKey:@"searchText"];
2599 [self addInput:[NSString stringWithFormat:@"<C-\\><C-N>gg/\\c%@/e<CR>",
2604 - (BOOL)checkForModifiedBuffers
2607 for (buf = firstbuf; buf != NULL; buf = buf->b_next) {
2608 if (bufIsChanged(buf)) {
2616 - (void)addInput:(NSString *)input
2618 // NOTE: This code is essentially identical to server_to_input_buf(),
2619 // except the 'silent' flag is TRUE in the call to ins_typebuf() below.
2620 char_u *string = [input vimStringSave];
2621 if (!string) return;
2623 /* Set 'cpoptions' the way we want it.
2624 * B set - backslashes are *not* treated specially
2625 * k set - keycodes are *not* reverse-engineered
2626 * < unset - <Key> sequences *are* interpreted
2627 * The last but one parameter of replace_termcodes() is TRUE so that the
2628 * <lt> sequence is recognised - needed for a real backslash.
2631 char_u *cpo_save = p_cpo;
2632 p_cpo = (char_u *)"Bk";
2633 char_u *str = replace_termcodes((char_u *)string, &ptr, FALSE, TRUE, FALSE);
2636 if (*ptr != NUL) /* trailing CTRL-V results in nothing */
2639 * Add the string to the input stream.
2640 * Can't use add_to_input_buf() here, we now have K_SPECIAL bytes.
2642 * First clear typed characters from the typeahead buffer, there could
2643 * be half a mapping there. Then append to the existing string, so
2644 * that multiple commands from a client are concatenated.
2646 if (typebuf.tb_maplen < typebuf.tb_len)
2647 del_typebuf(typebuf.tb_len - typebuf.tb_maplen, typebuf.tb_maplen);
2648 (void)ins_typebuf(str, REMAP_NONE, typebuf.tb_len, TRUE, TRUE);
2650 /* Let input_available() know we inserted text in the typeahead
2652 typebuf_was_filled = TRUE;
2658 - (BOOL)unusedEditor
2660 BOOL oneWindowInTab = topframe ? YES
2661 : (topframe->fr_layout == FR_LEAF);
2662 BOOL bufChanged = NO;
2663 BOOL bufHasFilename = NO;
2665 bufChanged = curbufIsChanged();
2666 bufHasFilename = curbuf->b_ffname != NULL;
2669 BOOL onlyOneTab = (first_tabpage->tp_next == NULL);
2671 return onlyOneTab && oneWindowInTab && !bufChanged && !bufHasFilename;
2674 - (void)redrawScreen
2676 // Force screen redraw (does it have to be this complicated?).
2677 redraw_all_later(CLEAR);
2678 update_screen(NOT_VALID);
2681 gui_update_cursor(FALSE, FALSE);
2683 // HACK! The cursor is not put back at the command line by the above
2684 // "redraw commands". The following test seems to do the trick though.
2685 if (State & CMDLINE)
2689 - (void)handleFindReplace:(NSDictionary *)args
2693 NSString *findString = [args objectForKey:@"find"];
2694 if (!findString) return;
2696 char_u *find = [findString vimStringSave];
2697 char_u *replace = [[args objectForKey:@"replace"] vimStringSave];
2698 int flags = [[args objectForKey:@"flags"] intValue];
2700 // NOTE: The flag 0x100 is used to indicate a backward search.
2701 gui_do_findrepl(flags, find, replace, (flags & 0x100) == 0);
2707 @end // MMBackend (Private)
2712 @implementation MMBackend (ClientServer)
2714 - (NSString *)connectionNameFromServerName:(NSString *)name
2716 NSString *bundlePath = [[NSBundle mainBundle] bundlePath];
2718 return [[NSString stringWithFormat:@"%@.%@", bundlePath, name]
2722 - (NSConnection *)connectionForServerName:(NSString *)name
2724 // TODO: Try 'name%d' if 'name' fails.
2725 NSString *connName = [self connectionNameFromServerName:name];
2726 NSConnection *svrConn = [connectionNameDict objectForKey:connName];
2729 svrConn = [NSConnection connectionWithRegisteredName:connName
2731 // Try alternate server...
2732 if (!svrConn && alternateServerName) {
2733 ASLogInfo(@" trying to connect to alternate server: %@",
2734 alternateServerName);
2735 connName = [self connectionNameFromServerName:alternateServerName];
2736 svrConn = [NSConnection connectionWithRegisteredName:connName
2740 // Try looking for alternate servers...
2742 ASLogInfo(@" looking for alternate servers...");
2743 NSString *alt = [self alternateServerNameForName:name];
2744 if (alt != alternateServerName) {
2745 ASLogInfo(@" found alternate server: %@", alt);
2746 [alternateServerName release];
2747 alternateServerName = [alt copy];
2751 // Try alternate server again...
2752 if (!svrConn && alternateServerName) {
2753 ASLogInfo(@" trying to connect to alternate server: %@",
2754 alternateServerName);
2755 connName = [self connectionNameFromServerName:alternateServerName];
2756 svrConn = [NSConnection connectionWithRegisteredName:connName
2761 [connectionNameDict setObject:svrConn forKey:connName];
2763 ASLogDebug(@"Adding %@ as connection observer for %@",
2765 [[NSNotificationCenter defaultCenter] addObserver:self
2766 selector:@selector(serverConnectionDidDie:)
2767 name:NSConnectionDidDieNotification object:svrConn];
2774 - (NSConnection *)connectionForServerPort:(int)port
2777 NSEnumerator *e = [connectionNameDict objectEnumerator];
2779 while ((conn = [e nextObject])) {
2780 // HACK! Assume connection uses mach ports.
2781 if (port == [(NSMachPort*)[conn sendPort] machPort])
2788 - (void)serverConnectionDidDie:(NSNotification *)notification
2790 ASLogDebug(@"notification=%@", notification);
2792 NSConnection *svrConn = [notification object];
2794 ASLogDebug(@"Removing %@ as connection observer from %@", self, svrConn);
2795 [[NSNotificationCenter defaultCenter]
2797 name:NSConnectionDidDieNotification
2800 [connectionNameDict removeObjectsForKeys:
2801 [connectionNameDict allKeysForObject:svrConn]];
2803 // HACK! Assume connection uses mach ports.
2804 int port = [(NSMachPort*)[svrConn sendPort] machPort];
2805 NSNumber *key = [NSNumber numberWithInt:port];
2807 [clientProxyDict removeObjectForKey:key];
2808 [serverReplyDict removeObjectForKey:key];
2811 - (void)addClient:(NSDistantObject *)client
2813 NSConnection *conn = [client connectionForProxy];
2814 // HACK! Assume connection uses mach ports.
2815 int port = [(NSMachPort*)[conn sendPort] machPort];
2816 NSNumber *key = [NSNumber numberWithInt:port];
2818 if (![clientProxyDict objectForKey:key]) {
2819 [client setProtocolForProxy:@protocol(MMVimClientProtocol)];
2820 [clientProxyDict setObject:client forKey:key];
2823 // NOTE: 'clientWindow' is a global variable which is used by <client>
2824 clientWindow = port;
2827 - (NSString *)alternateServerNameForName:(NSString *)name
2829 if (!(name && [name length] > 0))
2832 // Only look for alternates if 'name' doesn't end in a digit.
2833 unichar lastChar = [name characterAtIndex:[name length]-1];
2834 if (lastChar >= '0' && lastChar <= '9')
2837 // Look for alternates among all current servers.
2838 NSArray *list = [self serverList];
2839 if (!(list && [list count] > 0))
2842 // Filter out servers starting with 'name' and ending with a number. The
2843 // (?i) pattern ensures that the match is case insensitive.
2844 NSString *pat = [NSString stringWithFormat:@"(?i)%@[0-9]+\\z", name];
2845 NSPredicate *pred = [NSPredicate predicateWithFormat:
2846 @"SELF MATCHES %@", pat];
2847 list = [list filteredArrayUsingPredicate:pred];
2848 if ([list count] > 0) {
2849 list = [list sortedArrayUsingSelector:@selector(serverNameCompare:)];
2850 return [list objectAtIndex:0];
2856 @end // MMBackend (ClientServer)
2861 @implementation NSString (MMServerNameCompare)
2862 - (NSComparisonResult)serverNameCompare:(NSString *)string
2864 return [self compare:string
2865 options:NSCaseInsensitiveSearch|NSNumericSearch];
2872 static int eventModifierFlagsToVimModMask(int modifierFlags)
2876 if (modifierFlags & NSShiftKeyMask)
2877 modMask |= MOD_MASK_SHIFT;
2878 if (modifierFlags & NSControlKeyMask)
2879 modMask |= MOD_MASK_CTRL;
2880 if (modifierFlags & NSAlternateKeyMask)
2881 modMask |= MOD_MASK_ALT;
2882 if (modifierFlags & NSCommandKeyMask)
2883 modMask |= MOD_MASK_CMD;
2888 static int eventModifierFlagsToVimMouseModMask(int modifierFlags)
2892 if (modifierFlags & NSShiftKeyMask)
2893 modMask |= MOUSE_SHIFT;
2894 if (modifierFlags & NSControlKeyMask)
2895 modMask |= MOUSE_CTRL;
2896 if (modifierFlags & NSAlternateKeyMask)
2897 modMask |= MOUSE_ALT;
2902 static int eventButtonNumberToVimMouseButton(int buttonNumber)
2904 static int mouseButton[] = { MOUSE_LEFT, MOUSE_RIGHT, MOUSE_MIDDLE };
2906 return (buttonNumber >= 0 && buttonNumber < 3)
2907 ? mouseButton[buttonNumber] : -1;
2912 // This function is modeled after the VimToPython function found in if_python.c
2913 // NB This does a deep copy by value, it does not lookup references like the
2914 // VimToPython function does. This is because I didn't want to deal with the
2915 // retain cycles that this would create, and we can cover 99% of the use cases
2916 // by ignoring it. If we ever switch to using GC in MacVim then this
2917 // functionality can be implemented easily.
2918 static id vimToCocoa(typval_T * tv, int depth)
2924 // Avoid infinite recursion
2929 if (tv->v_type == VAR_STRING) {
2930 char_u * val = tv->vval.v_string;
2931 // val can be NULL if the string is empty
2933 result = [NSString string];
2936 val = CONVERT_TO_UTF8(val);
2938 result = [NSString stringWithUTF8String:(char*)val];
2940 CONVERT_TO_UTF8_FREE(val);
2943 } else if (tv->v_type == VAR_NUMBER) {
2944 // looks like sizeof(varnumber_T) is always <= sizeof(long)
2945 result = [NSNumber numberWithLong:(long)tv->vval.v_number];
2946 } else if (tv->v_type == VAR_LIST) {
2947 list_T * list = tv->vval.v_list;
2950 NSMutableArray * arr = result = [NSMutableArray array];
2953 for (curr = list->lv_first; curr != NULL; curr = curr->li_next) {
2954 newObj = vimToCocoa(&curr->li_tv, depth + 1);
2955 [arr addObject:newObj];
2958 } else if (tv->v_type == VAR_DICT) {
2959 NSMutableDictionary * dict = result = [NSMutableDictionary dictionary];
2961 if (tv->vval.v_dict != NULL) {
2962 hashtab_T * ht = &tv->vval.v_dict->dv_hashtab;
2963 int todo = ht->ht_used;
2967 for (hi = ht->ht_array; todo > 0; ++hi) {
2968 if (!HASHITEM_EMPTY(hi)) {
2971 di = dict_lookup(hi);
2972 newObj = vimToCocoa(&di->di_tv, depth + 1);
2974 char_u * keyval = hi->hi_key;
2976 keyval = CONVERT_TO_UTF8(keyval);
2978 NSString * key = [NSString stringWithUTF8String:(char*)keyval];
2980 CONVERT_TO_UTF8_FREE(keyval);
2982 [dict setObject:newObj forKey:key];
2986 } else { // only func refs should fall into this category?
2994 // This function is modeled after eval_client_expr_to_string found in main.c
2995 // Returns nil if there was an error evaluating the expression, and writes a
2996 // message to errorStr.
2997 // TODO Get the error that occurred while evaluating the expression in vim
2999 static id evalExprCocoa(NSString * expr, NSString ** errstr)
3002 char_u *s = (char_u*)[expr UTF8String];
3005 s = CONVERT_FROM_UTF8(s);
3008 int save_dbl = debug_break_level;
3009 int save_ro = redir_off;
3011 debug_break_level = -1;
3015 typval_T * tvres = eval_expr(s, NULL);
3017 debug_break_level = save_dbl;
3018 redir_off = save_ro;
3025 CONVERT_FROM_UTF8_FREE(s);
3030 gui_update_cursor(FALSE, FALSE);
3033 if (tvres == NULL) {
3035 *errstr = @"Expression evaluation failed.";
3038 id res = vimToCocoa(tvres, 1);
3043 *errstr = @"Conversion to cocoa values failed.";
3051 @implementation NSString (VimStrings)
3053 + (id)stringWithVimString:(char_u *)s
3055 // This method ensures a non-nil string is returned. If 's' cannot be
3056 // converted to a utf-8 string it is assumed to be latin-1. If conversion
3057 // still fails an empty NSString is returned.
3058 NSString *string = nil;
3061 s = CONVERT_TO_UTF8(s);
3063 string = [NSString stringWithUTF8String:(char*)s];
3065 // HACK! Apparently 's' is not a valid utf-8 string, maybe it is
3067 string = [NSString stringWithCString:(char*)s
3068 encoding:NSISOLatin1StringEncoding];
3071 CONVERT_TO_UTF8_FREE(s);
3075 return string != nil ? string : [NSString string];
3078 - (char_u *)vimStringSave
3080 char_u *s = (char_u*)[self UTF8String], *ret = NULL;
3083 s = CONVERT_FROM_UTF8(s);
3085 ret = vim_strsave(s);
3087 CONVERT_FROM_UTF8_FREE(s);
3093 @end // NSString (VimStrings)