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);
1469 @implementation MMBackend (Private)
1471 - (void)clearDrawData
1473 [drawData setLength:0];
1474 numWholeLineChanges = offsetForDrawDataPrune = 0;
1477 - (void)didChangeWholeLine
1479 // It may happen that draw queue is filled up with lots of changes that
1480 // affect a whole row. If the number of such changes equals twice the
1481 // number of visible rows then we can prune some commands off the queue.
1483 // NOTE: If we don't perform this pruning the draw queue may grow
1484 // indefinitely if Vim were to repeatedly send draw commands without ever
1485 // waiting for new input (that's when the draw queue is flushed). The one
1486 // instance I know where this can happen is when a command is executed in
1487 // the shell (think ":grep" with thousands of matches).
1489 ++numWholeLineChanges;
1490 if (numWholeLineChanges == gui.num_rows) {
1491 // Remember the offset to prune up to.
1492 offsetForDrawDataPrune = [drawData length];
1493 } else if (numWholeLineChanges == 2*gui.num_rows) {
1494 // Delete all the unnecessary draw commands.
1495 NSMutableData *d = [[NSMutableData alloc]
1496 initWithBytes:[drawData bytes] + offsetForDrawDataPrune
1497 length:[drawData length] - offsetForDrawDataPrune];
1498 offsetForDrawDataPrune = [d length];
1499 numWholeLineChanges -= gui.num_rows;
1505 - (void)waitForDialogReturn
1507 // Keep processing the run loop until a dialog returns. To avoid getting
1508 // stuck in an endless loop (could happen if the setDialogReturn: message
1509 // was lost) we also do some paranoia checks.
1511 // Note that in Cocoa the user can still resize windows and select menu
1512 // items while a sheet is being displayed, so we can't just wait for the
1513 // first message to arrive and assume that is the setDialogReturn: call.
1515 while (nil == dialogReturn && !got_int && [connection isValid])
1516 [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
1517 beforeDate:[NSDate distantFuture]];
1519 // Search for any resize messages on the input queue. All other messages
1520 // on the input queue are dropped. The reason why we single out resize
1521 // messages is because the user may have resized the window while a sheet
1523 int i, count = [inputQueue count];
1525 id textDimData = nil;
1527 for (i = count-2; i >= 0; i -= 2) {
1528 int msgid = [[inputQueue objectAtIndex:i] intValue];
1529 if (SetTextDimensionsMsgID == msgid) {
1530 textDimData = [[inputQueue objectAtIndex:i+1] retain];
1536 [inputQueue removeAllObjects];
1539 [inputQueue addObject:
1540 [NSNumber numberWithInt:SetTextDimensionsMsgID]];
1541 [inputQueue addObject:textDimData];
1542 [textDimData release];
1547 - (void)insertVimStateMessage
1549 // NOTE: This is the place to add Vim state that needs to be accessed from
1550 // MacVim. Do not add state that could potentially require lots of memory
1551 // since this message gets sent each time the output queue is forcibly
1552 // flushed (e.g. storing the currently selected text would be a bad idea).
1553 // We take this approach of "pushing" the state to MacVim to avoid having
1554 // to make synchronous calls from MacVim to Vim in order to get state.
1556 BOOL mmta = curbuf ? curbuf->b_p_mmta : NO;
1557 int numTabs = tabpage_index(NULL) - 1;
1561 NSDictionary *vimState = [NSDictionary dictionaryWithObjectsAndKeys:
1562 [[NSFileManager defaultManager] currentDirectoryPath], @"pwd",
1563 [NSNumber numberWithInt:p_mh], @"p_mh",
1564 [NSNumber numberWithBool:[self unusedEditor]], @"unusedEditor",
1565 [NSNumber numberWithBool:mmta], @"p_mmta",
1566 [NSNumber numberWithInt:numTabs], @"numTabs",
1569 // Put the state before all other messages.
1570 int msgid = SetVimStateMsgID;
1571 [outputQueue insertObject:[vimState dictionaryAsData] atIndex:0];
1572 [outputQueue insertObject:[NSData dataWithBytes:&msgid length:sizeof(int)]
1576 - (void)processInputQueue
1578 if ([inputQueue count] == 0) return;
1580 // NOTE: One of the input events may cause this method to be called
1581 // recursively, so copy the input queue to a local variable and clear the
1582 // queue before starting to process input events (otherwise we could get
1583 // stuck in an endless loop).
1584 NSArray *q = [inputQueue copy];
1585 unsigned i, count = [q count];
1587 [inputQueue removeAllObjects];
1589 for (i = 1; i < count; i+=2) {
1590 int msgid = [[q objectAtIndex:i-1] intValue];
1591 id data = [q objectAtIndex:i];
1592 if ([data isEqual:[NSNull null]])
1595 //NSLog(@"(%d) %s:%s", i, _cmd, MessageStrings[msgid]);
1596 [self handleInputEvent:msgid data:data];
1600 //NSLog(@"Clear input event queue");
1603 - (void)handleInputEvent:(int)msgid data:(NSData *)data
1605 if (InsertTextMsgID == msgid || KeyDownMsgID == msgid ||
1606 CmdKeyMsgID == msgid) {
1608 const void *bytes = [data bytes];
1609 int mods = *((int*)bytes); bytes += sizeof(int);
1610 int len = *((int*)bytes); bytes += sizeof(int);
1611 NSString *key = [[NSString alloc] initWithBytes:bytes
1613 encoding:NSUTF8StringEncoding];
1614 mods = eventModifierFlagsToVimModMask(mods);
1616 if (InsertTextMsgID == msgid)
1617 [self handleInsertText:key];
1619 [self handleKeyDown:key modifiers:mods];
1622 } else if (ScrollWheelMsgID == msgid) {
1624 const void *bytes = [data bytes];
1626 int row = *((int*)bytes); bytes += sizeof(int);
1627 int col = *((int*)bytes); bytes += sizeof(int);
1628 int flags = *((int*)bytes); bytes += sizeof(int);
1629 float dy = *((float*)bytes); bytes += sizeof(float);
1631 int button = MOUSE_5;
1632 if (dy > 0) button = MOUSE_4;
1634 flags = eventModifierFlagsToVimMouseModMask(flags);
1636 int numLines = (int)round(dy);
1637 if (numLines < 0) numLines = -numLines;
1638 if (numLines == 0) numLines = 1;
1640 #ifdef FEAT_GUI_SCROLL_WHEEL_FORCE
1641 gui.scroll_wheel_force = numLines;
1644 gui_send_mouse_event(button, col, row, NO, flags);
1645 } else if (MouseDownMsgID == msgid) {
1647 const void *bytes = [data bytes];
1649 int row = *((int*)bytes); bytes += sizeof(int);
1650 int col = *((int*)bytes); bytes += sizeof(int);
1651 int button = *((int*)bytes); bytes += sizeof(int);
1652 int flags = *((int*)bytes); bytes += sizeof(int);
1653 int count = *((int*)bytes); bytes += sizeof(int);
1655 button = eventButtonNumberToVimMouseButton(button);
1657 flags = eventModifierFlagsToVimMouseModMask(flags);
1658 gui_send_mouse_event(button, col, row, count>1, flags);
1660 } else if (MouseUpMsgID == msgid) {
1662 const void *bytes = [data bytes];
1664 int row = *((int*)bytes); bytes += sizeof(int);
1665 int col = *((int*)bytes); bytes += sizeof(int);
1666 int flags = *((int*)bytes); bytes += sizeof(int);
1668 flags = eventModifierFlagsToVimMouseModMask(flags);
1670 gui_send_mouse_event(MOUSE_RELEASE, col, row, NO, flags);
1671 } else if (MouseDraggedMsgID == msgid) {
1673 const void *bytes = [data bytes];
1675 int row = *((int*)bytes); bytes += sizeof(int);
1676 int col = *((int*)bytes); bytes += sizeof(int);
1677 int flags = *((int*)bytes); bytes += sizeof(int);
1679 flags = eventModifierFlagsToVimMouseModMask(flags);
1681 gui_send_mouse_event(MOUSE_DRAG, col, row, NO, flags);
1682 } else if (MouseMovedMsgID == msgid) {
1683 const void *bytes = [data bytes];
1684 int row = *((int*)bytes); bytes += sizeof(int);
1685 int col = *((int*)bytes); bytes += sizeof(int);
1687 gui_mouse_moved(col, row);
1688 } else if (AddInputMsgID == msgid) {
1689 NSString *string = [[NSString alloc] initWithData:data
1690 encoding:NSUTF8StringEncoding];
1692 [self addInput:string];
1695 } else if (SelectTabMsgID == msgid) {
1697 const void *bytes = [data bytes];
1698 int idx = *((int*)bytes) + 1;
1699 //NSLog(@"Selecting tab %d", idx);
1700 send_tabline_event(idx);
1701 } else if (CloseTabMsgID == msgid) {
1703 const void *bytes = [data bytes];
1704 int idx = *((int*)bytes) + 1;
1705 //NSLog(@"Closing tab %d", idx);
1706 send_tabline_menu_event(idx, TABLINE_MENU_CLOSE);
1707 } else if (AddNewTabMsgID == msgid) {
1708 //NSLog(@"Adding new tab");
1709 send_tabline_menu_event(0, TABLINE_MENU_NEW);
1710 } else if (DraggedTabMsgID == msgid) {
1712 const void *bytes = [data bytes];
1713 // NOTE! The destination index is 0 based, so do not add 1 to make it 1
1715 int idx = *((int*)bytes);
1718 } else if (SetTextDimensionsMsgID == msgid || LiveResizeMsgID == msgid
1719 || SetTextRowsMsgID == msgid || SetTextColumnsMsgID == msgid) {
1721 const void *bytes = [data bytes];
1723 if (SetTextColumnsMsgID != msgid) {
1724 rows = *((int*)bytes); bytes += sizeof(int);
1727 if (SetTextRowsMsgID != msgid) {
1728 cols = *((int*)bytes); bytes += sizeof(int);
1732 if (SetTextRowsMsgID == msgid || SetTextColumnsMsgID == msgid) {
1733 int dim[2] = { rows, cols };
1734 d = [NSData dataWithBytes:dim length:2*sizeof(int)];
1735 msgid = SetTextDimensionsReplyMsgID;
1738 if (SetTextDimensionsMsgID == msgid)
1739 msgid = SetTextDimensionsReplyMsgID;
1741 // NOTE! Vim doesn't call gui_mch_set_shellsize() after
1742 // gui_resize_shell(), so we have to manually set the rows and columns
1743 // here since MacVim doesn't change the rows and columns to avoid
1744 // inconsistent states between Vim and MacVim. The message sent back
1745 // indicates that it is a reply to a message that originated in MacVim
1746 // since we need to be able to determine where a message originated.
1747 [self queueMessage:msgid data:d];
1749 //NSLog(@"[VimTask] Resizing shell to %dx%d.", cols, rows);
1750 gui_resize_shell(cols, rows);
1751 } else if (ExecuteMenuMsgID == msgid) {
1752 NSDictionary *attrs = [NSDictionary dictionaryWithData:data];
1754 NSArray *desc = [attrs objectForKey:@"descriptor"];
1755 vimmenu_T *menu = menu_for_descriptor(desc);
1759 } else if (ToggleToolbarMsgID == msgid) {
1760 [self handleToggleToolbar];
1761 } else if (ScrollbarEventMsgID == msgid) {
1762 [self handleScrollbarEvent:data];
1763 } else if (SetFontMsgID == msgid) {
1764 [self handleSetFont:data];
1765 } else if (VimShouldCloseMsgID == msgid) {
1767 } else if (DropFilesMsgID == msgid) {
1768 [self handleDropFiles:data];
1769 } else if (DropStringMsgID == msgid) {
1770 [self handleDropString:data];
1771 } else if (GotFocusMsgID == msgid) {
1773 [self focusChange:YES];
1774 } else if (LostFocusMsgID == msgid) {
1776 [self focusChange:NO];
1777 } else if (SetMouseShapeMsgID == msgid) {
1778 const void *bytes = [data bytes];
1779 int shape = *((int*)bytes); bytes += sizeof(int);
1780 update_mouseshape(shape);
1781 } else if (XcodeModMsgID == msgid) {
1782 [self handleXcodeMod:data];
1783 } else if (OpenWithArgumentsMsgID == msgid) {
1784 [self handleOpenWithArguments:[NSDictionary dictionaryWithData:data]];
1785 } else if (FindReplaceMsgID == msgid) {
1786 [self handleFindReplace:[NSDictionary dictionaryWithData:data]];
1788 NSLog(@"WARNING: Unknown message received (msgid=%d)", msgid);
1792 + (NSDictionary *)specialKeys
1794 static NSDictionary *specialKeys = nil;
1797 NSBundle *mainBundle = [NSBundle mainBundle];
1798 NSString *path = [mainBundle pathForResource:@"SpecialKeys"
1800 specialKeys = [[NSDictionary alloc] initWithContentsOfFile:path];
1806 - (void)handleInsertText:(NSString *)text
1810 char_u *str = (char_u*)[text UTF8String];
1811 int i, len = [text lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
1814 char_u *conv_str = NULL;
1815 if (input_conv.vc_type != CONV_NONE) {
1816 conv_str = string_convert(&input_conv, str, &len);
1822 for (i = 0; i < len; ++i) {
1823 add_to_input_buf(str+i, 1);
1824 if (CSI == str[i]) {
1825 // NOTE: If the converted string contains the byte CSI, then it
1826 // must be followed by the bytes KS_EXTRA, KE_CSI or things
1828 static char_u extra[2] = { KS_EXTRA, KE_CSI };
1829 add_to_input_buf(extra, 2);
1839 - (void)handleKeyDown:(NSString *)key modifiers:(int)mods
1841 // TODO: This code is a horrible mess -- clean up!
1844 char_u *chars = (char_u*)[key UTF8String];
1846 char_u *conv_str = NULL;
1848 int length = [key lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
1850 // Special keys (arrow keys, function keys, etc.) are stored in a plist so
1851 // that new keys can easily be added.
1852 NSString *specialString = [[MMBackend specialKeys]
1854 if (specialString && [specialString length] > 1) {
1855 //NSLog(@"special key: %@", specialString);
1856 int ikey = TO_SPECIAL([specialString characterAtIndex:0],
1857 [specialString characterAtIndex:1]);
1859 ikey = simplify_key(ikey, &mods);
1864 special[1] = K_SECOND(ikey);
1865 special[2] = K_THIRD(ikey);
1869 } else if (1 == length && TAB == chars[0]) {
1870 // Tab is a trouble child:
1871 // - <Tab> is added to the input buffer as is
1872 // - <S-Tab> is translated to, {CSI,'k','B'} (i.e. 'Back-tab')
1873 // - <M-Tab> should be 0x80|TAB but this is not valid utf-8 so it needs
1874 // to be converted to utf-8
1875 // - <S-M-Tab> is translated to <S-Tab> with ALT modifier
1876 // - <C-Tab> is reserved by Mac OS X
1877 // - <D-Tab> is reserved by Mac OS X
1882 if (mods & MOD_MASK_SHIFT) {
1883 mods &= ~MOD_MASK_SHIFT;
1885 special[1] = K_SECOND(K_S_TAB);
1886 special[2] = K_THIRD(K_S_TAB);
1888 } else if (mods & MOD_MASK_ALT) {
1889 int mtab = 0x80 | TAB;
1893 special[0] = (mtab >> 6) + 0xc0;
1894 special[1] = mtab & 0xbf;
1902 mods &= ~MOD_MASK_ALT;
1904 } else if (1 == length && chars[0] < 0x80 && (mods & MOD_MASK_ALT)) {
1905 // META key is treated separately. This code was taken from gui_w48.c
1906 // and gui_gtk_x11.c.
1908 int ch = simplify_key(chars[0], &mods);
1910 // Remove the SHIFT modifier for keys where it's already included,
1911 // e.g., '(' and '*'
1912 if (ch < 0x100 && !isalpha(ch) && isprint(ch))
1913 mods &= ~MOD_MASK_SHIFT;
1915 // Interpret the ALT key as making the key META, include SHIFT, etc.
1916 ch = extract_modifiers(ch, &mods);
1922 string[len++] = CSI;
1923 string[len++] = KS_MODIFIER;
1924 string[len++] = mods;
1927 if (IS_SPECIAL(ch)) {
1928 string[len++] = CSI;
1929 string[len++] = K_SECOND(ch);
1930 string[len++] = K_THIRD(ch);
1934 // TODO: What if 'enc' is not "utf-8"?
1935 if (enc_utf8 && (ch & 0x80)) { // convert to utf-8
1936 string[len++] = ch & 0xbf;
1937 string[len-2] = ((unsigned)ch >> 6) + 0xc0;
1938 if (string[len-1] == CSI) {
1939 string[len++] = KS_EXTRA;
1940 string[len++] = (int)KE_CSI;
1946 add_to_input_buf(string, len);
1948 } else if (length > 0) {
1949 unichar c = [key characterAtIndex:0];
1950 //NSLog(@"non-special: %@ (hex=%x, mods=%d)", key,
1951 // [key characterAtIndex:0], mods);
1953 // HACK! In most circumstances the Ctrl and Shift modifiers should be
1954 // cleared since they are already added to the key by the AppKit.
1955 // Unfortunately, the only way to deal with when to clear the modifiers
1956 // or not seems to be to have hard-wired rules like this.
1957 if ( !((' ' == c) || (0xa0 == c) || (mods & MOD_MASK_CMD)
1958 || 0x9 == c || 0xd == c || ESC == c) ) {
1959 mods &= ~MOD_MASK_SHIFT;
1960 mods &= ~MOD_MASK_CTRL;
1961 //NSLog(@"clear shift ctrl");
1965 if (input_conv.vc_type != CONV_NONE) {
1966 conv_str = string_convert(&input_conv, chars, &length);
1973 if (chars && length > 0) {
1975 //NSLog(@"adding mods: %d", mods);
1977 modChars[1] = KS_MODIFIER;
1979 add_to_input_buf(modChars, 3);
1982 //NSLog(@"add to input buf: 0x%x", chars[0]);
1983 // TODO: Check for CSI bytes?
1984 add_to_input_buf(chars, length);
1993 - (void)queueMessage:(int)msgid data:(NSData *)data
1995 //if (msgid != EnableMenuItemMsgID)
1996 // NSLog(@"queueMessage:%s", MessageStrings[msgid]);
1998 [outputQueue addObject:[NSData dataWithBytes:&msgid length:sizeof(int)]];
2000 [outputQueue addObject:data];
2002 [outputQueue addObject:[NSData data]];
2005 - (void)connectionDidDie:(NSNotification *)notification
2007 // If the main connection to MacVim is lost this means that either MacVim
2008 // has crashed or this process did not receive its termination message
2009 // properly (e.g. if the TerminateNowMsgID was dropped).
2011 // NOTE: This is not called if a Vim controller invalidates its connection.
2013 NSLog(@"WARNING[%s]: Main connection was lost before process had a chance "
2014 "to terminate; preserving swap files.", _cmd);
2015 getout_preserve_modified(1);
2018 - (void)blinkTimerFired:(NSTimer *)timer
2020 NSTimeInterval timeInterval = 0;
2022 [blinkTimer release];
2025 if (MMBlinkStateOn == blinkState) {
2026 gui_undraw_cursor();
2027 blinkState = MMBlinkStateOff;
2028 timeInterval = blinkOffInterval;
2029 } else if (MMBlinkStateOff == blinkState) {
2030 gui_update_cursor(TRUE, FALSE);
2031 blinkState = MMBlinkStateOn;
2032 timeInterval = blinkOnInterval;
2035 if (timeInterval > 0) {
2037 [[NSTimer scheduledTimerWithTimeInterval:timeInterval target:self
2038 selector:@selector(blinkTimerFired:)
2039 userInfo:nil repeats:NO] retain];
2040 [self flushQueue:YES];
2044 - (void)focusChange:(BOOL)on
2046 gui_focus_change(on);
2049 - (void)handleToggleToolbar
2051 // If 'go' contains 'T', then remove it, else add it.
2053 char_u go[sizeof(GO_ALL)+2];
2058 p = vim_strchr(go, GO_TOOLBAR);
2062 char_u *end = go + len;
2068 go[len] = GO_TOOLBAR;
2072 set_option_value((char_u*)"guioptions", 0, go, 0);
2074 [self redrawScreen];
2077 - (void)handleScrollbarEvent:(NSData *)data
2081 const void *bytes = [data bytes];
2082 long ident = *((long*)bytes); bytes += sizeof(long);
2083 int hitPart = *((int*)bytes); bytes += sizeof(int);
2084 float fval = *((float*)bytes); bytes += sizeof(float);
2085 scrollbar_T *sb = gui_find_scrollbar(ident);
2088 scrollbar_T *sb_info = sb->wp ? &sb->wp->w_scrollbars[0] : sb;
2089 long value = sb_info->value;
2090 long size = sb_info->size;
2091 long max = sb_info->max;
2092 BOOL isStillDragging = NO;
2093 BOOL updateKnob = YES;
2096 case NSScrollerDecrementPage:
2097 value -= (size > 2 ? size - 2 : 1);
2099 case NSScrollerIncrementPage:
2100 value += (size > 2 ? size - 2 : 1);
2102 case NSScrollerDecrementLine:
2105 case NSScrollerIncrementLine:
2108 case NSScrollerKnob:
2109 isStillDragging = YES;
2111 case NSScrollerKnobSlot:
2112 value = (long)(fval * (max - size + 1));
2119 //NSLog(@"value %d -> %d", sb_info->value, value);
2120 gui_drag_scrollbar(sb, value, isStillDragging);
2123 // Dragging the knob or option+clicking automatically updates
2124 // the knob position (on the actual NSScroller), so we only
2125 // need to set the knob position in the other cases.
2127 // Update both the left&right vertical scrollbars.
2128 long identLeft = sb->wp->w_scrollbars[SBAR_LEFT].ident;
2129 long identRight = sb->wp->w_scrollbars[SBAR_RIGHT].ident;
2130 [self setScrollbarThumbValue:value size:size max:max
2131 identifier:identLeft];
2132 [self setScrollbarThumbValue:value size:size max:max
2133 identifier:identRight];
2135 // Update the horizontal scrollbar.
2136 [self setScrollbarThumbValue:value size:size max:max
2143 - (void)handleSetFont:(NSData *)data
2147 const void *bytes = [data bytes];
2148 float pointSize = *((float*)bytes); bytes += sizeof(float);
2149 //unsigned len = *((unsigned*)bytes); bytes += sizeof(unsigned);
2150 bytes += sizeof(unsigned); // len not used
2152 NSMutableString *name = [NSMutableString stringWithUTF8String:bytes];
2153 [name appendString:[NSString stringWithFormat:@":h%d", (int)pointSize]];
2154 char_u *s = (char_u*)[name UTF8String];
2157 s = CONVERT_FROM_UTF8(s);
2160 set_option_value((char_u*)"guifont", 0, s, 0);
2163 CONVERT_FROM_UTF8_FREE(s);
2166 [self redrawScreen];
2169 - (void)handleDropFiles:(NSData *)data
2171 // TODO: Get rid of this method; instead use Vim script directly. At the
2172 // moment I know how to do this to open files in tabs, but I'm not sure how
2173 // to add the filenames to the command line when in command line mode.
2177 NSMutableDictionary *args = [NSMutableDictionary dictionaryWithData:data];
2180 id obj = [args objectForKey:@"forceOpen"];
2181 BOOL forceOpen = YES;
2183 forceOpen = [obj boolValue];
2185 NSArray *filenames = [args objectForKey:@"filenames"];
2186 if (!(filenames && [filenames count] > 0)) return;
2189 if (!forceOpen && (State & CMDLINE)) {
2190 // HACK! If Vim is in command line mode then the files names
2191 // should be added to the command line, instead of opening the
2192 // files in tabs (unless forceOpen is set). This is taken care of by
2193 // gui_handle_drop().
2194 int n = [filenames count];
2195 char_u **fnames = (char_u **)alloc(n * sizeof(char_u *));
2198 for (i = 0; i < n; ++i)
2199 fnames[i] = [[filenames objectAtIndex:i] vimStringSave];
2201 // NOTE! This function will free 'fnames'.
2202 // HACK! It is assumed that the 'x' and 'y' arguments are
2203 // unused when in command line mode.
2204 gui_handle_drop(0, 0, 0, fnames, n);
2209 [self handleOpenWithArguments:args];
2213 - (void)handleDropString:(NSData *)data
2218 char_u dropkey[3] = { CSI, KS_EXTRA, (char_u)KE_DROP };
2219 const void *bytes = [data bytes];
2220 int len = *((int*)bytes); bytes += sizeof(int);
2221 NSMutableString *string = [NSMutableString stringWithUTF8String:bytes];
2223 // Replace unrecognized end-of-line sequences with \x0a (line feed).
2224 NSRange range = { 0, [string length] };
2225 unsigned n = [string replaceOccurrencesOfString:@"\x0d\x0a"
2226 withString:@"\x0a" options:0
2229 n = [string replaceOccurrencesOfString:@"\x0d" withString:@"\x0a"
2230 options:0 range:range];
2233 len = [string lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
2234 char_u *s = (char_u*)[string UTF8String];
2236 if (input_conv.vc_type != CONV_NONE)
2237 s = string_convert(&input_conv, s, &len);
2239 dnd_yank_drag_data(s, len);
2241 if (input_conv.vc_type != CONV_NONE)
2244 add_to_input_buf(dropkey, sizeof(dropkey));
2248 - (void)startOdbEditWithArguments:(NSDictionary *)args
2250 #ifdef FEAT_ODB_EDITOR
2251 id obj = [args objectForKey:@"remoteID"];
2254 OSType serverID = [obj unsignedIntValue];
2255 NSString *remotePath = [args objectForKey:@"remotePath"];
2257 NSAppleEventDescriptor *token = nil;
2258 NSData *tokenData = [args objectForKey:@"remoteTokenData"];
2259 obj = [args objectForKey:@"remoteTokenDescType"];
2260 if (tokenData && obj) {
2261 DescType tokenType = [obj unsignedLongValue];
2262 token = [NSAppleEventDescriptor descriptorWithDescriptorType:tokenType
2266 NSArray *filenames = [args objectForKey:@"filenames"];
2267 unsigned i, numFiles = [filenames count];
2268 for (i = 0; i < numFiles; ++i) {
2269 NSString *filename = [filenames objectAtIndex:i];
2270 char_u *s = [filename vimStringSave];
2271 buf_T *buf = buflist_findname(s);
2275 if (buf->b_odb_token) {
2276 [(NSAppleEventDescriptor*)(buf->b_odb_token) release];
2277 buf->b_odb_token = NULL;
2280 if (buf->b_odb_fname) {
2281 vim_free(buf->b_odb_fname);
2282 buf->b_odb_fname = NULL;
2285 buf->b_odb_server_id = serverID;
2288 buf->b_odb_token = [token retain];
2290 buf->b_odb_fname = [remotePath vimStringSave];
2292 NSLog(@"WARNING: Could not find buffer '%@' for ODB editing.",
2296 #endif // FEAT_ODB_EDITOR
2299 - (void)handleXcodeMod:(NSData *)data
2302 const void *bytes = [data bytes];
2303 DescType type = *((DescType*)bytes); bytes += sizeof(DescType);
2304 unsigned len = *((unsigned*)bytes); bytes += sizeof(unsigned);
2308 NSAppleEventDescriptor *replyEvent = [NSAppleEventDescriptor
2309 descriptorWithDescriptorType:type
2315 - (void)handleOpenWithArguments:(NSDictionary *)args
2317 // ARGUMENT: DESCRIPTION:
2318 // -------------------------------------------------------------
2319 // filenames list of filenames
2320 // dontOpen don't open files specified in above argument
2321 // layout which layout to use to open files
2322 // selectionRange range of lines to select
2323 // searchText string to search for
2324 // cursorLine line to position the cursor on
2325 // cursorColumn column to position the cursor on
2326 // (only valid when "cursorLine" is set)
2327 // remoteID ODB parameter
2328 // remotePath ODB parameter
2329 // remoteTokenDescType ODB parameter
2330 // remoteTokenData ODB parameter
2332 //NSLog(@"%s%@ (starting=%d)", _cmd, args, starting);
2334 NSArray *filenames = [args objectForKey:@"filenames"];
2335 int i, numFiles = filenames ? [filenames count] : 0;
2336 BOOL openFiles = ![[args objectForKey:@"dontOpen"] boolValue];
2337 int layout = [[args objectForKey:@"layout"] intValue];
2339 // Change to directory of first file to open if this is an "unused" editor
2340 // (but do not do this if editing remotely).
2341 if (openFiles && numFiles > 0 && ![args objectForKey:@"remoteID"]
2342 && (starting || [self unusedEditor]) ) {
2343 char_u *s = [[filenames objectAtIndex:0] vimStringSave];
2349 // When Vim is starting we simply add the files to be opened to the
2350 // global arglist and Vim will take care of opening them for us.
2351 if (openFiles && numFiles > 0) {
2352 for (i = 0; i < numFiles; i++) {
2353 NSString *fname = [filenames objectAtIndex:i];
2356 if (ga_grow(&global_alist.al_ga, 1) == FAIL
2357 || (p = [fname vimStringSave]) == NULL)
2358 exit(2); // See comment in -[MMBackend exit]
2360 alist_add(&global_alist, p, 2);
2363 // Vim will take care of arranging the files added to the arglist
2364 // in windows or tabs; all we must do is to specify which layout to
2366 initialWindowLayout = layout;
2369 // When Vim is already open we resort to some trickery to open the
2370 // files with the specified layout.
2372 // TODO: Figure out a better way to handle this?
2373 if (openFiles && numFiles > 0) {
2374 BOOL oneWindowInTab = topframe ? YES
2375 : (topframe->fr_layout == FR_LEAF);
2376 BOOL bufChanged = NO;
2377 BOOL bufHasFilename = NO;
2379 bufChanged = curbufIsChanged();
2380 bufHasFilename = curbuf->b_ffname != NULL;
2383 // Temporarily disable flushing since the following code may
2384 // potentially cause multiple redraws.
2385 flushDisabled = YES;
2387 BOOL onlyOneTab = (first_tabpage->tp_next == NULL);
2388 if (WIN_TABS == layout && !onlyOneTab) {
2389 // By going to the last tabpage we ensure that the new tabs
2390 // will appear last (if this call is left out, the taborder
2395 // Make sure we're in normal mode first.
2396 [self addInput:@"<C-\\><C-N>"];
2399 // With "split layout" we open a new tab before opening
2400 // multiple files if the current tab has more than one window
2401 // or if there is exactly one window but whose buffer has a
2402 // filename. (The :drop command ensures modified buffers get
2403 // their own window.)
2404 if ((WIN_HOR == layout || WIN_VER == layout) &&
2405 (!oneWindowInTab || bufHasFilename))
2406 [self addInput:@":tabnew<CR>"];
2408 // The files are opened by constructing a ":drop ..." command
2409 // and executing it.
2410 NSMutableString *cmd = (WIN_TABS == layout)
2411 ? [NSMutableString stringWithString:@":tab drop"]
2412 : [NSMutableString stringWithString:@":drop"];
2414 for (i = 0; i < numFiles; ++i) {
2415 NSString *file = [filenames objectAtIndex:i];
2416 file = [file stringByEscapingSpecialFilenameCharacters];
2417 [cmd appendString:@" "];
2418 [cmd appendString:file];
2421 // Temporarily clear 'suffixes' so that the files are opened in
2422 // the same order as they appear in the "filenames" array.
2423 [self addInput:@":let mvim_oldsu=&su|set su=<CR>"];
2425 [self addInput:cmd];
2427 // Split the view into multiple windows if requested.
2428 if (WIN_HOR == layout)
2429 [self addInput:@"|sall"];
2430 else if (WIN_VER == layout)
2431 [self addInput:@"|vert sall"];
2433 // Restore the old value of 'suffixes'.
2434 [self addInput:@"|let &su=mvim_oldsu|unlet mvim_oldsu<CR>"];
2436 // When opening one file we try to reuse the current window,
2437 // but not if its buffer is modified or has a filename.
2438 // However, the 'arglist' layout always opens the file in the
2440 NSString *file = [[filenames lastObject]
2441 stringByEscapingSpecialFilenameCharacters];
2443 if (WIN_HOR == layout) {
2444 if (!(bufHasFilename || bufChanged))
2445 cmd = [NSString stringWithFormat:@":e %@", file];
2447 cmd = [NSString stringWithFormat:@":sp %@", file];
2448 } else if (WIN_VER == layout) {
2449 if (!(bufHasFilename || bufChanged))
2450 cmd = [NSString stringWithFormat:@":e %@", file];
2452 cmd = [NSString stringWithFormat:@":vsp %@", file];
2453 } else if (WIN_TABS == layout) {
2454 if (oneWindowInTab && !(bufHasFilename || bufChanged))
2455 cmd = [NSString stringWithFormat:@":e %@", file];
2457 cmd = [NSString stringWithFormat:@":tabe %@", file];
2459 // (The :drop command will split if there is a modified
2461 cmd = [NSString stringWithFormat:@":drop %@", file];
2464 [self addInput:cmd];
2465 [self addInput:@"<CR>"];
2468 // Force screen redraw (does it have to be this complicated?).
2469 // (This code was taken from the end of gui_handle_drop().)
2470 update_screen(NOT_VALID);
2473 gui_update_cursor(FALSE, FALSE);
2480 if ([args objectForKey:@"remoteID"]) {
2481 // NOTE: We have to delay processing any ODB related arguments since
2482 // the file(s) may not be opened until the input buffer is processed.
2483 [self performSelectorOnMainThread:@selector(startOdbEditWithArguments:)
2488 NSString *lineString = [args objectForKey:@"cursorLine"];
2489 if (lineString && [lineString intValue] > 0) {
2490 NSString *columnString = [args objectForKey:@"cursorColumn"];
2491 if (!(columnString && [columnString intValue] > 0))
2492 columnString = @"1";
2494 NSString *cmd = [NSString stringWithFormat:@"<C-\\><C-N>:cal "
2495 "cursor(%@,%@)|norm! zz<CR>:f<CR>", lineString, columnString];
2496 [self addInput:cmd];
2499 NSString *rangeString = [args objectForKey:@"selectionRange"];
2501 // Build a command line string that will select the given range of
2502 // lines. If range.length == 0, then position the cursor on the given
2503 // line but do not select.
2504 NSRange range = NSRangeFromString(rangeString);
2506 if (range.length > 0) {
2507 cmd = [NSString stringWithFormat:@"<C-\\><C-N>%dGV%dGz.0",
2508 NSMaxRange(range), range.location];
2510 cmd = [NSString stringWithFormat:@"<C-\\><C-N>%dGz.0",
2514 [self addInput:cmd];
2517 NSString *searchText = [args objectForKey:@"searchText"];
2519 [self addInput:[NSString stringWithFormat:@"<C-\\><C-N>gg/\\c%@/e<CR>",
2524 - (BOOL)checkForModifiedBuffers
2527 for (buf = firstbuf; buf != NULL; buf = buf->b_next) {
2528 if (bufIsChanged(buf)) {
2536 - (void)addInput:(NSString *)input
2538 // NOTE: This code is essentially identical to server_to_input_buf(),
2539 // except the 'silent' flag is TRUE in the call to ins_typebuf() below.
2540 char_u *string = [input vimStringSave];
2541 if (!string) return;
2543 /* Set 'cpoptions' the way we want it.
2544 * B set - backslashes are *not* treated specially
2545 * k set - keycodes are *not* reverse-engineered
2546 * < unset - <Key> sequences *are* interpreted
2547 * The last but one parameter of replace_termcodes() is TRUE so that the
2548 * <lt> sequence is recognised - needed for a real backslash.
2551 char_u *cpo_save = p_cpo;
2552 p_cpo = (char_u *)"Bk";
2553 char_u *str = replace_termcodes((char_u *)string, &ptr, FALSE, TRUE, FALSE);
2556 if (*ptr != NUL) /* trailing CTRL-V results in nothing */
2559 * Add the string to the input stream.
2560 * Can't use add_to_input_buf() here, we now have K_SPECIAL bytes.
2562 * First clear typed characters from the typeahead buffer, there could
2563 * be half a mapping there. Then append to the existing string, so
2564 * that multiple commands from a client are concatenated.
2566 if (typebuf.tb_maplen < typebuf.tb_len)
2567 del_typebuf(typebuf.tb_len - typebuf.tb_maplen, typebuf.tb_maplen);
2568 (void)ins_typebuf(str, REMAP_NONE, typebuf.tb_len, TRUE, TRUE);
2570 /* Let input_available() know we inserted text in the typeahead
2572 typebuf_was_filled = TRUE;
2578 - (BOOL)unusedEditor
2580 BOOL oneWindowInTab = topframe ? YES
2581 : (topframe->fr_layout == FR_LEAF);
2582 BOOL bufChanged = NO;
2583 BOOL bufHasFilename = NO;
2585 bufChanged = curbufIsChanged();
2586 bufHasFilename = curbuf->b_ffname != NULL;
2589 BOOL onlyOneTab = (first_tabpage->tp_next == NULL);
2591 return onlyOneTab && oneWindowInTab && !bufChanged && !bufHasFilename;
2594 - (void)redrawScreen
2596 // Force screen redraw (does it have to be this complicated?).
2597 redraw_all_later(CLEAR);
2598 update_screen(NOT_VALID);
2601 gui_update_cursor(FALSE, FALSE);
2603 // HACK! The cursor is not put back at the command line by the above
2604 // "redraw commands". The following test seems to do the trick though.
2605 if (State & CMDLINE)
2609 - (void)handleFindReplace:(NSDictionary *)args
2613 NSString *findString = [args objectForKey:@"find"];
2614 if (!findString) return;
2616 char_u *find = [findString vimStringSave];
2617 char_u *replace = [[args objectForKey:@"replace"] vimStringSave];
2618 int flags = [[args objectForKey:@"flags"] intValue];
2620 // NOTE: The flag 0x100 is used to indicate a backward search.
2621 gui_do_findrepl(flags, find, replace, (flags & 0x100) == 0);
2627 @end // MMBackend (Private)
2632 @implementation MMBackend (ClientServer)
2634 - (NSString *)connectionNameFromServerName:(NSString *)name
2636 NSString *bundlePath = [[NSBundle mainBundle] bundlePath];
2638 return [[NSString stringWithFormat:@"%@.%@", bundlePath, name]
2642 - (NSConnection *)connectionForServerName:(NSString *)name
2644 // TODO: Try 'name%d' if 'name' fails.
2645 NSString *connName = [self connectionNameFromServerName:name];
2646 NSConnection *svrConn = [connectionNameDict objectForKey:connName];
2649 svrConn = [NSConnection connectionWithRegisteredName:connName
2651 // Try alternate server...
2652 if (!svrConn && alternateServerName) {
2653 //NSLog(@" trying to connect to alternate server: %@",
2654 // alternateServerName);
2655 connName = [self connectionNameFromServerName:alternateServerName];
2656 svrConn = [NSConnection connectionWithRegisteredName:connName
2660 // Try looking for alternate servers...
2662 //NSLog(@" looking for alternate servers...");
2663 NSString *alt = [self alternateServerNameForName:name];
2664 if (alt != alternateServerName) {
2665 //NSLog(@" found alternate server: %@", string);
2666 [alternateServerName release];
2667 alternateServerName = [alt copy];
2671 // Try alternate server again...
2672 if (!svrConn && alternateServerName) {
2673 //NSLog(@" trying to connect to alternate server: %@",
2674 // alternateServerName);
2675 connName = [self connectionNameFromServerName:alternateServerName];
2676 svrConn = [NSConnection connectionWithRegisteredName:connName
2681 [connectionNameDict setObject:svrConn forKey:connName];
2683 //NSLog(@"Adding %@ as connection observer for %@", self, svrConn);
2684 [[NSNotificationCenter defaultCenter] addObserver:self
2685 selector:@selector(serverConnectionDidDie:)
2686 name:NSConnectionDidDieNotification object:svrConn];
2693 - (NSConnection *)connectionForServerPort:(int)port
2696 NSEnumerator *e = [connectionNameDict objectEnumerator];
2698 while ((conn = [e nextObject])) {
2699 // HACK! Assume connection uses mach ports.
2700 if (port == [(NSMachPort*)[conn sendPort] machPort])
2707 - (void)serverConnectionDidDie:(NSNotification *)notification
2709 //NSLog(@"%s%@", _cmd, notification);
2711 NSConnection *svrConn = [notification object];
2713 //NSLog(@"Removing %@ as connection observer from %@", self, svrConn);
2714 [[NSNotificationCenter defaultCenter]
2716 name:NSConnectionDidDieNotification
2719 [connectionNameDict removeObjectsForKeys:
2720 [connectionNameDict allKeysForObject:svrConn]];
2722 // HACK! Assume connection uses mach ports.
2723 int port = [(NSMachPort*)[svrConn sendPort] machPort];
2724 NSNumber *key = [NSNumber numberWithInt:port];
2726 [clientProxyDict removeObjectForKey:key];
2727 [serverReplyDict removeObjectForKey:key];
2730 - (void)addClient:(NSDistantObject *)client
2732 NSConnection *conn = [client connectionForProxy];
2733 // HACK! Assume connection uses mach ports.
2734 int port = [(NSMachPort*)[conn sendPort] machPort];
2735 NSNumber *key = [NSNumber numberWithInt:port];
2737 if (![clientProxyDict objectForKey:key]) {
2738 [client setProtocolForProxy:@protocol(MMVimClientProtocol)];
2739 [clientProxyDict setObject:client forKey:key];
2742 // NOTE: 'clientWindow' is a global variable which is used by <client>
2743 clientWindow = port;
2746 - (NSString *)alternateServerNameForName:(NSString *)name
2748 if (!(name && [name length] > 0))
2751 // Only look for alternates if 'name' doesn't end in a digit.
2752 unichar lastChar = [name characterAtIndex:[name length]-1];
2753 if (lastChar >= '0' && lastChar <= '9')
2756 // Look for alternates among all current servers.
2757 NSArray *list = [self serverList];
2758 if (!(list && [list count] > 0))
2761 // Filter out servers starting with 'name' and ending with a number. The
2762 // (?i) pattern ensures that the match is case insensitive.
2763 NSString *pat = [NSString stringWithFormat:@"(?i)%@[0-9]+\\z", name];
2764 NSPredicate *pred = [NSPredicate predicateWithFormat:
2765 @"SELF MATCHES %@", pat];
2766 list = [list filteredArrayUsingPredicate:pred];
2767 if ([list count] > 0) {
2768 list = [list sortedArrayUsingSelector:@selector(serverNameCompare:)];
2769 return [list objectAtIndex:0];
2775 @end // MMBackend (ClientServer)
2780 @implementation NSString (MMServerNameCompare)
2781 - (NSComparisonResult)serverNameCompare:(NSString *)string
2783 return [self compare:string
2784 options:NSCaseInsensitiveSearch|NSNumericSearch];
2791 static int eventModifierFlagsToVimModMask(int modifierFlags)
2795 if (modifierFlags & NSShiftKeyMask)
2796 modMask |= MOD_MASK_SHIFT;
2797 if (modifierFlags & NSControlKeyMask)
2798 modMask |= MOD_MASK_CTRL;
2799 if (modifierFlags & NSAlternateKeyMask)
2800 modMask |= MOD_MASK_ALT;
2801 if (modifierFlags & NSCommandKeyMask)
2802 modMask |= MOD_MASK_CMD;
2807 static int eventModifierFlagsToVimMouseModMask(int modifierFlags)
2811 if (modifierFlags & NSShiftKeyMask)
2812 modMask |= MOUSE_SHIFT;
2813 if (modifierFlags & NSControlKeyMask)
2814 modMask |= MOUSE_CTRL;
2815 if (modifierFlags & NSAlternateKeyMask)
2816 modMask |= MOUSE_ALT;
2821 static int eventButtonNumberToVimMouseButton(int buttonNumber)
2823 static int mouseButton[] = { MOUSE_LEFT, MOUSE_RIGHT, MOUSE_MIDDLE };
2825 return (buttonNumber >= 0 && buttonNumber < 3)
2826 ? mouseButton[buttonNumber] : -1;
2831 // This function is modeled after the VimToPython function found in if_python.c
2832 // NB This does a deep copy by value, it does not lookup references like the
2833 // VimToPython function does. This is because I didn't want to deal with the
2834 // retain cycles that this would create, and we can cover 99% of the use cases
2835 // by ignoring it. If we ever switch to using GC in MacVim then this
2836 // functionality can be implemented easily.
2837 static id vimToCocoa(typval_T * tv, int depth)
2843 // Avoid infinite recursion
2848 if (tv->v_type == VAR_STRING) {
2849 char_u * val = tv->vval.v_string;
2850 // val can be NULL if the string is empty
2852 result = [NSString string];
2855 val = CONVERT_TO_UTF8(val);
2857 result = [NSString stringWithUTF8String:(char*)val];
2859 CONVERT_TO_UTF8_FREE(val);
2862 } else if (tv->v_type == VAR_NUMBER) {
2863 // looks like sizeof(varnumber_T) is always <= sizeof(long)
2864 result = [NSNumber numberWithLong:(long)tv->vval.v_number];
2865 } else if (tv->v_type == VAR_LIST) {
2866 list_T * list = tv->vval.v_list;
2869 NSMutableArray * arr = result = [NSMutableArray array];
2872 for (curr = list->lv_first; curr != NULL; curr = curr->li_next) {
2873 newObj = vimToCocoa(&curr->li_tv, depth + 1);
2874 [arr addObject:newObj];
2877 } else if (tv->v_type == VAR_DICT) {
2878 NSMutableDictionary * dict = result = [NSMutableDictionary dictionary];
2880 if (tv->vval.v_dict != NULL) {
2881 hashtab_T * ht = &tv->vval.v_dict->dv_hashtab;
2882 int todo = ht->ht_used;
2886 for (hi = ht->ht_array; todo > 0; ++hi) {
2887 if (!HASHITEM_EMPTY(hi)) {
2890 di = dict_lookup(hi);
2891 newObj = vimToCocoa(&di->di_tv, depth + 1);
2893 char_u * keyval = hi->hi_key;
2895 keyval = CONVERT_TO_UTF8(keyval);
2897 NSString * key = [NSString stringWithUTF8String:(char*)keyval];
2899 CONVERT_TO_UTF8_FREE(keyval);
2901 [dict setObject:newObj forKey:key];
2905 } else { // only func refs should fall into this category?
2913 // This function is modeled after eval_client_expr_to_string found in main.c
2914 // Returns nil if there was an error evaluating the expression, and writes a
2915 // message to errorStr.
2916 // TODO Get the error that occurred while evaluating the expression in vim
2918 static id evalExprCocoa(NSString * expr, NSString ** errstr)
2921 char_u *s = (char_u*)[expr UTF8String];
2924 s = CONVERT_FROM_UTF8(s);
2927 int save_dbl = debug_break_level;
2928 int save_ro = redir_off;
2930 debug_break_level = -1;
2934 typval_T * tvres = eval_expr(s, NULL);
2936 debug_break_level = save_dbl;
2937 redir_off = save_ro;
2944 CONVERT_FROM_UTF8_FREE(s);
2949 gui_update_cursor(FALSE, FALSE);
2952 if (tvres == NULL) {
2954 *errstr = @"Expression evaluation failed.";
2957 id res = vimToCocoa(tvres, 1);
2962 *errstr = @"Conversion to cocoa values failed.";
2970 @implementation NSString (VimStrings)
2972 + (id)stringWithVimString:(char_u *)s
2974 // This method ensures a non-nil string is returned. If 's' cannot be
2975 // converted to a utf-8 string it is assumed to be latin-1. If conversion
2976 // still fails an empty NSString is returned.
2977 NSString *string = nil;
2980 s = CONVERT_TO_UTF8(s);
2982 string = [NSString stringWithUTF8String:(char*)s];
2984 // HACK! Apparently 's' is not a valid utf-8 string, maybe it is
2986 string = [NSString stringWithCString:(char*)s
2987 encoding:NSISOLatin1StringEncoding];
2990 CONVERT_TO_UTF8_FREE(s);
2994 return string != nil ? string : [NSString string];
2997 - (char_u *)vimStringSave
2999 char_u *s = (char_u*)[self UTF8String], *ret = NULL;
3002 s = CONVERT_FROM_UTF8(s);
3004 ret = vim_strsave(s);
3006 CONVERT_FROM_UTF8_FREE(s);
3012 @end // NSString (VimStrings)