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 float pointSize = *((float*)bytes); bytes += sizeof(float);
2163 //unsigned len = *((unsigned*)bytes); bytes += sizeof(unsigned);
2164 bytes += sizeof(unsigned); // len not used
2166 NSMutableString *name = [NSMutableString stringWithUTF8String:bytes];
2167 [name appendString:[NSString stringWithFormat:@":h%d", (int)pointSize]];
2168 char_u *s = (char_u*)[name UTF8String];
2171 s = CONVERT_FROM_UTF8(s);
2174 set_option_value((char_u*)"guifont", 0, s, 0);
2177 CONVERT_FROM_UTF8_FREE(s);
2180 [self redrawScreen];
2183 - (void)handleDropFiles:(NSData *)data
2185 // TODO: Get rid of this method; instead use Vim script directly. At the
2186 // moment I know how to do this to open files in tabs, but I'm not sure how
2187 // to add the filenames to the command line when in command line mode.
2191 NSMutableDictionary *args = [NSMutableDictionary dictionaryWithData:data];
2194 id obj = [args objectForKey:@"forceOpen"];
2195 BOOL forceOpen = YES;
2197 forceOpen = [obj boolValue];
2199 NSArray *filenames = [args objectForKey:@"filenames"];
2200 if (!(filenames && [filenames count] > 0)) return;
2203 if (!forceOpen && (State & CMDLINE)) {
2204 // HACK! If Vim is in command line mode then the files names
2205 // should be added to the command line, instead of opening the
2206 // files in tabs (unless forceOpen is set). This is taken care of by
2207 // gui_handle_drop().
2208 int n = [filenames count];
2209 char_u **fnames = (char_u **)alloc(n * sizeof(char_u *));
2212 for (i = 0; i < n; ++i)
2213 fnames[i] = [[filenames objectAtIndex:i] vimStringSave];
2215 // NOTE! This function will free 'fnames'.
2216 // HACK! It is assumed that the 'x' and 'y' arguments are
2217 // unused when in command line mode.
2218 gui_handle_drop(0, 0, 0, fnames, n);
2223 [self handleOpenWithArguments:args];
2227 - (void)handleDropString:(NSData *)data
2232 char_u dropkey[3] = { CSI, KS_EXTRA, (char_u)KE_DROP };
2233 const void *bytes = [data bytes];
2234 int len = *((int*)bytes); bytes += sizeof(int);
2235 NSMutableString *string = [NSMutableString stringWithUTF8String:bytes];
2237 // Replace unrecognized end-of-line sequences with \x0a (line feed).
2238 NSRange range = { 0, [string length] };
2239 unsigned n = [string replaceOccurrencesOfString:@"\x0d\x0a"
2240 withString:@"\x0a" options:0
2243 n = [string replaceOccurrencesOfString:@"\x0d" withString:@"\x0a"
2244 options:0 range:range];
2247 len = [string lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
2248 char_u *s = (char_u*)[string UTF8String];
2250 if (input_conv.vc_type != CONV_NONE)
2251 s = string_convert(&input_conv, s, &len);
2253 dnd_yank_drag_data(s, len);
2255 if (input_conv.vc_type != CONV_NONE)
2258 add_to_input_buf(dropkey, sizeof(dropkey));
2262 - (void)startOdbEditWithArguments:(NSDictionary *)args
2264 #ifdef FEAT_ODB_EDITOR
2265 id obj = [args objectForKey:@"remoteID"];
2268 OSType serverID = [obj unsignedIntValue];
2269 NSString *remotePath = [args objectForKey:@"remotePath"];
2271 NSAppleEventDescriptor *token = nil;
2272 NSData *tokenData = [args objectForKey:@"remoteTokenData"];
2273 obj = [args objectForKey:@"remoteTokenDescType"];
2274 if (tokenData && obj) {
2275 DescType tokenType = [obj unsignedLongValue];
2276 token = [NSAppleEventDescriptor descriptorWithDescriptorType:tokenType
2280 NSArray *filenames = [args objectForKey:@"filenames"];
2281 unsigned i, numFiles = [filenames count];
2282 for (i = 0; i < numFiles; ++i) {
2283 NSString *filename = [filenames objectAtIndex:i];
2284 char_u *s = [filename vimStringSave];
2285 buf_T *buf = buflist_findname(s);
2289 if (buf->b_odb_token) {
2290 [(NSAppleEventDescriptor*)(buf->b_odb_token) release];
2291 buf->b_odb_token = NULL;
2294 if (buf->b_odb_fname) {
2295 vim_free(buf->b_odb_fname);
2296 buf->b_odb_fname = NULL;
2299 buf->b_odb_server_id = serverID;
2302 buf->b_odb_token = [token retain];
2304 buf->b_odb_fname = [remotePath vimStringSave];
2306 NSLog(@"WARNING: Could not find buffer '%@' for ODB editing.",
2310 #endif // FEAT_ODB_EDITOR
2313 - (void)handleXcodeMod:(NSData *)data
2316 const void *bytes = [data bytes];
2317 DescType type = *((DescType*)bytes); bytes += sizeof(DescType);
2318 unsigned len = *((unsigned*)bytes); bytes += sizeof(unsigned);
2322 NSAppleEventDescriptor *replyEvent = [NSAppleEventDescriptor
2323 descriptorWithDescriptorType:type
2329 - (void)handleOpenWithArguments:(NSDictionary *)args
2331 // ARGUMENT: DESCRIPTION:
2332 // -------------------------------------------------------------
2333 // filenames list of filenames
2334 // dontOpen don't open files specified in above argument
2335 // layout which layout to use to open files
2336 // selectionRange range of lines to select
2337 // searchText string to search for
2338 // cursorLine line to position the cursor on
2339 // cursorColumn column to position the cursor on
2340 // (only valid when "cursorLine" is set)
2341 // remoteID ODB parameter
2342 // remotePath ODB parameter
2343 // remoteTokenDescType ODB parameter
2344 // remoteTokenData ODB parameter
2346 //NSLog(@"%s%@ (starting=%d)", _cmd, args, starting);
2348 NSArray *filenames = [args objectForKey:@"filenames"];
2349 int i, numFiles = filenames ? [filenames count] : 0;
2350 BOOL openFiles = ![[args objectForKey:@"dontOpen"] boolValue];
2351 int layout = [[args objectForKey:@"layout"] intValue];
2353 // Change to directory of first file to open if this is an "unused" editor
2354 // (but do not do this if editing remotely).
2355 if (openFiles && numFiles > 0 && ![args objectForKey:@"remoteID"]
2356 && (starting || [self unusedEditor]) ) {
2357 char_u *s = [[filenames objectAtIndex:0] vimStringSave];
2363 // When Vim is starting we simply add the files to be opened to the
2364 // global arglist and Vim will take care of opening them for us.
2365 if (openFiles && numFiles > 0) {
2366 for (i = 0; i < numFiles; i++) {
2367 NSString *fname = [filenames objectAtIndex:i];
2370 if (ga_grow(&global_alist.al_ga, 1) == FAIL
2371 || (p = [fname vimStringSave]) == NULL)
2372 exit(2); // See comment in -[MMBackend exit]
2374 alist_add(&global_alist, p, 2);
2377 // Vim will take care of arranging the files added to the arglist
2378 // in windows or tabs; all we must do is to specify which layout to
2380 initialWindowLayout = layout;
2383 // When Vim is already open we resort to some trickery to open the
2384 // files with the specified layout.
2386 // TODO: Figure out a better way to handle this?
2387 if (openFiles && numFiles > 0) {
2388 BOOL oneWindowInTab = topframe ? YES
2389 : (topframe->fr_layout == FR_LEAF);
2390 BOOL bufChanged = NO;
2391 BOOL bufHasFilename = NO;
2393 bufChanged = curbufIsChanged();
2394 bufHasFilename = curbuf->b_ffname != NULL;
2397 // Temporarily disable flushing since the following code may
2398 // potentially cause multiple redraws.
2399 flushDisabled = YES;
2401 BOOL onlyOneTab = (first_tabpage->tp_next == NULL);
2402 if (WIN_TABS == layout && !onlyOneTab) {
2403 // By going to the last tabpage we ensure that the new tabs
2404 // will appear last (if this call is left out, the taborder
2409 // Make sure we're in normal mode first.
2410 [self addInput:@"<C-\\><C-N>"];
2413 // With "split layout" we open a new tab before opening
2414 // multiple files if the current tab has more than one window
2415 // or if there is exactly one window but whose buffer has a
2416 // filename. (The :drop command ensures modified buffers get
2417 // their own window.)
2418 if ((WIN_HOR == layout || WIN_VER == layout) &&
2419 (!oneWindowInTab || bufHasFilename))
2420 [self addInput:@":tabnew<CR>"];
2422 // The files are opened by constructing a ":drop ..." command
2423 // and executing it.
2424 NSMutableString *cmd = (WIN_TABS == layout)
2425 ? [NSMutableString stringWithString:@":tab drop"]
2426 : [NSMutableString stringWithString:@":drop"];
2428 for (i = 0; i < numFiles; ++i) {
2429 NSString *file = [filenames objectAtIndex:i];
2430 file = [file stringByEscapingSpecialFilenameCharacters];
2431 [cmd appendString:@" "];
2432 [cmd appendString:file];
2435 // Temporarily clear 'suffixes' so that the files are opened in
2436 // the same order as they appear in the "filenames" array.
2437 [self addInput:@":let mvim_oldsu=&su|set su=<CR>"];
2439 [self addInput:cmd];
2441 // Split the view into multiple windows if requested.
2442 if (WIN_HOR == layout)
2443 [self addInput:@"|sall"];
2444 else if (WIN_VER == layout)
2445 [self addInput:@"|vert sall"];
2447 // Restore the old value of 'suffixes'.
2448 [self addInput:@"|let &su=mvim_oldsu|unlet mvim_oldsu<CR>"];
2450 // When opening one file we try to reuse the current window,
2451 // but not if its buffer is modified or has a filename.
2452 // However, the 'arglist' layout always opens the file in the
2454 NSString *file = [[filenames lastObject]
2455 stringByEscapingSpecialFilenameCharacters];
2457 if (WIN_HOR == layout) {
2458 if (!(bufHasFilename || bufChanged))
2459 cmd = [NSString stringWithFormat:@":e %@", file];
2461 cmd = [NSString stringWithFormat:@":sp %@", file];
2462 } else if (WIN_VER == layout) {
2463 if (!(bufHasFilename || bufChanged))
2464 cmd = [NSString stringWithFormat:@":e %@", file];
2466 cmd = [NSString stringWithFormat:@":vsp %@", file];
2467 } else if (WIN_TABS == layout) {
2468 if (oneWindowInTab && !(bufHasFilename || bufChanged))
2469 cmd = [NSString stringWithFormat:@":e %@", file];
2471 cmd = [NSString stringWithFormat:@":tabe %@", file];
2473 // (The :drop command will split if there is a modified
2475 cmd = [NSString stringWithFormat:@":drop %@", file];
2478 [self addInput:cmd];
2479 [self addInput:@"<CR>"];
2482 // Force screen redraw (does it have to be this complicated?).
2483 // (This code was taken from the end of gui_handle_drop().)
2484 update_screen(NOT_VALID);
2487 gui_update_cursor(FALSE, FALSE);
2494 if ([args objectForKey:@"remoteID"]) {
2495 // NOTE: We have to delay processing any ODB related arguments since
2496 // the file(s) may not be opened until the input buffer is processed.
2497 [self performSelector:@selector(startOdbEditWithArguments:)
2502 NSString *lineString = [args objectForKey:@"cursorLine"];
2503 if (lineString && [lineString intValue] > 0) {
2504 NSString *columnString = [args objectForKey:@"cursorColumn"];
2505 if (!(columnString && [columnString intValue] > 0))
2506 columnString = @"1";
2508 NSString *cmd = [NSString stringWithFormat:@"<C-\\><C-N>:cal "
2509 "cursor(%@,%@)|norm! zz<CR>:f<CR>", lineString, columnString];
2510 [self addInput:cmd];
2513 NSString *rangeString = [args objectForKey:@"selectionRange"];
2515 // Build a command line string that will select the given range of
2516 // lines. If range.length == 0, then position the cursor on the given
2517 // line but do not select.
2518 NSRange range = NSRangeFromString(rangeString);
2520 if (range.length > 0) {
2521 cmd = [NSString stringWithFormat:@"<C-\\><C-N>%dGV%dGz.0",
2522 NSMaxRange(range), range.location];
2524 cmd = [NSString stringWithFormat:@"<C-\\><C-N>%dGz.0",
2528 [self addInput:cmd];
2531 NSString *searchText = [args objectForKey:@"searchText"];
2533 [self addInput:[NSString stringWithFormat:@"<C-\\><C-N>gg/\\c%@/e<CR>",
2538 - (BOOL)checkForModifiedBuffers
2541 for (buf = firstbuf; buf != NULL; buf = buf->b_next) {
2542 if (bufIsChanged(buf)) {
2550 - (void)addInput:(NSString *)input
2552 // NOTE: This code is essentially identical to server_to_input_buf(),
2553 // except the 'silent' flag is TRUE in the call to ins_typebuf() below.
2554 char_u *string = [input vimStringSave];
2555 if (!string) return;
2557 /* Set 'cpoptions' the way we want it.
2558 * B set - backslashes are *not* treated specially
2559 * k set - keycodes are *not* reverse-engineered
2560 * < unset - <Key> sequences *are* interpreted
2561 * The last but one parameter of replace_termcodes() is TRUE so that the
2562 * <lt> sequence is recognised - needed for a real backslash.
2565 char_u *cpo_save = p_cpo;
2566 p_cpo = (char_u *)"Bk";
2567 char_u *str = replace_termcodes((char_u *)string, &ptr, FALSE, TRUE, FALSE);
2570 if (*ptr != NUL) /* trailing CTRL-V results in nothing */
2573 * Add the string to the input stream.
2574 * Can't use add_to_input_buf() here, we now have K_SPECIAL bytes.
2576 * First clear typed characters from the typeahead buffer, there could
2577 * be half a mapping there. Then append to the existing string, so
2578 * that multiple commands from a client are concatenated.
2580 if (typebuf.tb_maplen < typebuf.tb_len)
2581 del_typebuf(typebuf.tb_len - typebuf.tb_maplen, typebuf.tb_maplen);
2582 (void)ins_typebuf(str, REMAP_NONE, typebuf.tb_len, TRUE, TRUE);
2584 /* Let input_available() know we inserted text in the typeahead
2586 typebuf_was_filled = TRUE;
2592 - (BOOL)unusedEditor
2594 BOOL oneWindowInTab = topframe ? YES
2595 : (topframe->fr_layout == FR_LEAF);
2596 BOOL bufChanged = NO;
2597 BOOL bufHasFilename = NO;
2599 bufChanged = curbufIsChanged();
2600 bufHasFilename = curbuf->b_ffname != NULL;
2603 BOOL onlyOneTab = (first_tabpage->tp_next == NULL);
2605 return onlyOneTab && oneWindowInTab && !bufChanged && !bufHasFilename;
2608 - (void)redrawScreen
2610 // Force screen redraw (does it have to be this complicated?).
2611 redraw_all_later(CLEAR);
2612 update_screen(NOT_VALID);
2615 gui_update_cursor(FALSE, FALSE);
2617 // HACK! The cursor is not put back at the command line by the above
2618 // "redraw commands". The following test seems to do the trick though.
2619 if (State & CMDLINE)
2623 - (void)handleFindReplace:(NSDictionary *)args
2627 NSString *findString = [args objectForKey:@"find"];
2628 if (!findString) return;
2630 char_u *find = [findString vimStringSave];
2631 char_u *replace = [[args objectForKey:@"replace"] vimStringSave];
2632 int flags = [[args objectForKey:@"flags"] intValue];
2634 // NOTE: The flag 0x100 is used to indicate a backward search.
2635 gui_do_findrepl(flags, find, replace, (flags & 0x100) == 0);
2641 @end // MMBackend (Private)
2646 @implementation MMBackend (ClientServer)
2648 - (NSString *)connectionNameFromServerName:(NSString *)name
2650 NSString *bundlePath = [[NSBundle mainBundle] bundlePath];
2652 return [[NSString stringWithFormat:@"%@.%@", bundlePath, name]
2656 - (NSConnection *)connectionForServerName:(NSString *)name
2658 // TODO: Try 'name%d' if 'name' fails.
2659 NSString *connName = [self connectionNameFromServerName:name];
2660 NSConnection *svrConn = [connectionNameDict objectForKey:connName];
2663 svrConn = [NSConnection connectionWithRegisteredName:connName
2665 // Try alternate server...
2666 if (!svrConn && alternateServerName) {
2667 //NSLog(@" trying to connect to alternate server: %@",
2668 // alternateServerName);
2669 connName = [self connectionNameFromServerName:alternateServerName];
2670 svrConn = [NSConnection connectionWithRegisteredName:connName
2674 // Try looking for alternate servers...
2676 //NSLog(@" looking for alternate servers...");
2677 NSString *alt = [self alternateServerNameForName:name];
2678 if (alt != alternateServerName) {
2679 //NSLog(@" found alternate server: %@", string);
2680 [alternateServerName release];
2681 alternateServerName = [alt copy];
2685 // Try alternate server again...
2686 if (!svrConn && alternateServerName) {
2687 //NSLog(@" trying to connect to alternate server: %@",
2688 // alternateServerName);
2689 connName = [self connectionNameFromServerName:alternateServerName];
2690 svrConn = [NSConnection connectionWithRegisteredName:connName
2695 [connectionNameDict setObject:svrConn forKey:connName];
2697 //NSLog(@"Adding %@ as connection observer for %@", self, svrConn);
2698 [[NSNotificationCenter defaultCenter] addObserver:self
2699 selector:@selector(serverConnectionDidDie:)
2700 name:NSConnectionDidDieNotification object:svrConn];
2707 - (NSConnection *)connectionForServerPort:(int)port
2710 NSEnumerator *e = [connectionNameDict objectEnumerator];
2712 while ((conn = [e nextObject])) {
2713 // HACK! Assume connection uses mach ports.
2714 if (port == [(NSMachPort*)[conn sendPort] machPort])
2721 - (void)serverConnectionDidDie:(NSNotification *)notification
2723 //NSLog(@"%s%@", _cmd, notification);
2725 NSConnection *svrConn = [notification object];
2727 //NSLog(@"Removing %@ as connection observer from %@", self, svrConn);
2728 [[NSNotificationCenter defaultCenter]
2730 name:NSConnectionDidDieNotification
2733 [connectionNameDict removeObjectsForKeys:
2734 [connectionNameDict allKeysForObject:svrConn]];
2736 // HACK! Assume connection uses mach ports.
2737 int port = [(NSMachPort*)[svrConn sendPort] machPort];
2738 NSNumber *key = [NSNumber numberWithInt:port];
2740 [clientProxyDict removeObjectForKey:key];
2741 [serverReplyDict removeObjectForKey:key];
2744 - (void)addClient:(NSDistantObject *)client
2746 NSConnection *conn = [client connectionForProxy];
2747 // HACK! Assume connection uses mach ports.
2748 int port = [(NSMachPort*)[conn sendPort] machPort];
2749 NSNumber *key = [NSNumber numberWithInt:port];
2751 if (![clientProxyDict objectForKey:key]) {
2752 [client setProtocolForProxy:@protocol(MMVimClientProtocol)];
2753 [clientProxyDict setObject:client forKey:key];
2756 // NOTE: 'clientWindow' is a global variable which is used by <client>
2757 clientWindow = port;
2760 - (NSString *)alternateServerNameForName:(NSString *)name
2762 if (!(name && [name length] > 0))
2765 // Only look for alternates if 'name' doesn't end in a digit.
2766 unichar lastChar = [name characterAtIndex:[name length]-1];
2767 if (lastChar >= '0' && lastChar <= '9')
2770 // Look for alternates among all current servers.
2771 NSArray *list = [self serverList];
2772 if (!(list && [list count] > 0))
2775 // Filter out servers starting with 'name' and ending with a number. The
2776 // (?i) pattern ensures that the match is case insensitive.
2777 NSString *pat = [NSString stringWithFormat:@"(?i)%@[0-9]+\\z", name];
2778 NSPredicate *pred = [NSPredicate predicateWithFormat:
2779 @"SELF MATCHES %@", pat];
2780 list = [list filteredArrayUsingPredicate:pred];
2781 if ([list count] > 0) {
2782 list = [list sortedArrayUsingSelector:@selector(serverNameCompare:)];
2783 return [list objectAtIndex:0];
2789 @end // MMBackend (ClientServer)
2794 @implementation NSString (MMServerNameCompare)
2795 - (NSComparisonResult)serverNameCompare:(NSString *)string
2797 return [self compare:string
2798 options:NSCaseInsensitiveSearch|NSNumericSearch];
2805 static int eventModifierFlagsToVimModMask(int modifierFlags)
2809 if (modifierFlags & NSShiftKeyMask)
2810 modMask |= MOD_MASK_SHIFT;
2811 if (modifierFlags & NSControlKeyMask)
2812 modMask |= MOD_MASK_CTRL;
2813 if (modifierFlags & NSAlternateKeyMask)
2814 modMask |= MOD_MASK_ALT;
2815 if (modifierFlags & NSCommandKeyMask)
2816 modMask |= MOD_MASK_CMD;
2821 static int eventModifierFlagsToVimMouseModMask(int modifierFlags)
2825 if (modifierFlags & NSShiftKeyMask)
2826 modMask |= MOUSE_SHIFT;
2827 if (modifierFlags & NSControlKeyMask)
2828 modMask |= MOUSE_CTRL;
2829 if (modifierFlags & NSAlternateKeyMask)
2830 modMask |= MOUSE_ALT;
2835 static int eventButtonNumberToVimMouseButton(int buttonNumber)
2837 static int mouseButton[] = { MOUSE_LEFT, MOUSE_RIGHT, MOUSE_MIDDLE };
2839 return (buttonNumber >= 0 && buttonNumber < 3)
2840 ? mouseButton[buttonNumber] : -1;
2845 // This function is modeled after the VimToPython function found in if_python.c
2846 // NB This does a deep copy by value, it does not lookup references like the
2847 // VimToPython function does. This is because I didn't want to deal with the
2848 // retain cycles that this would create, and we can cover 99% of the use cases
2849 // by ignoring it. If we ever switch to using GC in MacVim then this
2850 // functionality can be implemented easily.
2851 static id vimToCocoa(typval_T * tv, int depth)
2857 // Avoid infinite recursion
2862 if (tv->v_type == VAR_STRING) {
2863 char_u * val = tv->vval.v_string;
2864 // val can be NULL if the string is empty
2866 result = [NSString string];
2869 val = CONVERT_TO_UTF8(val);
2871 result = [NSString stringWithUTF8String:(char*)val];
2873 CONVERT_TO_UTF8_FREE(val);
2876 } else if (tv->v_type == VAR_NUMBER) {
2877 // looks like sizeof(varnumber_T) is always <= sizeof(long)
2878 result = [NSNumber numberWithLong:(long)tv->vval.v_number];
2879 } else if (tv->v_type == VAR_LIST) {
2880 list_T * list = tv->vval.v_list;
2883 NSMutableArray * arr = result = [NSMutableArray array];
2886 for (curr = list->lv_first; curr != NULL; curr = curr->li_next) {
2887 newObj = vimToCocoa(&curr->li_tv, depth + 1);
2888 [arr addObject:newObj];
2891 } else if (tv->v_type == VAR_DICT) {
2892 NSMutableDictionary * dict = result = [NSMutableDictionary dictionary];
2894 if (tv->vval.v_dict != NULL) {
2895 hashtab_T * ht = &tv->vval.v_dict->dv_hashtab;
2896 int todo = ht->ht_used;
2900 for (hi = ht->ht_array; todo > 0; ++hi) {
2901 if (!HASHITEM_EMPTY(hi)) {
2904 di = dict_lookup(hi);
2905 newObj = vimToCocoa(&di->di_tv, depth + 1);
2907 char_u * keyval = hi->hi_key;
2909 keyval = CONVERT_TO_UTF8(keyval);
2911 NSString * key = [NSString stringWithUTF8String:(char*)keyval];
2913 CONVERT_TO_UTF8_FREE(keyval);
2915 [dict setObject:newObj forKey:key];
2919 } else { // only func refs should fall into this category?
2927 // This function is modeled after eval_client_expr_to_string found in main.c
2928 // Returns nil if there was an error evaluating the expression, and writes a
2929 // message to errorStr.
2930 // TODO Get the error that occurred while evaluating the expression in vim
2932 static id evalExprCocoa(NSString * expr, NSString ** errstr)
2935 char_u *s = (char_u*)[expr UTF8String];
2938 s = CONVERT_FROM_UTF8(s);
2941 int save_dbl = debug_break_level;
2942 int save_ro = redir_off;
2944 debug_break_level = -1;
2948 typval_T * tvres = eval_expr(s, NULL);
2950 debug_break_level = save_dbl;
2951 redir_off = save_ro;
2958 CONVERT_FROM_UTF8_FREE(s);
2963 gui_update_cursor(FALSE, FALSE);
2966 if (tvres == NULL) {
2968 *errstr = @"Expression evaluation failed.";
2971 id res = vimToCocoa(tvres, 1);
2976 *errstr = @"Conversion to cocoa values failed.";
2984 @implementation NSString (VimStrings)
2986 + (id)stringWithVimString:(char_u *)s
2988 // This method ensures a non-nil string is returned. If 's' cannot be
2989 // converted to a utf-8 string it is assumed to be latin-1. If conversion
2990 // still fails an empty NSString is returned.
2991 NSString *string = nil;
2994 s = CONVERT_TO_UTF8(s);
2996 string = [NSString stringWithUTF8String:(char*)s];
2998 // HACK! Apparently 's' is not a valid utf-8 string, maybe it is
3000 string = [NSString stringWithCString:(char*)s
3001 encoding:NSISOLatin1StringEncoding];
3004 CONVERT_TO_UTF8_FREE(s);
3008 return string != nil ? string : [NSString string];
3011 - (char_u *)vimStringSave
3013 char_u *s = (char_u*)[self UTF8String], *ret = NULL;
3016 s = CONVERT_FROM_UTF8(s);
3018 ret = vim_strsave(s);
3020 CONVERT_FROM_UTF8_FREE(s);
3026 @end // NSString (VimStrings)