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 NSLog(@"ERROR: Failed to load dictionaries.%@",
162 MMSymlinkWarningString);
169 //NSLog(@"%@ %s", [self className], _cmd);
170 [[NSNotificationCenter defaultCenter] removeObserver:self];
172 gui_mch_free_font(oldWideFont); oldWideFont = NOFONT;
173 [blinkTimer release]; blinkTimer = nil;
174 [alternateServerName release]; alternateServerName = nil;
175 [serverReplyDict release]; serverReplyDict = nil;
176 [clientProxyDict release]; clientProxyDict = nil;
177 [connectionNameDict release]; connectionNameDict = nil;
178 [inputQueue release]; inputQueue = nil;
179 [outputQueue release]; outputQueue = nil;
180 [drawData release]; drawData = nil;
181 [connection release]; connection = nil;
182 [actionDict release]; actionDict = nil;
183 [sysColorDict release]; sysColorDict = nil;
184 [colorDict release]; colorDict = nil;
189 - (void)setBackgroundColor:(int)color
191 backgroundColor = MM_COLOR_WITH_TRANSP(color,p_transp);
194 - (void)setForegroundColor:(int)color
196 foregroundColor = MM_COLOR(color);
199 - (void)setSpecialColor:(int)color
201 specialColor = MM_COLOR(color);
204 - (void)setDefaultColorsBackground:(int)bg foreground:(int)fg
206 defaultBackgroundColor = MM_COLOR_WITH_TRANSP(bg,p_transp);
207 defaultForegroundColor = MM_COLOR(fg);
209 NSMutableData *data = [NSMutableData data];
211 [data appendBytes:&defaultBackgroundColor length:sizeof(unsigned)];
212 [data appendBytes:&defaultForegroundColor length:sizeof(unsigned)];
214 [self queueMessage:SetDefaultColorsMsgID data:data];
217 - (NSConnection *)connection
220 // NOTE! If the name of the connection changes here it must also be
221 // updated in MMAppController.m.
222 NSString *name = [NSString stringWithFormat:@"%@-connection",
223 [[NSBundle mainBundle] bundlePath]];
225 connection = [NSConnection connectionWithRegisteredName:name host:nil];
229 // NOTE: 'connection' may be nil here.
233 - (NSDictionary *)actionDict
238 - (int)initialWindowLayout
240 return initialWindowLayout;
243 - (void)queueMessage:(int)msgid properties:(NSDictionary *)props
245 [self queueMessage:msgid data:[props dictionaryAsData]];
250 if (![self connection]) {
252 // This is a preloaded process and as such should not cause the
253 // MacVim to be opened. We probably got here as a result of the
254 // user quitting MacVim while the process was preloading, so exit
256 // (Don't use mch_exit() since it assumes the process has properly
261 NSBundle *mainBundle = [NSBundle mainBundle];
266 // Launch MacVim using Launch Services (NSWorkspace would be nicer, but
267 // the API to pass Apple Event parameters is broken on 10.4).
268 NSString *path = [mainBundle bundlePath];
269 status = FSPathMakeRef((const UInt8 *)[path UTF8String], &ref, NULL);
270 if (noErr == status) {
271 // Pass parameter to the 'Open' Apple Event that tells MacVim not
272 // to open an untitled window.
273 NSAppleEventDescriptor *desc =
274 [NSAppleEventDescriptor recordDescriptor];
275 [desc setParamDescriptor:
276 [NSAppleEventDescriptor descriptorWithBoolean:NO]
277 forKeyword:keyMMUntitledWindow];
279 LSLaunchFSRefSpec spec = { &ref, 0, NULL, [desc aeDesc],
280 kLSLaunchDefaults, NULL };
281 status = LSOpenFromRefSpec(&spec, NULL);
284 if (noErr != status) {
285 NSLog(@"ERROR: Failed to launch MacVim (path=%@).%@",
286 path, MMSymlinkWarningString);
290 // Launch MacVim using NSTask. For some reason the above code using
291 // Launch Services sometimes fails on LSOpenFromRefSpec() (when it
292 // fails, the dock icon starts bouncing and never stops). It seems
293 // like rebuilding the Launch Services database takes care of this
294 // problem, but the NSTask way seems more stable so stick with it.
296 // NOTE! Using NSTask to launch the GUI has the negative side-effect
297 // that the GUI won't be activated (or raised) so there is a hack in
298 // MMAppController which raises the app when a new window is opened.
299 NSMutableArray *args = [NSMutableArray arrayWithObjects:
300 [NSString stringWithFormat:@"-%@", MMNoWindowKey], @"yes", nil];
301 NSString *exeName = [[mainBundle infoDictionary]
302 objectForKey:@"CFBundleExecutable"];
303 NSString *path = [mainBundle pathForAuxiliaryExecutable:exeName];
305 NSLog(@"ERROR: Could not find MacVim executable in bundle.%@",
306 MMSymlinkWarningString);
310 [NSTask launchedTaskWithLaunchPath:path arguments:args];
313 // HACK! Poll the mach bootstrap server until it returns a valid
314 // connection to detect that MacVim has finished launching. Also set a
315 // time-out date so that we don't get stuck doing this forever.
316 NSDate *timeOutDate = [NSDate dateWithTimeIntervalSinceNow:10];
317 while (![self connection] &&
318 NSOrderedDescending == [timeOutDate compare:[NSDate date]])
319 [[NSRunLoop currentRunLoop]
320 runMode:NSDefaultRunLoopMode
321 beforeDate:[NSDate dateWithTimeIntervalSinceNow:.1]];
323 // NOTE: [self connection] will set 'connection' as a side-effect.
325 NSLog(@"WARNING: Timed-out waiting for GUI to launch.");
331 [[NSNotificationCenter defaultCenter] addObserver:self
332 selector:@selector(connectionDidDie:)
333 name:NSConnectionDidDieNotification object:connection];
335 appProxy = [[connection rootProxy] retain];
336 [appProxy setProtocolForProxy:@protocol(MMAppProtocol)];
338 // NOTE: We do not set any new timeout values for the connection to the
339 // frontend. This means that if the frontend is "stuck" (e.g. in a
340 // modal loop) then any calls to the frontend will block indefinitely
341 // (the default timeouts are huge).
343 int pid = [[NSProcessInfo processInfo] processIdentifier];
345 identifier = [appProxy connectBackend:self pid:pid];
348 @catch (NSException *e) {
349 NSLog(@"Exception caught when trying to connect backend: \"%@\"", e);
355 - (BOOL)openGUIWindow
357 [self queueMessage:OpenWindowMsgID data:nil];
363 int type = ClearAllDrawType;
365 // Any draw commands in queue are effectively obsolete since this clearAll
366 // will negate any effect they have, therefore we may as well clear the
368 [self clearDrawData];
370 [drawData appendBytes:&type length:sizeof(int)];
373 - (void)clearBlockFromRow:(int)row1 column:(int)col1
374 toRow:(int)row2 column:(int)col2
376 int type = ClearBlockDrawType;
378 [drawData appendBytes:&type length:sizeof(int)];
380 [drawData appendBytes:&defaultBackgroundColor length:sizeof(unsigned)];
381 [drawData appendBytes:&row1 length:sizeof(int)];
382 [drawData appendBytes:&col1 length:sizeof(int)];
383 [drawData appendBytes:&row2 length:sizeof(int)];
384 [drawData appendBytes:&col2 length:sizeof(int)];
387 - (void)deleteLinesFromRow:(int)row count:(int)count
388 scrollBottom:(int)bottom left:(int)left right:(int)right
390 int type = DeleteLinesDrawType;
392 [drawData appendBytes:&type length:sizeof(int)];
394 [drawData appendBytes:&defaultBackgroundColor length:sizeof(unsigned)];
395 [drawData appendBytes:&row length:sizeof(int)];
396 [drawData appendBytes:&count length:sizeof(int)];
397 [drawData appendBytes:&bottom length:sizeof(int)];
398 [drawData appendBytes:&left length:sizeof(int)];
399 [drawData appendBytes:&right length:sizeof(int)];
401 if (left == 0 && right == gui.num_cols-1)
402 [self didChangeWholeLine];
405 - (void)drawString:(char*)s length:(int)len row:(int)row column:(int)col
406 cells:(int)cells flags:(int)flags
408 if (len <= 0 || cells <= 0) return;
410 int type = DrawStringDrawType;
412 [drawData appendBytes:&type length:sizeof(int)];
414 [drawData appendBytes:&backgroundColor length:sizeof(unsigned)];
415 [drawData appendBytes:&foregroundColor length:sizeof(unsigned)];
416 [drawData appendBytes:&specialColor length:sizeof(unsigned)];
417 [drawData appendBytes:&row length:sizeof(int)];
418 [drawData appendBytes:&col length:sizeof(int)];
419 [drawData appendBytes:&cells length:sizeof(int)];
420 [drawData appendBytes:&flags length:sizeof(int)];
421 [drawData appendBytes:&len length:sizeof(int)];
422 [drawData appendBytes:s length:len];
425 - (void)insertLinesFromRow:(int)row count:(int)count
426 scrollBottom:(int)bottom left:(int)left right:(int)right
428 int type = InsertLinesDrawType;
430 [drawData appendBytes:&type length:sizeof(int)];
432 [drawData appendBytes:&defaultBackgroundColor length:sizeof(unsigned)];
433 [drawData appendBytes:&row length:sizeof(int)];
434 [drawData appendBytes:&count length:sizeof(int)];
435 [drawData appendBytes:&bottom length:sizeof(int)];
436 [drawData appendBytes:&left length:sizeof(int)];
437 [drawData appendBytes:&right length:sizeof(int)];
439 if (left == 0 && right == gui.num_cols-1)
440 [self didChangeWholeLine];
443 - (void)drawCursorAtRow:(int)row column:(int)col shape:(int)shape
444 fraction:(int)percent color:(int)color
446 int type = DrawCursorDrawType;
447 unsigned uc = MM_COLOR(color);
449 [drawData appendBytes:&type length:sizeof(int)];
451 [drawData appendBytes:&uc length:sizeof(unsigned)];
452 [drawData appendBytes:&row length:sizeof(int)];
453 [drawData appendBytes:&col length:sizeof(int)];
454 [drawData appendBytes:&shape length:sizeof(int)];
455 [drawData appendBytes:&percent length:sizeof(int)];
458 - (void)drawInvertedRectAtRow:(int)row column:(int)col numRows:(int)nr
459 numColumns:(int)nc invert:(int)invert
461 int type = DrawInvertedRectDrawType;
462 [drawData appendBytes:&type length:sizeof(int)];
464 [drawData appendBytes:&row length:sizeof(int)];
465 [drawData appendBytes:&col length:sizeof(int)];
466 [drawData appendBytes:&nr length:sizeof(int)];
467 [drawData appendBytes:&nc length:sizeof(int)];
468 [drawData appendBytes:&invert length:sizeof(int)];
473 // Keep running the run-loop until there is no more input to process.
474 while (CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0, true)
475 == kCFRunLoopRunHandledSource)
479 - (void)flushQueue:(BOOL)force
481 // NOTE: This variable allows for better control over when the queue is
482 // flushed. It can be set to YES at the beginning of a sequence of calls
483 // that may potentially add items to the queue, and then restored back to
485 if (flushDisabled) return;
487 if ([drawData length] > 0) {
488 // HACK! Detect changes to 'guifontwide'.
489 if (gui.wide_font != oldWideFont) {
490 gui_mch_free_font(oldWideFont);
491 oldWideFont = gui_mch_retain_font(gui.wide_font);
492 [self setFont:oldWideFont wide:YES];
495 int type = SetCursorPosDrawType;
496 [drawData appendBytes:&type length:sizeof(type)];
497 [drawData appendBytes:&gui.row length:sizeof(gui.row)];
498 [drawData appendBytes:&gui.col length:sizeof(gui.col)];
500 [self queueMessage:BatchDrawMsgID data:[drawData copy]];
501 [self clearDrawData];
504 if ([outputQueue count] > 0) {
505 [self insertVimStateMessage];
508 //NSLog(@"[%s] Flushing (count=%d)", _cmd, [outputQueue count]);
509 [appProxy processInput:outputQueue forIdentifier:identifier];
511 @catch (NSException *e) {
512 NSLog(@"[%s] Exception caught: \"%@\"", _cmd, e);
513 NSLog(@"outputQueue(len:%d)=%@", [outputQueue count]/2,
515 if (![connection isValid]) {
516 NSLog(@"WARNING! Connection is invalid, exit now!");
517 NSLog(@"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 [appProxy processInput:outputQueue forIdentifier:identifier];
580 @catch (NSException *e) {
581 NSLog(@"Exception caught when sending CloseWindowMsgID: \"%@\"", e);
584 // NOTE: If Cmd-w was pressed to close the window the menu is briefly
585 // highlighted and during this pause the frontend won't receive any DO
586 // messages. If the Vim process exits before this highlighting has
587 // finished Cocoa will emit the following error message:
588 // *** -[NSMachPort handlePortMessage:]: dropping incoming DO message
589 // because the connection or ports are invalid
590 // To avoid this warning we delay here. If the warning still appears
591 // this delay may need to be increased.
595 #ifdef MAC_CLIENTSERVER
596 // The default connection is used for the client/server code.
597 [[NSConnection defaultConnection] setRootObject:nil];
598 [[NSConnection defaultConnection] invalidate];
602 - (void)selectTab:(int)index
604 //NSLog(@"%s%d", _cmd, index);
607 NSData *data = [NSData dataWithBytes:&index length:sizeof(int)];
608 [self queueMessage:SelectTabMsgID data:data];
613 //NSLog(@"%s", _cmd);
615 NSMutableData *data = [NSMutableData data];
617 int idx = tabpage_index(curtab) - 1;
618 [data appendBytes:&idx length:sizeof(int)];
621 for (tp = first_tabpage; tp != NULL; tp = tp->tp_next) {
622 // Count the number of windows in the tabpage.
623 //win_T *wp = tp->tp_firstwin;
625 //for (wincount = 0; wp != NULL; wp = wp->w_next, ++wincount);
626 //[data appendBytes:&wincount length:sizeof(int)];
628 int tabProp = MMTabInfoCount;
629 [data appendBytes:&tabProp length:sizeof(int)];
630 for (tabProp = MMTabLabel; tabProp < MMTabInfoCount; ++tabProp) {
631 // This function puts the label of the tab in the global 'NameBuff'.
632 get_tabline_label(tp, (tabProp == MMTabToolTip));
633 NSString *s = [NSString stringWithVimString:NameBuff];
634 int len = [s lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
638 [data appendBytes:&len length:sizeof(int)];
640 [data appendBytes:[s UTF8String] length:len];
644 [self queueMessage:UpdateTabBarMsgID data:data];
647 - (BOOL)tabBarVisible
649 return tabBarVisible;
652 - (void)showTabBar:(BOOL)enable
654 tabBarVisible = enable;
656 int msgid = enable ? ShowTabBarMsgID : HideTabBarMsgID;
657 [self queueMessage:msgid data:nil];
660 - (void)setRows:(int)rows columns:(int)cols
662 //NSLog(@"[VimTask] setRows:%d columns:%d", rows, cols);
664 int dim[] = { rows, cols };
665 NSData *data = [NSData dataWithBytes:&dim length:2*sizeof(int)];
667 [self queueMessage:SetTextDimensionsMsgID data:data];
670 - (void)setWindowTitle:(char *)title
672 NSMutableData *data = [NSMutableData data];
673 int len = strlen(title);
674 if (len <= 0) return;
676 [data appendBytes:&len length:sizeof(int)];
677 [data appendBytes:title length:len];
679 [self queueMessage:SetWindowTitleMsgID data:data];
682 - (void)setDocumentFilename:(char *)filename
684 NSMutableData *data = [NSMutableData data];
685 int len = filename ? strlen(filename) : 0;
687 [data appendBytes:&len length:sizeof(int)];
689 [data appendBytes:filename length:len];
691 [self queueMessage:SetDocumentFilenameMsgID data:data];
694 - (char *)browseForFileWithAttributes:(NSDictionary *)attr
698 [self queueMessage:BrowseForFileMsgID properties:attr];
699 [self flushQueue:YES];
702 [self waitForDialogReturn];
704 if (dialogReturn && [dialogReturn isKindOfClass:[NSString class]])
705 s = [dialogReturn vimStringSave];
707 [dialogReturn release]; dialogReturn = nil;
709 @catch (NSException *e) {
710 NSLog(@"[%s] Exception caught: \"%@\"", _cmd, e);
716 - (oneway void)setDialogReturn:(in bycopy id)obj
718 // NOTE: This is called by
719 // - [MMVimController panelDidEnd:::], and
720 // - [MMVimController alertDidEnd:::],
721 // to indicate that a save/open panel or alert has finished.
723 // We want to distinguish between "no dialog return yet" and "dialog
724 // returned nothing". The former can be tested with dialogReturn == nil,
725 // the latter with dialogReturn == [NSNull null].
726 if (!obj) obj = [NSNull null];
728 if (obj != dialogReturn) {
729 [dialogReturn release];
730 dialogReturn = [obj retain];
734 - (int)showDialogWithAttributes:(NSDictionary *)attr textField:(char *)txtfield
738 [self queueMessage:ShowDialogMsgID properties:attr];
739 [self flushQueue:YES];
742 [self waitForDialogReturn];
744 if (dialogReturn && [dialogReturn isKindOfClass:[NSArray class]]
745 && [dialogReturn count]) {
746 retval = [[dialogReturn objectAtIndex:0] intValue];
747 if (txtfield && [dialogReturn count] > 1) {
748 NSString *retString = [dialogReturn objectAtIndex:1];
749 char_u *ret = (char_u*)[retString UTF8String];
751 ret = CONVERT_FROM_UTF8(ret);
753 vim_strncpy((char_u*)txtfield, ret, IOSIZE - 1);
755 CONVERT_FROM_UTF8_FREE(ret);
760 [dialogReturn release]; dialogReturn = nil;
762 @catch (NSException *e) {
763 NSLog(@"[%s] Exception caught: \"%@\"", _cmd, e);
769 - (void)showToolbar:(int)enable flags:(int)flags
771 NSMutableData *data = [NSMutableData data];
773 [data appendBytes:&enable length:sizeof(int)];
774 [data appendBytes:&flags length:sizeof(int)];
776 [self queueMessage:ShowToolbarMsgID data:data];
779 - (void)createScrollbarWithIdentifier:(long)ident type:(int)type
781 NSMutableData *data = [NSMutableData data];
783 [data appendBytes:&ident length:sizeof(long)];
784 [data appendBytes:&type length:sizeof(int)];
786 [self queueMessage:CreateScrollbarMsgID data:data];
789 - (void)destroyScrollbarWithIdentifier:(long)ident
791 NSMutableData *data = [NSMutableData data];
792 [data appendBytes:&ident length:sizeof(long)];
794 [self queueMessage:DestroyScrollbarMsgID data:data];
797 - (void)showScrollbarWithIdentifier:(long)ident state:(int)visible
799 NSMutableData *data = [NSMutableData data];
801 [data appendBytes:&ident length:sizeof(long)];
802 [data appendBytes:&visible length:sizeof(int)];
804 [self queueMessage:ShowScrollbarMsgID data:data];
807 - (void)setScrollbarPosition:(int)pos length:(int)len identifier:(long)ident
809 NSMutableData *data = [NSMutableData data];
811 [data appendBytes:&ident length:sizeof(long)];
812 [data appendBytes:&pos length:sizeof(int)];
813 [data appendBytes:&len length:sizeof(int)];
815 [self queueMessage:SetScrollbarPositionMsgID data:data];
818 - (void)setScrollbarThumbValue:(long)val size:(long)size max:(long)max
819 identifier:(long)ident
821 float fval = max-size+1 > 0 ? (float)val/(max-size+1) : 0;
822 float prop = (float)size/(max+1);
823 if (fval < 0) fval = 0;
824 else if (fval > 1.0f) fval = 1.0f;
825 if (prop < 0) prop = 0;
826 else if (prop > 1.0f) prop = 1.0f;
828 NSMutableData *data = [NSMutableData data];
830 [data appendBytes:&ident length:sizeof(long)];
831 [data appendBytes:&fval length:sizeof(float)];
832 [data appendBytes:&prop length:sizeof(float)];
834 [self queueMessage:SetScrollbarThumbMsgID data:data];
837 - (void)setFont:(GuiFont)font wide:(BOOL)wide
839 NSString *fontName = (NSString *)font;
841 NSArray *components = [fontName componentsSeparatedByString:@":"];
842 if ([components count] == 2) {
843 size = [[components lastObject] floatValue];
844 fontName = [components objectAtIndex:0];
847 int len = [fontName lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
848 NSMutableData *data = [NSMutableData data];
849 [data appendBytes:&size length:sizeof(float)];
850 [data appendBytes:&len length:sizeof(int)];
853 [data appendBytes:[fontName UTF8String] length:len];
855 return; // Only the wide font can be set to nothing
857 [self queueMessage:(wide ? SetWideFontMsgID : SetFontMsgID) data:data];
860 - (void)executeActionWithName:(NSString *)name
862 int len = [name lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
865 NSMutableData *data = [NSMutableData data];
867 [data appendBytes:&len length:sizeof(int)];
868 [data appendBytes:[name UTF8String] length:len];
870 [self queueMessage:ExecuteActionMsgID data:data];
874 - (void)setMouseShape:(int)shape
876 NSMutableData *data = [NSMutableData data];
877 [data appendBytes:&shape length:sizeof(int)];
878 [self queueMessage:SetMouseShapeMsgID data:data];
881 - (void)setBlinkWait:(int)wait on:(int)on off:(int)off
883 // Vim specifies times in milliseconds, whereas Cocoa wants them in
885 blinkWaitInterval = .001f*wait;
886 blinkOnInterval = .001f*on;
887 blinkOffInterval = .001f*off;
893 [blinkTimer invalidate];
894 [blinkTimer release];
898 if (blinkWaitInterval > 0 && blinkOnInterval > 0 && blinkOffInterval > 0
900 blinkState = MMBlinkStateOn;
902 [[NSTimer scheduledTimerWithTimeInterval:blinkWaitInterval
904 selector:@selector(blinkTimerFired:)
905 userInfo:nil repeats:NO] retain];
906 gui_update_cursor(TRUE, FALSE);
907 [self flushQueue:YES];
913 if (MMBlinkStateOff == blinkState) {
914 gui_update_cursor(TRUE, FALSE);
915 [self flushQueue:YES];
918 blinkState = MMBlinkStateNone;
921 - (void)adjustLinespace:(int)linespace
923 NSMutableData *data = [NSMutableData data];
924 [data appendBytes:&linespace length:sizeof(int)];
925 [self queueMessage:AdjustLinespaceMsgID data:data];
930 [self queueMessage:ActivateMsgID data:nil];
933 - (void)setPreEditRow:(int)row column:(int)col
935 NSMutableData *data = [NSMutableData data];
936 [data appendBytes:&row length:sizeof(int)];
937 [data appendBytes:&col length:sizeof(int)];
938 [self queueMessage:SetPreEditPositionMsgID data:data];
941 - (int)lookupColorWithKey:(NSString *)key
943 if (!(key && [key length] > 0))
946 NSString *stripKey = [[[[key lowercaseString]
947 stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]
948 componentsSeparatedByString:@" "]
949 componentsJoinedByString:@""];
951 if (stripKey && [stripKey length] > 0) {
952 // First of all try to lookup key in the color dictionary; note that
953 // all keys in this dictionary are lowercase with no whitespace.
954 id obj = [colorDict objectForKey:stripKey];
955 if (obj) return [obj intValue];
957 // The key was not in the dictionary; is it perhaps of the form
959 if ([stripKey length] > 1 && [stripKey characterAtIndex:0] == '#') {
960 NSScanner *scanner = [NSScanner scannerWithString:stripKey];
961 [scanner setScanLocation:1];
963 if ([scanner scanHexInt:&hex]) {
968 // As a last resort, check if it is one of the system defined colors.
969 // The keys in this dictionary are also lowercase with no whitespace.
970 obj = [sysColorDict objectForKey:stripKey];
972 NSColor *col = [NSColor performSelector:NSSelectorFromString(obj)];
975 col = [col colorUsingColorSpaceName:NSCalibratedRGBColorSpace];
976 [col getRed:&r green:&g blue:&b alpha:&a];
977 return (((int)(r*255+.5f) & 0xff) << 16)
978 + (((int)(g*255+.5f) & 0xff) << 8)
979 + ((int)(b*255+.5f) & 0xff);
984 //NSLog(@"WARNING: No color with key %@ found.", stripKey);
988 - (BOOL)hasSpecialKeyWithValue:(NSString *)value
990 NSEnumerator *e = [[MMBackend specialKeys] objectEnumerator];
993 while ((obj = [e nextObject])) {
994 if ([value isEqual:obj])
1001 - (void)enterFullscreen:(int)fuoptions background:(int)bg
1003 NSMutableData *data = [NSMutableData data];
1004 [data appendBytes:&fuoptions length:sizeof(int)];
1006 [data appendBytes:&bg length:sizeof(int)];
1007 [self queueMessage:EnterFullscreenMsgID data:data];
1010 - (void)leaveFullscreen
1012 [self queueMessage:LeaveFullscreenMsgID data:nil];
1015 - (void)setFullscreenBackgroundColor:(int)color
1017 NSMutableData *data = [NSMutableData data];
1018 color = MM_COLOR(color);
1019 [data appendBytes:&color length:sizeof(int)];
1021 [self queueMessage:SetFullscreenColorMsgID data:data];
1024 - (void)setAntialias:(BOOL)antialias
1026 int msgid = antialias ? EnableAntialiasMsgID : DisableAntialiasMsgID;
1028 [self queueMessage:msgid data:nil];
1031 - (void)updateModifiedFlag
1033 // Notify MacVim if _any_ buffer has changed from unmodified to modified or
1035 int msgid = [self checkForModifiedBuffers]
1036 ? BuffersModifiedMsgID : BuffersNotModifiedMsgID;
1038 [self queueMessage:msgid data:nil];
1041 - (oneway void)processInput:(int)msgid data:(in bycopy NSData *)data
1043 // Look for Cmd-. and Ctrl-C immediately instead of waiting until the input
1044 // queue is processed since that only happens in waitForInput: (and Vim
1045 // regularly checks for Ctrl-C in between waiting for input).
1046 // Similarly, TerminateNowMsgID must be checked immediately otherwise code
1047 // which waits on the run loop will fail to detect this message (e.g. in
1048 // waitForConnectionAcknowledgement).
1050 if (InsertTextMsgID == msgid && data != nil) {
1051 const void *bytes = [data bytes];
1052 bytes += sizeof(int);
1053 int len = *((int*)bytes); bytes += sizeof(int);
1055 char_u *str = (char_u*)bytes;
1056 if ((str[0] == Ctrl_C && ctrl_c_interrupts) ||
1057 (str[0] == intr_char && intr_char != Ctrl_C)) {
1059 [inputQueue removeAllObjects];
1063 } else if (TerminateNowMsgID == msgid) {
1064 // Terminate immediately (the frontend is about to quit or this process
1065 // was aborted). Don't preserve modified files since the user would
1066 // already have been presented with a dialog warning if there were any
1067 // modified files when we get here.
1068 isTerminating = YES;
1073 // Remove all previous instances of this message from the input queue, else
1074 // the input queue may fill up as a result of Vim not being able to keep up
1075 // with the speed at which new messages are received.
1076 // Keyboard input is never dropped, unless the input represents and
1077 // auto-repeated key.
1079 BOOL isKeyRepeat = NO;
1080 BOOL isKeyboardInput = NO;
1082 if (data && (InsertTextMsgID == msgid || KeyDownMsgID == msgid ||
1083 CmdKeyMsgID == msgid)) {
1084 isKeyboardInput = YES;
1086 // The lowest bit of the first int is set if this key is a repeat.
1087 int flags = *((int*)[data bytes]);
1092 // Keyboard input is not removed from the queue; repeats are ignored if
1093 // there already is keyboard input on the input queue.
1094 if (isKeyRepeat || !isKeyboardInput) {
1095 int i, count = [inputQueue count];
1096 for (i = 1; i < count; i+=2) {
1097 if ([[inputQueue objectAtIndex:i-1] intValue] == msgid) {
1101 [inputQueue removeObjectAtIndex:i];
1102 [inputQueue removeObjectAtIndex:i-1];
1108 [inputQueue addObject:[NSNumber numberWithInt:msgid]];
1109 [inputQueue addObject:(data ? (id)data : [NSNull null])];
1112 - (id)evaluateExpressionCocoa:(in bycopy NSString *)expr
1113 errorString:(out bycopy NSString **)errstr
1115 return evalExprCocoa(expr, errstr);
1119 - (NSString *)evaluateExpression:(in bycopy NSString *)expr
1121 NSString *eval = nil;
1122 char_u *s = (char_u*)[expr UTF8String];
1125 s = CONVERT_FROM_UTF8(s);
1128 char_u *res = eval_client_expr_to_string(s);
1131 CONVERT_FROM_UTF8_FREE(s);
1137 s = CONVERT_TO_UTF8(s);
1139 eval = [NSString stringWithUTF8String:(char*)s];
1141 CONVERT_TO_UTF8_FREE(s);
1149 - (BOOL)starRegisterToPasteboard:(byref NSPasteboard *)pboard
1151 // TODO: This method should share code with clip_mch_request_selection().
1153 if (VIsual_active && (State & NORMAL) && clip_star.available) {
1154 // If there is no pasteboard, return YES to indicate that there is text
1159 clip_copy_selection();
1161 // Get the text to put on the pasteboard.
1162 long_u llen = 0; char_u *str = 0;
1163 int type = clip_convert_selection(&str, &llen, &clip_star);
1167 // TODO: Avoid overflow.
1168 int len = (int)llen;
1170 if (output_conv.vc_type != CONV_NONE) {
1171 char_u *conv_str = string_convert(&output_conv, str, &len);
1179 NSString *string = [[NSString alloc]
1180 initWithBytes:str length:len encoding:NSUTF8StringEncoding];
1182 NSArray *types = [NSArray arrayWithObject:NSStringPboardType];
1183 [pboard declareTypes:types owner:nil];
1184 BOOL ok = [pboard setString:string forType:NSStringPboardType];
1195 - (oneway void)addReply:(in bycopy NSString *)reply
1196 server:(in byref id <MMVimServerProtocol>)server
1198 //NSLog(@"addReply:%@ server:%@", reply, (id)server);
1200 // Replies might come at any time and in any order so we keep them in an
1201 // array inside a dictionary with the send port used as key.
1203 NSConnection *conn = [(NSDistantObject*)server connectionForProxy];
1204 // HACK! Assume connection uses mach ports.
1205 int port = [(NSMachPort*)[conn sendPort] machPort];
1206 NSNumber *key = [NSNumber numberWithInt:port];
1208 NSMutableArray *replies = [serverReplyDict objectForKey:key];
1210 replies = [NSMutableArray array];
1211 [serverReplyDict setObject:replies forKey:key];
1214 [replies addObject:reply];
1217 - (void)addInput:(in bycopy NSString *)input
1218 client:(in byref id <MMVimClientProtocol>)client
1220 //NSLog(@"addInput:%@ client:%@", input, (id)client);
1222 // NOTE: We don't call addInput: here because it differs from
1223 // server_to_input_buf() in that it always sets the 'silent' flag and we
1224 // don't want the MacVim client/server code to behave differently from
1226 char_u *s = [input vimStringSave];
1227 server_to_input_buf(s);
1230 [self addClient:(id)client];
1233 - (NSString *)evaluateExpression:(in bycopy NSString *)expr
1234 client:(in byref id <MMVimClientProtocol>)client
1236 [self addClient:(id)client];
1237 return [self evaluateExpression:expr];
1240 - (void)registerServerWithName:(NSString *)name
1242 NSString *svrName = name;
1243 NSConnection *svrConn = [NSConnection defaultConnection];
1246 for (i = 0; i < MMServerMax; ++i) {
1247 NSString *connName = [self connectionNameFromServerName:svrName];
1249 if ([svrConn registerName:connName]) {
1250 //NSLog(@"Registered server with name: %@", svrName);
1252 // TODO: Set request/reply time-outs to something else?
1254 // Don't wait for requests (time-out means that the message is
1256 [svrConn setRequestTimeout:0];
1257 //[svrConn setReplyTimeout:MMReplyTimeout];
1258 [svrConn setRootObject:self];
1260 // NOTE: 'serverName' is a global variable
1261 serverName = [svrName vimStringSave];
1263 set_vim_var_string(VV_SEND_SERVER, serverName, -1);
1266 need_maketitle = TRUE;
1268 [self queueMessage:SetServerNameMsgID data:
1269 [svrName dataUsingEncoding:NSUTF8StringEncoding]];
1273 svrName = [NSString stringWithFormat:@"%@%d", name, i+1];
1277 - (BOOL)sendToServer:(NSString *)name string:(NSString *)string
1278 reply:(char_u **)reply port:(int *)port expression:(BOOL)expr
1281 // NOTE: If 'name' equals 'serverName' then the request is local (client
1282 // and server are the same). This case is not handled separately, so a
1283 // connection will be set up anyway (this simplifies the code).
1285 NSConnection *conn = [self connectionForServerName:name];
1288 char_u *s = (char_u*)[name UTF8String];
1290 s = CONVERT_FROM_UTF8(s);
1292 EMSG2(_(e_noserver), s);
1294 CONVERT_FROM_UTF8_FREE(s);
1301 // HACK! Assume connection uses mach ports.
1302 *port = [(NSMachPort*)[conn sendPort] machPort];
1305 id proxy = [conn rootProxy];
1306 [proxy setProtocolForProxy:@protocol(MMVimServerProtocol)];
1310 NSString *eval = [proxy evaluateExpression:string client:self];
1313 *reply = [eval vimStringSave];
1315 *reply = vim_strsave((char_u*)_(e_invexprmsg));
1322 [proxy addInput:string client:self];
1325 @catch (NSException *e) {
1326 NSLog(@"WARNING: Caught exception in %s: \"%@\"", _cmd, e);
1333 - (NSArray *)serverList
1335 NSArray *list = nil;
1337 if ([self connection]) {
1338 id proxy = [connection rootProxy];
1339 [proxy setProtocolForProxy:@protocol(MMAppProtocol)];
1342 list = [proxy serverList];
1344 @catch (NSException *e) {
1345 NSLog(@"Exception caught when listing servers: \"%@\"", e);
1348 EMSG(_("E???: No connection to MacVim, server listing not possible."));
1354 - (NSString *)peekForReplyOnPort:(int)port
1356 //NSLog(@"%s%d", _cmd, port);
1358 NSNumber *key = [NSNumber numberWithInt:port];
1359 NSMutableArray *replies = [serverReplyDict objectForKey:key];
1360 if (replies && [replies count]) {
1361 //NSLog(@" %d replies, topmost is: %@", [replies count],
1362 // [replies objectAtIndex:0]);
1363 return [replies objectAtIndex:0];
1366 //NSLog(@" No replies");
1370 - (NSString *)waitForReplyOnPort:(int)port
1372 //NSLog(@"%s%d", _cmd, port);
1374 NSConnection *conn = [self connectionForServerPort:port];
1378 NSNumber *key = [NSNumber numberWithInt:port];
1379 NSMutableArray *replies = nil;
1380 NSString *reply = nil;
1382 // Wait for reply as long as the connection to the server is valid (unless
1383 // user interrupts wait with Ctrl-C).
1384 while (!got_int && [conn isValid] &&
1385 !(replies = [serverReplyDict objectForKey:key])) {
1386 [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
1387 beforeDate:[NSDate distantFuture]];
1391 if ([replies count] > 0) {
1392 reply = [[replies objectAtIndex:0] retain];
1393 //NSLog(@" Got reply: %@", reply);
1394 [replies removeObjectAtIndex:0];
1395 [reply autorelease];
1398 if ([replies count] == 0)
1399 [serverReplyDict removeObjectForKey:key];
1405 - (BOOL)sendReply:(NSString *)reply toPort:(int)port
1407 id client = [clientProxyDict objectForKey:[NSNumber numberWithInt:port]];
1410 //NSLog(@"sendReply:%@ toPort:%d", reply, port);
1411 [client addReply:reply server:self];
1414 @catch (NSException *e) {
1415 NSLog(@"WARNING: Exception caught in %s: \"%@\"", _cmd, e);
1418 EMSG2(_("E???: server2client failed; no client with id 0x%x"), port);
1429 - (void)setWaitForAck:(BOOL)yn
1434 - (void)waitForConnectionAcknowledgement
1436 if (!waitForAck) return;
1438 while (waitForAck && !got_int && [connection isValid]) {
1439 [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
1440 beforeDate:[NSDate distantFuture]];
1441 //NSLog(@" waitForAck=%d got_int=%d isValid=%d",
1442 // waitForAck, got_int, [connection isValid]);
1446 // Never received a connection acknowledgement, so die.
1447 [[NSNotificationCenter defaultCenter] removeObserver:self];
1448 [appProxy release]; appProxy = nil;
1450 // NOTE: We intentionally do not call mch_exit() since this in turn
1451 // will lead to -[MMBackend exit] getting called which we want to
1456 [self processInputQueue];
1459 - (oneway void)acknowledgeConnection
1461 //NSLog(@"%s", _cmd);
1470 - (void)setImState:(BOOL)activated
1472 imState = activated;
1479 @implementation MMBackend (Private)
1481 - (void)clearDrawData
1483 [drawData setLength:0];
1484 numWholeLineChanges = offsetForDrawDataPrune = 0;
1487 - (void)didChangeWholeLine
1489 // It may happen that draw queue is filled up with lots of changes that
1490 // affect a whole row. If the number of such changes equals twice the
1491 // number of visible rows then we can prune some commands off the queue.
1493 // NOTE: If we don't perform this pruning the draw queue may grow
1494 // indefinitely if Vim were to repeatedly send draw commands without ever
1495 // waiting for new input (that's when the draw queue is flushed). The one
1496 // instance I know where this can happen is when a command is executed in
1497 // the shell (think ":grep" with thousands of matches).
1499 ++numWholeLineChanges;
1500 if (numWholeLineChanges == gui.num_rows) {
1501 // Remember the offset to prune up to.
1502 offsetForDrawDataPrune = [drawData length];
1503 } else if (numWholeLineChanges == 2*gui.num_rows) {
1504 // Delete all the unnecessary draw commands.
1505 NSMutableData *d = [[NSMutableData alloc]
1506 initWithBytes:[drawData bytes] + offsetForDrawDataPrune
1507 length:[drawData length] - offsetForDrawDataPrune];
1508 offsetForDrawDataPrune = [d length];
1509 numWholeLineChanges -= gui.num_rows;
1515 - (void)waitForDialogReturn
1517 // Keep processing the run loop until a dialog returns. To avoid getting
1518 // stuck in an endless loop (could happen if the setDialogReturn: message
1519 // was lost) we also do some paranoia checks.
1521 // Note that in Cocoa the user can still resize windows and select menu
1522 // items while a sheet is being displayed, so we can't just wait for the
1523 // first message to arrive and assume that is the setDialogReturn: call.
1525 while (nil == dialogReturn && !got_int && [connection isValid])
1526 [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
1527 beforeDate:[NSDate distantFuture]];
1529 // Search for any resize messages on the input queue. All other messages
1530 // on the input queue are dropped. The reason why we single out resize
1531 // messages is because the user may have resized the window while a sheet
1533 int i, count = [inputQueue count];
1535 id textDimData = nil;
1537 for (i = count-2; i >= 0; i -= 2) {
1538 int msgid = [[inputQueue objectAtIndex:i] intValue];
1539 if (SetTextDimensionsMsgID == msgid) {
1540 textDimData = [[inputQueue objectAtIndex:i+1] retain];
1546 [inputQueue removeAllObjects];
1549 [inputQueue addObject:
1550 [NSNumber numberWithInt:SetTextDimensionsMsgID]];
1551 [inputQueue addObject:textDimData];
1552 [textDimData release];
1557 - (void)insertVimStateMessage
1559 // NOTE: This is the place to add Vim state that needs to be accessed from
1560 // MacVim. Do not add state that could potentially require lots of memory
1561 // since this message gets sent each time the output queue is forcibly
1562 // flushed (e.g. storing the currently selected text would be a bad idea).
1563 // We take this approach of "pushing" the state to MacVim to avoid having
1564 // to make synchronous calls from MacVim to Vim in order to get state.
1566 BOOL mmta = curbuf ? curbuf->b_p_mmta : NO;
1567 int numTabs = tabpage_index(NULL) - 1;
1571 NSDictionary *vimState = [NSDictionary dictionaryWithObjectsAndKeys:
1572 [[NSFileManager defaultManager] currentDirectoryPath], @"pwd",
1573 [NSNumber numberWithInt:p_mh], @"p_mh",
1574 [NSNumber numberWithBool:[self unusedEditor]], @"unusedEditor",
1575 [NSNumber numberWithBool:mmta], @"p_mmta",
1576 [NSNumber numberWithInt:numTabs], @"numTabs",
1579 // Put the state before all other messages.
1580 int msgid = SetVimStateMsgID;
1581 [outputQueue insertObject:[vimState dictionaryAsData] atIndex:0];
1582 [outputQueue insertObject:[NSData dataWithBytes:&msgid length:sizeof(int)]
1586 - (void)processInputQueue
1588 if ([inputQueue count] == 0) return;
1590 // NOTE: One of the input events may cause this method to be called
1591 // recursively, so copy the input queue to a local variable and clear the
1592 // queue before starting to process input events (otherwise we could get
1593 // stuck in an endless loop).
1594 NSArray *q = [inputQueue copy];
1595 unsigned i, count = [q count];
1597 [inputQueue removeAllObjects];
1599 for (i = 1; i < count; i+=2) {
1600 int msgid = [[q objectAtIndex:i-1] intValue];
1601 id data = [q objectAtIndex:i];
1602 if ([data isEqual:[NSNull null]])
1605 //NSLog(@"(%d) %s:%s", i, _cmd, MessageStrings[msgid]);
1606 [self handleInputEvent:msgid data:data];
1610 //NSLog(@"Clear input event queue");
1613 - (void)handleInputEvent:(int)msgid data:(NSData *)data
1615 if (InsertTextMsgID == msgid || KeyDownMsgID == msgid ||
1616 CmdKeyMsgID == msgid) {
1618 const void *bytes = [data bytes];
1619 int mods = *((int*)bytes); bytes += sizeof(int);
1620 int len = *((int*)bytes); bytes += sizeof(int);
1621 NSString *key = [[NSString alloc] initWithBytes:bytes
1623 encoding:NSUTF8StringEncoding];
1624 mods = eventModifierFlagsToVimModMask(mods);
1626 if (InsertTextMsgID == msgid)
1627 [self handleInsertText:key];
1629 [self handleKeyDown:key modifiers:mods];
1632 } else if (ScrollWheelMsgID == msgid) {
1634 const void *bytes = [data bytes];
1636 int row = *((int*)bytes); bytes += sizeof(int);
1637 int col = *((int*)bytes); bytes += sizeof(int);
1638 int flags = *((int*)bytes); bytes += sizeof(int);
1639 float dy = *((float*)bytes); bytes += sizeof(float);
1641 int button = MOUSE_5;
1642 if (dy > 0) button = MOUSE_4;
1644 flags = eventModifierFlagsToVimMouseModMask(flags);
1646 int numLines = (int)round(dy);
1647 if (numLines < 0) numLines = -numLines;
1648 if (numLines == 0) numLines = 1;
1650 #ifdef FEAT_GUI_SCROLL_WHEEL_FORCE
1651 gui.scroll_wheel_force = numLines;
1654 gui_send_mouse_event(button, col, row, NO, flags);
1655 } else if (MouseDownMsgID == msgid) {
1657 const void *bytes = [data bytes];
1659 int row = *((int*)bytes); bytes += sizeof(int);
1660 int col = *((int*)bytes); bytes += sizeof(int);
1661 int button = *((int*)bytes); bytes += sizeof(int);
1662 int flags = *((int*)bytes); bytes += sizeof(int);
1663 int count = *((int*)bytes); bytes += sizeof(int);
1665 button = eventButtonNumberToVimMouseButton(button);
1667 flags = eventModifierFlagsToVimMouseModMask(flags);
1668 gui_send_mouse_event(button, col, row, count>1, flags);
1670 } else if (MouseUpMsgID == msgid) {
1672 const void *bytes = [data bytes];
1674 int row = *((int*)bytes); bytes += sizeof(int);
1675 int col = *((int*)bytes); bytes += sizeof(int);
1676 int flags = *((int*)bytes); bytes += sizeof(int);
1678 flags = eventModifierFlagsToVimMouseModMask(flags);
1680 gui_send_mouse_event(MOUSE_RELEASE, col, row, NO, flags);
1681 } else if (MouseDraggedMsgID == msgid) {
1683 const void *bytes = [data bytes];
1685 int row = *((int*)bytes); bytes += sizeof(int);
1686 int col = *((int*)bytes); bytes += sizeof(int);
1687 int flags = *((int*)bytes); bytes += sizeof(int);
1689 flags = eventModifierFlagsToVimMouseModMask(flags);
1691 gui_send_mouse_event(MOUSE_DRAG, col, row, NO, flags);
1692 } else if (MouseMovedMsgID == msgid) {
1693 const void *bytes = [data bytes];
1694 int row = *((int*)bytes); bytes += sizeof(int);
1695 int col = *((int*)bytes); bytes += sizeof(int);
1697 gui_mouse_moved(col, row);
1698 } else if (AddInputMsgID == msgid) {
1699 NSString *string = [[NSString alloc] initWithData:data
1700 encoding:NSUTF8StringEncoding];
1702 [self addInput:string];
1705 } else if (SelectTabMsgID == msgid) {
1707 const void *bytes = [data bytes];
1708 int idx = *((int*)bytes) + 1;
1709 //NSLog(@"Selecting tab %d", idx);
1710 send_tabline_event(idx);
1711 } else if (CloseTabMsgID == msgid) {
1713 const void *bytes = [data bytes];
1714 int idx = *((int*)bytes) + 1;
1715 //NSLog(@"Closing tab %d", idx);
1716 send_tabline_menu_event(idx, TABLINE_MENU_CLOSE);
1717 } else if (AddNewTabMsgID == msgid) {
1718 //NSLog(@"Adding new tab");
1719 send_tabline_menu_event(0, TABLINE_MENU_NEW);
1720 } else if (DraggedTabMsgID == msgid) {
1722 const void *bytes = [data bytes];
1723 // NOTE! The destination index is 0 based, so do not add 1 to make it 1
1725 int idx = *((int*)bytes);
1728 } else if (SetTextDimensionsMsgID == msgid || LiveResizeMsgID == msgid
1729 || SetTextRowsMsgID == msgid || SetTextColumnsMsgID == msgid) {
1731 const void *bytes = [data bytes];
1733 if (SetTextColumnsMsgID != msgid) {
1734 rows = *((int*)bytes); bytes += sizeof(int);
1737 if (SetTextRowsMsgID != msgid) {
1738 cols = *((int*)bytes); bytes += sizeof(int);
1742 if (SetTextRowsMsgID == msgid || SetTextColumnsMsgID == msgid) {
1743 int dim[2] = { rows, cols };
1744 d = [NSData dataWithBytes:dim length:2*sizeof(int)];
1745 msgid = SetTextDimensionsReplyMsgID;
1748 if (SetTextDimensionsMsgID == msgid)
1749 msgid = SetTextDimensionsReplyMsgID;
1751 // NOTE! Vim doesn't call gui_mch_set_shellsize() after
1752 // gui_resize_shell(), so we have to manually set the rows and columns
1753 // here since MacVim doesn't change the rows and columns to avoid
1754 // inconsistent states between Vim and MacVim. The message sent back
1755 // indicates that it is a reply to a message that originated in MacVim
1756 // since we need to be able to determine where a message originated.
1757 [self queueMessage:msgid data:d];
1759 //NSLog(@"[VimTask] Resizing shell to %dx%d.", cols, rows);
1760 gui_resize_shell(cols, rows);
1761 } else if (ExecuteMenuMsgID == msgid) {
1762 NSDictionary *attrs = [NSDictionary dictionaryWithData:data];
1764 NSArray *desc = [attrs objectForKey:@"descriptor"];
1765 vimmenu_T *menu = menu_for_descriptor(desc);
1769 } else if (ToggleToolbarMsgID == msgid) {
1770 [self handleToggleToolbar];
1771 } else if (ScrollbarEventMsgID == msgid) {
1772 [self handleScrollbarEvent:data];
1773 } else if (SetFontMsgID == msgid) {
1774 [self handleSetFont:data];
1775 } else if (VimShouldCloseMsgID == msgid) {
1777 } else if (DropFilesMsgID == msgid) {
1778 [self handleDropFiles:data];
1779 } else if (DropStringMsgID == msgid) {
1780 [self handleDropString:data];
1781 } else if (GotFocusMsgID == msgid) {
1783 [self focusChange:YES];
1784 } else if (LostFocusMsgID == msgid) {
1786 [self focusChange:NO];
1787 } else if (SetMouseShapeMsgID == msgid) {
1788 const void *bytes = [data bytes];
1789 int shape = *((int*)bytes); bytes += sizeof(int);
1790 update_mouseshape(shape);
1791 } else if (XcodeModMsgID == msgid) {
1792 [self handleXcodeMod:data];
1793 } else if (OpenWithArgumentsMsgID == msgid) {
1794 [self handleOpenWithArguments:[NSDictionary dictionaryWithData:data]];
1795 } else if (FindReplaceMsgID == msgid) {
1796 [self handleFindReplace:[NSDictionary dictionaryWithData:data]];
1797 } else if (ActivatedImMsgID == msgid) {
1798 [self setImState:YES];
1799 } else if (DeactivatedImMsgID == msgid) {
1800 [self setImState:NO];
1802 NSLog(@"WARNING: Unknown message received (msgid=%d)", msgid);
1806 + (NSDictionary *)specialKeys
1808 static NSDictionary *specialKeys = nil;
1811 NSBundle *mainBundle = [NSBundle mainBundle];
1812 NSString *path = [mainBundle pathForResource:@"SpecialKeys"
1814 specialKeys = [[NSDictionary alloc] initWithContentsOfFile:path];
1820 - (void)handleInsertText:(NSString *)text
1824 char_u *str = (char_u*)[text UTF8String];
1825 int i, len = [text lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
1828 char_u *conv_str = NULL;
1829 if (input_conv.vc_type != CONV_NONE) {
1830 conv_str = string_convert(&input_conv, str, &len);
1836 for (i = 0; i < len; ++i) {
1837 add_to_input_buf(str+i, 1);
1838 if (CSI == str[i]) {
1839 // NOTE: If the converted string contains the byte CSI, then it
1840 // must be followed by the bytes KS_EXTRA, KE_CSI or things
1842 static char_u extra[2] = { KS_EXTRA, KE_CSI };
1843 add_to_input_buf(extra, 2);
1853 - (void)handleKeyDown:(NSString *)key modifiers:(int)mods
1855 // TODO: This code is a horrible mess -- clean up!
1858 char_u *chars = (char_u*)[key UTF8String];
1860 char_u *conv_str = NULL;
1862 int length = [key lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
1864 // Special keys (arrow keys, function keys, etc.) are stored in a plist so
1865 // that new keys can easily be added.
1866 NSString *specialString = [[MMBackend specialKeys]
1868 if (specialString && [specialString length] > 1) {
1869 //NSLog(@"special key: %@", specialString);
1870 int ikey = TO_SPECIAL([specialString characterAtIndex:0],
1871 [specialString characterAtIndex:1]);
1873 ikey = simplify_key(ikey, &mods);
1878 special[1] = K_SECOND(ikey);
1879 special[2] = K_THIRD(ikey);
1883 } else if (1 == length && TAB == chars[0]) {
1884 // Tab is a trouble child:
1885 // - <Tab> is added to the input buffer as is
1886 // - <S-Tab> is translated to, {CSI,'k','B'} (i.e. 'Back-tab')
1887 // - <M-Tab> should be 0x80|TAB but this is not valid utf-8 so it needs
1888 // to be converted to utf-8
1889 // - <S-M-Tab> is translated to <S-Tab> with ALT modifier
1890 // - <C-Tab> is reserved by Mac OS X
1891 // - <D-Tab> is reserved by Mac OS X
1896 if (mods & MOD_MASK_SHIFT) {
1897 mods &= ~MOD_MASK_SHIFT;
1899 special[1] = K_SECOND(K_S_TAB);
1900 special[2] = K_THIRD(K_S_TAB);
1902 } else if (mods & MOD_MASK_ALT) {
1903 int mtab = 0x80 | TAB;
1907 special[0] = (mtab >> 6) + 0xc0;
1908 special[1] = mtab & 0xbf;
1916 mods &= ~MOD_MASK_ALT;
1918 } else if (1 == length && chars[0] < 0x80 && (mods & MOD_MASK_ALT)) {
1919 // META key is treated separately. This code was taken from gui_w48.c
1920 // and gui_gtk_x11.c.
1922 int ch = simplify_key(chars[0], &mods);
1924 // Remove the SHIFT modifier for keys where it's already included,
1925 // e.g., '(' and '*'
1926 if (ch < 0x100 && !isalpha(ch) && isprint(ch))
1927 mods &= ~MOD_MASK_SHIFT;
1929 // Interpret the ALT key as making the key META, include SHIFT, etc.
1930 ch = extract_modifiers(ch, &mods);
1936 string[len++] = CSI;
1937 string[len++] = KS_MODIFIER;
1938 string[len++] = mods;
1941 if (IS_SPECIAL(ch)) {
1942 string[len++] = CSI;
1943 string[len++] = K_SECOND(ch);
1944 string[len++] = K_THIRD(ch);
1948 // TODO: What if 'enc' is not "utf-8"?
1949 if (enc_utf8 && (ch & 0x80)) { // convert to utf-8
1950 string[len++] = ch & 0xbf;
1951 string[len-2] = ((unsigned)ch >> 6) + 0xc0;
1952 if (string[len-1] == CSI) {
1953 string[len++] = KS_EXTRA;
1954 string[len++] = (int)KE_CSI;
1960 add_to_input_buf(string, len);
1962 } else if (length > 0) {
1963 unichar c = [key characterAtIndex:0];
1964 //NSLog(@"non-special: %@ (hex=%x, mods=%d)", key,
1965 // [key characterAtIndex:0], mods);
1967 // HACK! In most circumstances the Ctrl and Shift modifiers should be
1968 // cleared since they are already added to the key by the AppKit.
1969 // Unfortunately, the only way to deal with when to clear the modifiers
1970 // or not seems to be to have hard-wired rules like this.
1971 if ( !((' ' == c) || (0xa0 == c) || (mods & MOD_MASK_CMD)
1972 || 0x9 == c || 0xd == c || ESC == c) ) {
1973 mods &= ~MOD_MASK_SHIFT;
1974 mods &= ~MOD_MASK_CTRL;
1975 //NSLog(@"clear shift ctrl");
1979 if (input_conv.vc_type != CONV_NONE) {
1980 conv_str = string_convert(&input_conv, chars, &length);
1987 if (chars && length > 0) {
1989 //NSLog(@"adding mods: %d", mods);
1991 modChars[1] = KS_MODIFIER;
1993 add_to_input_buf(modChars, 3);
1996 //NSLog(@"add to input buf: 0x%x", chars[0]);
1997 // TODO: Check for CSI bytes?
1998 add_to_input_buf(chars, length);
2007 - (void)queueMessage:(int)msgid data:(NSData *)data
2009 //if (msgid != EnableMenuItemMsgID)
2010 // NSLog(@"queueMessage:%s", MessageStrings[msgid]);
2012 [outputQueue addObject:[NSData dataWithBytes:&msgid length:sizeof(int)]];
2014 [outputQueue addObject:data];
2016 [outputQueue addObject:[NSData data]];
2019 - (void)connectionDidDie:(NSNotification *)notification
2021 // If the main connection to MacVim is lost this means that either MacVim
2022 // has crashed or this process did not receive its termination message
2023 // properly (e.g. if the TerminateNowMsgID was dropped).
2025 // NOTE: This is not called if a Vim controller invalidates its connection.
2027 NSLog(@"WARNING[%s]: Main connection was lost before process had a chance "
2028 "to terminate; preserving swap files.", _cmd);
2029 getout_preserve_modified(1);
2032 - (void)blinkTimerFired:(NSTimer *)timer
2034 NSTimeInterval timeInterval = 0;
2036 [blinkTimer release];
2039 if (MMBlinkStateOn == blinkState) {
2040 gui_undraw_cursor();
2041 blinkState = MMBlinkStateOff;
2042 timeInterval = blinkOffInterval;
2043 } else if (MMBlinkStateOff == blinkState) {
2044 gui_update_cursor(TRUE, FALSE);
2045 blinkState = MMBlinkStateOn;
2046 timeInterval = blinkOnInterval;
2049 if (timeInterval > 0) {
2051 [[NSTimer scheduledTimerWithTimeInterval:timeInterval target:self
2052 selector:@selector(blinkTimerFired:)
2053 userInfo:nil repeats:NO] retain];
2054 [self flushQueue:YES];
2058 - (void)focusChange:(BOOL)on
2060 gui_focus_change(on);
2063 - (void)handleToggleToolbar
2065 // If 'go' contains 'T', then remove it, else add it.
2067 char_u go[sizeof(GO_ALL)+2];
2072 p = vim_strchr(go, GO_TOOLBAR);
2076 char_u *end = go + len;
2082 go[len] = GO_TOOLBAR;
2086 set_option_value((char_u*)"guioptions", 0, go, 0);
2088 [self redrawScreen];
2091 - (void)handleScrollbarEvent:(NSData *)data
2095 const void *bytes = [data bytes];
2096 long ident = *((long*)bytes); bytes += sizeof(long);
2097 int hitPart = *((int*)bytes); bytes += sizeof(int);
2098 float fval = *((float*)bytes); bytes += sizeof(float);
2099 scrollbar_T *sb = gui_find_scrollbar(ident);
2102 scrollbar_T *sb_info = sb->wp ? &sb->wp->w_scrollbars[0] : sb;
2103 long value = sb_info->value;
2104 long size = sb_info->size;
2105 long max = sb_info->max;
2106 BOOL isStillDragging = NO;
2107 BOOL updateKnob = YES;
2110 case NSScrollerDecrementPage:
2111 value -= (size > 2 ? size - 2 : 1);
2113 case NSScrollerIncrementPage:
2114 value += (size > 2 ? size - 2 : 1);
2116 case NSScrollerDecrementLine:
2119 case NSScrollerIncrementLine:
2122 case NSScrollerKnob:
2123 isStillDragging = YES;
2125 case NSScrollerKnobSlot:
2126 value = (long)(fval * (max - size + 1));
2133 //NSLog(@"value %d -> %d", sb_info->value, value);
2134 gui_drag_scrollbar(sb, value, isStillDragging);
2137 // Dragging the knob or option+clicking automatically updates
2138 // the knob position (on the actual NSScroller), so we only
2139 // need to set the knob position in the other cases.
2141 // Update both the left&right vertical scrollbars.
2142 long identLeft = sb->wp->w_scrollbars[SBAR_LEFT].ident;
2143 long identRight = sb->wp->w_scrollbars[SBAR_RIGHT].ident;
2144 [self setScrollbarThumbValue:value size:size max:max
2145 identifier:identLeft];
2146 [self setScrollbarThumbValue:value size:size max:max
2147 identifier:identRight];
2149 // Update the horizontal scrollbar.
2150 [self setScrollbarThumbValue:value size:size max:max
2157 - (void)handleSetFont:(NSData *)data
2161 const void *bytes = [data bytes];
2162 int pointSize = (int)*((float*)bytes); bytes += sizeof(float);
2164 unsigned len = *((unsigned*)bytes); bytes += sizeof(unsigned);
2165 NSMutableString *name = [NSMutableString stringWithUTF8String:bytes];
2168 [name appendString:[NSString stringWithFormat:@":h%d", pointSize]];
2169 char_u *s = (char_u*)[name UTF8String];
2171 unsigned wlen = *((unsigned*)bytes); bytes += sizeof(unsigned);
2174 NSMutableString *wname = [NSMutableString stringWithUTF8String:bytes];
2177 [wname appendString:[NSString stringWithFormat:@":h%d", pointSize]];
2178 ws = (char_u*)[wname UTF8String];
2182 s = CONVERT_FROM_UTF8(s);
2184 ws = CONVERT_FROM_UTF8(ws);
2188 set_option_value((char_u*)"guifont", 0, s, 0);
2190 if (ws && gui.wide_font != NOFONT) {
2191 // NOTE: This message is sent on Cmd-+/Cmd-- and as such should only
2192 // change the wide font if 'gfw' is non-empty (the frontend always has
2193 // some wide font set, even if 'gfw' is empty).
2194 set_option_value((char_u*)"guifontwide", 0, ws, 0);
2199 CONVERT_FROM_UTF8_FREE(ws);
2201 CONVERT_FROM_UTF8_FREE(s);
2204 [self redrawScreen];
2207 - (void)handleDropFiles:(NSData *)data
2209 // TODO: Get rid of this method; instead use Vim script directly. At the
2210 // moment I know how to do this to open files in tabs, but I'm not sure how
2211 // to add the filenames to the command line when in command line mode.
2215 NSMutableDictionary *args = [NSMutableDictionary dictionaryWithData:data];
2218 id obj = [args objectForKey:@"forceOpen"];
2219 BOOL forceOpen = YES;
2221 forceOpen = [obj boolValue];
2223 NSArray *filenames = [args objectForKey:@"filenames"];
2224 if (!(filenames && [filenames count] > 0)) return;
2227 if (!forceOpen && (State & CMDLINE)) {
2228 // HACK! If Vim is in command line mode then the files names
2229 // should be added to the command line, instead of opening the
2230 // files in tabs (unless forceOpen is set). This is taken care of by
2231 // gui_handle_drop().
2232 int n = [filenames count];
2233 char_u **fnames = (char_u **)alloc(n * sizeof(char_u *));
2236 for (i = 0; i < n; ++i)
2237 fnames[i] = [[filenames objectAtIndex:i] vimStringSave];
2239 // NOTE! This function will free 'fnames'.
2240 // HACK! It is assumed that the 'x' and 'y' arguments are
2241 // unused when in command line mode.
2242 gui_handle_drop(0, 0, 0, fnames, n);
2247 [self handleOpenWithArguments:args];
2251 - (void)handleDropString:(NSData *)data
2256 char_u dropkey[3] = { CSI, KS_EXTRA, (char_u)KE_DROP };
2257 const void *bytes = [data bytes];
2258 int len = *((int*)bytes); bytes += sizeof(int);
2259 NSMutableString *string = [NSMutableString stringWithUTF8String:bytes];
2261 // Replace unrecognized end-of-line sequences with \x0a (line feed).
2262 NSRange range = { 0, [string length] };
2263 unsigned n = [string replaceOccurrencesOfString:@"\x0d\x0a"
2264 withString:@"\x0a" options:0
2267 n = [string replaceOccurrencesOfString:@"\x0d" withString:@"\x0a"
2268 options:0 range:range];
2271 len = [string lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
2272 char_u *s = (char_u*)[string UTF8String];
2274 if (input_conv.vc_type != CONV_NONE)
2275 s = string_convert(&input_conv, s, &len);
2277 dnd_yank_drag_data(s, len);
2279 if (input_conv.vc_type != CONV_NONE)
2282 add_to_input_buf(dropkey, sizeof(dropkey));
2286 - (void)startOdbEditWithArguments:(NSDictionary *)args
2288 #ifdef FEAT_ODB_EDITOR
2289 id obj = [args objectForKey:@"remoteID"];
2292 OSType serverID = [obj unsignedIntValue];
2293 NSString *remotePath = [args objectForKey:@"remotePath"];
2295 NSAppleEventDescriptor *token = nil;
2296 NSData *tokenData = [args objectForKey:@"remoteTokenData"];
2297 obj = [args objectForKey:@"remoteTokenDescType"];
2298 if (tokenData && obj) {
2299 DescType tokenType = [obj unsignedLongValue];
2300 token = [NSAppleEventDescriptor descriptorWithDescriptorType:tokenType
2304 NSArray *filenames = [args objectForKey:@"filenames"];
2305 unsigned i, numFiles = [filenames count];
2306 for (i = 0; i < numFiles; ++i) {
2307 NSString *filename = [filenames objectAtIndex:i];
2308 char_u *s = [filename vimStringSave];
2309 buf_T *buf = buflist_findname(s);
2313 if (buf->b_odb_token) {
2314 [(NSAppleEventDescriptor*)(buf->b_odb_token) release];
2315 buf->b_odb_token = NULL;
2318 if (buf->b_odb_fname) {
2319 vim_free(buf->b_odb_fname);
2320 buf->b_odb_fname = NULL;
2323 buf->b_odb_server_id = serverID;
2326 buf->b_odb_token = [token retain];
2328 buf->b_odb_fname = [remotePath vimStringSave];
2330 NSLog(@"WARNING: Could not find buffer '%@' for ODB editing.",
2334 #endif // FEAT_ODB_EDITOR
2337 - (void)handleXcodeMod:(NSData *)data
2340 const void *bytes = [data bytes];
2341 DescType type = *((DescType*)bytes); bytes += sizeof(DescType);
2342 unsigned len = *((unsigned*)bytes); bytes += sizeof(unsigned);
2346 NSAppleEventDescriptor *replyEvent = [NSAppleEventDescriptor
2347 descriptorWithDescriptorType:type
2353 - (void)handleOpenWithArguments:(NSDictionary *)args
2355 // ARGUMENT: DESCRIPTION:
2356 // -------------------------------------------------------------
2357 // filenames list of filenames
2358 // dontOpen don't open files specified in above argument
2359 // layout which layout to use to open files
2360 // selectionRange range of lines to select
2361 // searchText string to search for
2362 // cursorLine line to position the cursor on
2363 // cursorColumn column to position the cursor on
2364 // (only valid when "cursorLine" is set)
2365 // remoteID ODB parameter
2366 // remotePath ODB parameter
2367 // remoteTokenDescType ODB parameter
2368 // remoteTokenData ODB parameter
2370 //NSLog(@"%s%@ (starting=%d)", _cmd, args, starting);
2372 NSArray *filenames = [args objectForKey:@"filenames"];
2373 int i, numFiles = filenames ? [filenames count] : 0;
2374 BOOL openFiles = ![[args objectForKey:@"dontOpen"] boolValue];
2375 int layout = [[args objectForKey:@"layout"] intValue];
2377 // Change to directory of first file to open if this is an "unused" editor
2378 // (but do not do this if editing remotely).
2379 if (openFiles && numFiles > 0 && ![args objectForKey:@"remoteID"]
2380 && (starting || [self unusedEditor]) ) {
2381 char_u *s = [[filenames objectAtIndex:0] vimStringSave];
2387 // When Vim is starting we simply add the files to be opened to the
2388 // global arglist and Vim will take care of opening them for us.
2389 if (openFiles && numFiles > 0) {
2390 for (i = 0; i < numFiles; i++) {
2391 NSString *fname = [filenames objectAtIndex:i];
2394 if (ga_grow(&global_alist.al_ga, 1) == FAIL
2395 || (p = [fname vimStringSave]) == NULL)
2396 exit(2); // See comment in -[MMBackend exit]
2398 alist_add(&global_alist, p, 2);
2401 // Vim will take care of arranging the files added to the arglist
2402 // in windows or tabs; all we must do is to specify which layout to
2404 initialWindowLayout = layout;
2407 // When Vim is already open we resort to some trickery to open the
2408 // files with the specified layout.
2410 // TODO: Figure out a better way to handle this?
2411 if (openFiles && numFiles > 0) {
2412 BOOL oneWindowInTab = topframe ? YES
2413 : (topframe->fr_layout == FR_LEAF);
2414 BOOL bufChanged = NO;
2415 BOOL bufHasFilename = NO;
2417 bufChanged = curbufIsChanged();
2418 bufHasFilename = curbuf->b_ffname != NULL;
2421 // Temporarily disable flushing since the following code may
2422 // potentially cause multiple redraws.
2423 flushDisabled = YES;
2425 BOOL onlyOneTab = (first_tabpage->tp_next == NULL);
2426 if (WIN_TABS == layout && !onlyOneTab) {
2427 // By going to the last tabpage we ensure that the new tabs
2428 // will appear last (if this call is left out, the taborder
2433 // Make sure we're in normal mode first.
2434 [self addInput:@"<C-\\><C-N>"];
2437 // With "split layout" we open a new tab before opening
2438 // multiple files if the current tab has more than one window
2439 // or if there is exactly one window but whose buffer has a
2440 // filename. (The :drop command ensures modified buffers get
2441 // their own window.)
2442 if ((WIN_HOR == layout || WIN_VER == layout) &&
2443 (!oneWindowInTab || bufHasFilename))
2444 [self addInput:@":tabnew<CR>"];
2446 // The files are opened by constructing a ":drop ..." command
2447 // and executing it.
2448 NSMutableString *cmd = (WIN_TABS == layout)
2449 ? [NSMutableString stringWithString:@":tab drop"]
2450 : [NSMutableString stringWithString:@":drop"];
2452 for (i = 0; i < numFiles; ++i) {
2453 NSString *file = [filenames objectAtIndex:i];
2454 file = [file stringByEscapingSpecialFilenameCharacters];
2455 [cmd appendString:@" "];
2456 [cmd appendString:file];
2459 // Temporarily clear 'suffixes' so that the files are opened in
2460 // the same order as they appear in the "filenames" array.
2461 [self addInput:@":let mvim_oldsu=&su|set su=<CR>"];
2463 [self addInput:cmd];
2465 // Split the view into multiple windows if requested.
2466 if (WIN_HOR == layout)
2467 [self addInput:@"|sall"];
2468 else if (WIN_VER == layout)
2469 [self addInput:@"|vert sall"];
2471 // Restore the old value of 'suffixes'.
2472 [self addInput:@"|let &su=mvim_oldsu|unlet mvim_oldsu<CR>"];
2474 // When opening one file we try to reuse the current window,
2475 // but not if its buffer is modified or has a filename.
2476 // However, the 'arglist' layout always opens the file in the
2478 NSString *file = [[filenames lastObject]
2479 stringByEscapingSpecialFilenameCharacters];
2481 if (WIN_HOR == layout) {
2482 if (!(bufHasFilename || bufChanged))
2483 cmd = [NSString stringWithFormat:@":e %@", file];
2485 cmd = [NSString stringWithFormat:@":sp %@", file];
2486 } else if (WIN_VER == layout) {
2487 if (!(bufHasFilename || bufChanged))
2488 cmd = [NSString stringWithFormat:@":e %@", file];
2490 cmd = [NSString stringWithFormat:@":vsp %@", file];
2491 } else if (WIN_TABS == layout) {
2492 if (oneWindowInTab && !(bufHasFilename || bufChanged))
2493 cmd = [NSString stringWithFormat:@":e %@", file];
2495 cmd = [NSString stringWithFormat:@":tabe %@", file];
2497 // (The :drop command will split if there is a modified
2499 cmd = [NSString stringWithFormat:@":drop %@", file];
2502 [self addInput:cmd];
2503 [self addInput:@"<CR>"];
2506 // Force screen redraw (does it have to be this complicated?).
2507 // (This code was taken from the end of gui_handle_drop().)
2508 update_screen(NOT_VALID);
2511 gui_update_cursor(FALSE, FALSE);
2518 if ([args objectForKey:@"remoteID"]) {
2519 // NOTE: We have to delay processing any ODB related arguments since
2520 // the file(s) may not be opened until the input buffer is processed.
2521 [self performSelector:@selector(startOdbEditWithArguments:)
2526 NSString *lineString = [args objectForKey:@"cursorLine"];
2527 if (lineString && [lineString intValue] > 0) {
2528 NSString *columnString = [args objectForKey:@"cursorColumn"];
2529 if (!(columnString && [columnString intValue] > 0))
2530 columnString = @"1";
2532 NSString *cmd = [NSString stringWithFormat:@"<C-\\><C-N>:cal "
2533 "cursor(%@,%@)|norm! zz<CR>:f<CR>", lineString, columnString];
2534 [self addInput:cmd];
2537 NSString *rangeString = [args objectForKey:@"selectionRange"];
2539 // Build a command line string that will select the given range of
2540 // lines. If range.length == 0, then position the cursor on the given
2541 // line but do not select.
2542 NSRange range = NSRangeFromString(rangeString);
2544 if (range.length > 0) {
2545 cmd = [NSString stringWithFormat:@"<C-\\><C-N>%dGV%dGz.0",
2546 NSMaxRange(range), range.location];
2548 cmd = [NSString stringWithFormat:@"<C-\\><C-N>%dGz.0",
2552 [self addInput:cmd];
2555 NSString *searchText = [args objectForKey:@"searchText"];
2557 [self addInput:[NSString stringWithFormat:@"<C-\\><C-N>gg/\\c%@/e<CR>",
2562 - (BOOL)checkForModifiedBuffers
2565 for (buf = firstbuf; buf != NULL; buf = buf->b_next) {
2566 if (bufIsChanged(buf)) {
2574 - (void)addInput:(NSString *)input
2576 // NOTE: This code is essentially identical to server_to_input_buf(),
2577 // except the 'silent' flag is TRUE in the call to ins_typebuf() below.
2578 char_u *string = [input vimStringSave];
2579 if (!string) return;
2581 /* Set 'cpoptions' the way we want it.
2582 * B set - backslashes are *not* treated specially
2583 * k set - keycodes are *not* reverse-engineered
2584 * < unset - <Key> sequences *are* interpreted
2585 * The last but one parameter of replace_termcodes() is TRUE so that the
2586 * <lt> sequence is recognised - needed for a real backslash.
2589 char_u *cpo_save = p_cpo;
2590 p_cpo = (char_u *)"Bk";
2591 char_u *str = replace_termcodes((char_u *)string, &ptr, FALSE, TRUE, FALSE);
2594 if (*ptr != NUL) /* trailing CTRL-V results in nothing */
2597 * Add the string to the input stream.
2598 * Can't use add_to_input_buf() here, we now have K_SPECIAL bytes.
2600 * First clear typed characters from the typeahead buffer, there could
2601 * be half a mapping there. Then append to the existing string, so
2602 * that multiple commands from a client are concatenated.
2604 if (typebuf.tb_maplen < typebuf.tb_len)
2605 del_typebuf(typebuf.tb_len - typebuf.tb_maplen, typebuf.tb_maplen);
2606 (void)ins_typebuf(str, REMAP_NONE, typebuf.tb_len, TRUE, TRUE);
2608 /* Let input_available() know we inserted text in the typeahead
2610 typebuf_was_filled = TRUE;
2616 - (BOOL)unusedEditor
2618 BOOL oneWindowInTab = topframe ? YES
2619 : (topframe->fr_layout == FR_LEAF);
2620 BOOL bufChanged = NO;
2621 BOOL bufHasFilename = NO;
2623 bufChanged = curbufIsChanged();
2624 bufHasFilename = curbuf->b_ffname != NULL;
2627 BOOL onlyOneTab = (first_tabpage->tp_next == NULL);
2629 return onlyOneTab && oneWindowInTab && !bufChanged && !bufHasFilename;
2632 - (void)redrawScreen
2634 // Force screen redraw (does it have to be this complicated?).
2635 redraw_all_later(CLEAR);
2636 update_screen(NOT_VALID);
2639 gui_update_cursor(FALSE, FALSE);
2641 // HACK! The cursor is not put back at the command line by the above
2642 // "redraw commands". The following test seems to do the trick though.
2643 if (State & CMDLINE)
2647 - (void)handleFindReplace:(NSDictionary *)args
2651 NSString *findString = [args objectForKey:@"find"];
2652 if (!findString) return;
2654 char_u *find = [findString vimStringSave];
2655 char_u *replace = [[args objectForKey:@"replace"] vimStringSave];
2656 int flags = [[args objectForKey:@"flags"] intValue];
2658 // NOTE: The flag 0x100 is used to indicate a backward search.
2659 gui_do_findrepl(flags, find, replace, (flags & 0x100) == 0);
2665 @end // MMBackend (Private)
2670 @implementation MMBackend (ClientServer)
2672 - (NSString *)connectionNameFromServerName:(NSString *)name
2674 NSString *bundlePath = [[NSBundle mainBundle] bundlePath];
2676 return [[NSString stringWithFormat:@"%@.%@", bundlePath, name]
2680 - (NSConnection *)connectionForServerName:(NSString *)name
2682 // TODO: Try 'name%d' if 'name' fails.
2683 NSString *connName = [self connectionNameFromServerName:name];
2684 NSConnection *svrConn = [connectionNameDict objectForKey:connName];
2687 svrConn = [NSConnection connectionWithRegisteredName:connName
2689 // Try alternate server...
2690 if (!svrConn && alternateServerName) {
2691 //NSLog(@" trying to connect to alternate server: %@",
2692 // alternateServerName);
2693 connName = [self connectionNameFromServerName:alternateServerName];
2694 svrConn = [NSConnection connectionWithRegisteredName:connName
2698 // Try looking for alternate servers...
2700 //NSLog(@" looking for alternate servers...");
2701 NSString *alt = [self alternateServerNameForName:name];
2702 if (alt != alternateServerName) {
2703 //NSLog(@" found alternate server: %@", string);
2704 [alternateServerName release];
2705 alternateServerName = [alt copy];
2709 // Try alternate server again...
2710 if (!svrConn && alternateServerName) {
2711 //NSLog(@" trying to connect to alternate server: %@",
2712 // alternateServerName);
2713 connName = [self connectionNameFromServerName:alternateServerName];
2714 svrConn = [NSConnection connectionWithRegisteredName:connName
2719 [connectionNameDict setObject:svrConn forKey:connName];
2721 //NSLog(@"Adding %@ as connection observer for %@", self, svrConn);
2722 [[NSNotificationCenter defaultCenter] addObserver:self
2723 selector:@selector(serverConnectionDidDie:)
2724 name:NSConnectionDidDieNotification object:svrConn];
2731 - (NSConnection *)connectionForServerPort:(int)port
2734 NSEnumerator *e = [connectionNameDict objectEnumerator];
2736 while ((conn = [e nextObject])) {
2737 // HACK! Assume connection uses mach ports.
2738 if (port == [(NSMachPort*)[conn sendPort] machPort])
2745 - (void)serverConnectionDidDie:(NSNotification *)notification
2747 //NSLog(@"%s%@", _cmd, notification);
2749 NSConnection *svrConn = [notification object];
2751 //NSLog(@"Removing %@ as connection observer from %@", self, svrConn);
2752 [[NSNotificationCenter defaultCenter]
2754 name:NSConnectionDidDieNotification
2757 [connectionNameDict removeObjectsForKeys:
2758 [connectionNameDict allKeysForObject:svrConn]];
2760 // HACK! Assume connection uses mach ports.
2761 int port = [(NSMachPort*)[svrConn sendPort] machPort];
2762 NSNumber *key = [NSNumber numberWithInt:port];
2764 [clientProxyDict removeObjectForKey:key];
2765 [serverReplyDict removeObjectForKey:key];
2768 - (void)addClient:(NSDistantObject *)client
2770 NSConnection *conn = [client connectionForProxy];
2771 // HACK! Assume connection uses mach ports.
2772 int port = [(NSMachPort*)[conn sendPort] machPort];
2773 NSNumber *key = [NSNumber numberWithInt:port];
2775 if (![clientProxyDict objectForKey:key]) {
2776 [client setProtocolForProxy:@protocol(MMVimClientProtocol)];
2777 [clientProxyDict setObject:client forKey:key];
2780 // NOTE: 'clientWindow' is a global variable which is used by <client>
2781 clientWindow = port;
2784 - (NSString *)alternateServerNameForName:(NSString *)name
2786 if (!(name && [name length] > 0))
2789 // Only look for alternates if 'name' doesn't end in a digit.
2790 unichar lastChar = [name characterAtIndex:[name length]-1];
2791 if (lastChar >= '0' && lastChar <= '9')
2794 // Look for alternates among all current servers.
2795 NSArray *list = [self serverList];
2796 if (!(list && [list count] > 0))
2799 // Filter out servers starting with 'name' and ending with a number. The
2800 // (?i) pattern ensures that the match is case insensitive.
2801 NSString *pat = [NSString stringWithFormat:@"(?i)%@[0-9]+\\z", name];
2802 NSPredicate *pred = [NSPredicate predicateWithFormat:
2803 @"SELF MATCHES %@", pat];
2804 list = [list filteredArrayUsingPredicate:pred];
2805 if ([list count] > 0) {
2806 list = [list sortedArrayUsingSelector:@selector(serverNameCompare:)];
2807 return [list objectAtIndex:0];
2813 @end // MMBackend (ClientServer)
2818 @implementation NSString (MMServerNameCompare)
2819 - (NSComparisonResult)serverNameCompare:(NSString *)string
2821 return [self compare:string
2822 options:NSCaseInsensitiveSearch|NSNumericSearch];
2829 static int eventModifierFlagsToVimModMask(int modifierFlags)
2833 if (modifierFlags & NSShiftKeyMask)
2834 modMask |= MOD_MASK_SHIFT;
2835 if (modifierFlags & NSControlKeyMask)
2836 modMask |= MOD_MASK_CTRL;
2837 if (modifierFlags & NSAlternateKeyMask)
2838 modMask |= MOD_MASK_ALT;
2839 if (modifierFlags & NSCommandKeyMask)
2840 modMask |= MOD_MASK_CMD;
2845 static int eventModifierFlagsToVimMouseModMask(int modifierFlags)
2849 if (modifierFlags & NSShiftKeyMask)
2850 modMask |= MOUSE_SHIFT;
2851 if (modifierFlags & NSControlKeyMask)
2852 modMask |= MOUSE_CTRL;
2853 if (modifierFlags & NSAlternateKeyMask)
2854 modMask |= MOUSE_ALT;
2859 static int eventButtonNumberToVimMouseButton(int buttonNumber)
2861 static int mouseButton[] = { MOUSE_LEFT, MOUSE_RIGHT, MOUSE_MIDDLE };
2863 return (buttonNumber >= 0 && buttonNumber < 3)
2864 ? mouseButton[buttonNumber] : -1;
2869 // This function is modeled after the VimToPython function found in if_python.c
2870 // NB This does a deep copy by value, it does not lookup references like the
2871 // VimToPython function does. This is because I didn't want to deal with the
2872 // retain cycles that this would create, and we can cover 99% of the use cases
2873 // by ignoring it. If we ever switch to using GC in MacVim then this
2874 // functionality can be implemented easily.
2875 static id vimToCocoa(typval_T * tv, int depth)
2881 // Avoid infinite recursion
2886 if (tv->v_type == VAR_STRING) {
2887 char_u * val = tv->vval.v_string;
2888 // val can be NULL if the string is empty
2890 result = [NSString string];
2893 val = CONVERT_TO_UTF8(val);
2895 result = [NSString stringWithUTF8String:(char*)val];
2897 CONVERT_TO_UTF8_FREE(val);
2900 } else if (tv->v_type == VAR_NUMBER) {
2901 // looks like sizeof(varnumber_T) is always <= sizeof(long)
2902 result = [NSNumber numberWithLong:(long)tv->vval.v_number];
2903 } else if (tv->v_type == VAR_LIST) {
2904 list_T * list = tv->vval.v_list;
2907 NSMutableArray * arr = result = [NSMutableArray array];
2910 for (curr = list->lv_first; curr != NULL; curr = curr->li_next) {
2911 newObj = vimToCocoa(&curr->li_tv, depth + 1);
2912 [arr addObject:newObj];
2915 } else if (tv->v_type == VAR_DICT) {
2916 NSMutableDictionary * dict = result = [NSMutableDictionary dictionary];
2918 if (tv->vval.v_dict != NULL) {
2919 hashtab_T * ht = &tv->vval.v_dict->dv_hashtab;
2920 int todo = ht->ht_used;
2924 for (hi = ht->ht_array; todo > 0; ++hi) {
2925 if (!HASHITEM_EMPTY(hi)) {
2928 di = dict_lookup(hi);
2929 newObj = vimToCocoa(&di->di_tv, depth + 1);
2931 char_u * keyval = hi->hi_key;
2933 keyval = CONVERT_TO_UTF8(keyval);
2935 NSString * key = [NSString stringWithUTF8String:(char*)keyval];
2937 CONVERT_TO_UTF8_FREE(keyval);
2939 [dict setObject:newObj forKey:key];
2943 } else { // only func refs should fall into this category?
2951 // This function is modeled after eval_client_expr_to_string found in main.c
2952 // Returns nil if there was an error evaluating the expression, and writes a
2953 // message to errorStr.
2954 // TODO Get the error that occurred while evaluating the expression in vim
2956 static id evalExprCocoa(NSString * expr, NSString ** errstr)
2959 char_u *s = (char_u*)[expr UTF8String];
2962 s = CONVERT_FROM_UTF8(s);
2965 int save_dbl = debug_break_level;
2966 int save_ro = redir_off;
2968 debug_break_level = -1;
2972 typval_T * tvres = eval_expr(s, NULL);
2974 debug_break_level = save_dbl;
2975 redir_off = save_ro;
2982 CONVERT_FROM_UTF8_FREE(s);
2987 gui_update_cursor(FALSE, FALSE);
2990 if (tvres == NULL) {
2992 *errstr = @"Expression evaluation failed.";
2995 id res = vimToCocoa(tvres, 1);
3000 *errstr = @"Conversion to cocoa values failed.";
3008 @implementation NSString (VimStrings)
3010 + (id)stringWithVimString:(char_u *)s
3012 // This method ensures a non-nil string is returned. If 's' cannot be
3013 // converted to a utf-8 string it is assumed to be latin-1. If conversion
3014 // still fails an empty NSString is returned.
3015 NSString *string = nil;
3018 s = CONVERT_TO_UTF8(s);
3020 string = [NSString stringWithUTF8String:(char*)s];
3022 // HACK! Apparently 's' is not a valid utf-8 string, maybe it is
3024 string = [NSString stringWithCString:(char*)s
3025 encoding:NSISOLatin1StringEncoding];
3028 CONVERT_TO_UTF8_FREE(s);
3032 return string != nil ? string : [NSString string];
3035 - (char_u *)vimStringSave
3037 char_u *s = (char_u*)[self UTF8String], *ret = NULL;
3040 s = CONVERT_FROM_UTF8(s);
3042 ret = vim_strsave(s);
3044 CONVERT_FROM_UTF8_FREE(s);
3050 @end // NSString (VimStrings)