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 [MMVimController processCommandQueue:].
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)waitForDialogReturn;
86 - (void)insertVimStateMessage;
87 - (void)processInputQueue;
88 - (void)handleInputEvent:(int)msgid data:(NSData *)data;
89 + (NSDictionary *)specialKeys;
90 - (void)handleInsertText:(NSString *)text;
91 - (void)handleKeyDown:(NSString *)key modifiers:(int)mods;
92 - (void)queueMessage:(int)msgid data:(NSData *)data;
93 - (void)connectionDidDie:(NSNotification *)notification;
94 - (void)blinkTimerFired:(NSTimer *)timer;
95 - (void)focusChange:(BOOL)on;
96 - (void)handleToggleToolbar;
97 - (void)handleScrollbarEvent:(NSData *)data;
98 - (void)handleSetFont:(NSData *)data;
99 - (void)handleDropFiles:(NSData *)data;
100 - (void)handleDropString:(NSData *)data;
101 - (void)startOdbEditWithArguments:(NSDictionary *)args;
102 - (void)handleXcodeMod:(NSData *)data;
103 - (void)handleOpenWithArguments:(NSDictionary *)args;
104 - (BOOL)checkForModifiedBuffers;
105 - (void)addInput:(NSString *)input;
106 - (BOOL)unusedEditor;
107 - (void)redrawScreen;
108 - (void)handleFindReplace:(NSDictionary *)args;
113 @interface MMBackend (ClientServer)
114 - (NSString *)connectionNameFromServerName:(NSString *)name;
115 - (NSConnection *)connectionForServerName:(NSString *)name;
116 - (NSConnection *)connectionForServerPort:(int)port;
117 - (void)serverConnectionDidDie:(NSNotification *)notification;
118 - (void)addClient:(NSDistantObject *)client;
119 - (NSString *)alternateServerNameForName:(NSString *)name;
124 @implementation MMBackend
126 + (MMBackend *)sharedInstance
128 static MMBackend *singleton = nil;
129 return singleton ? singleton : (singleton = [MMBackend new]);
135 if (!self) return nil;
137 outputQueue = [[NSMutableArray alloc] init];
138 inputQueue = [[NSMutableArray alloc] init];
139 drawData = [[NSMutableData alloc] initWithCapacity:1024];
140 connectionNameDict = [[NSMutableDictionary alloc] init];
141 clientProxyDict = [[NSMutableDictionary alloc] init];
142 serverReplyDict = [[NSMutableDictionary alloc] init];
144 NSBundle *mainBundle = [NSBundle mainBundle];
145 NSString *path = [mainBundle pathForResource:@"Colors" ofType:@"plist"];
147 colorDict = [[NSDictionary dictionaryWithContentsOfFile:path] retain];
149 path = [mainBundle pathForResource:@"SystemColors" ofType:@"plist"];
151 sysColorDict = [[NSDictionary dictionaryWithContentsOfFile:path]
154 path = [mainBundle pathForResource:@"Actions" ofType:@"plist"];
156 actionDict = [[NSDictionary dictionaryWithContentsOfFile:path] retain];
158 if (!(colorDict && sysColorDict && actionDict))
159 NSLog(@"ERROR: Failed to load dictionaries.%@",
160 MMSymlinkWarningString);
167 //NSLog(@"%@ %s", [self className], _cmd);
168 [[NSNotificationCenter defaultCenter] removeObserver:self];
170 gui_mch_free_font(oldWideFont); oldWideFont = NOFONT;
171 [blinkTimer release]; blinkTimer = nil;
172 [alternateServerName release]; alternateServerName = nil;
173 [serverReplyDict release]; serverReplyDict = nil;
174 [clientProxyDict release]; clientProxyDict = nil;
175 [connectionNameDict release]; connectionNameDict = nil;
176 [inputQueue release]; inputQueue = nil;
177 [outputQueue release]; outputQueue = nil;
178 [drawData release]; drawData = nil;
179 [frontendProxy release]; frontendProxy = nil;
180 [connection release]; connection = nil;
181 [actionDict release]; actionDict = nil;
182 [sysColorDict release]; sysColorDict = nil;
183 [colorDict release]; colorDict = nil;
188 - (void)setBackgroundColor:(int)color
190 backgroundColor = MM_COLOR_WITH_TRANSP(color,p_transp);
193 - (void)setForegroundColor:(int)color
195 foregroundColor = MM_COLOR(color);
198 - (void)setSpecialColor:(int)color
200 specialColor = MM_COLOR(color);
203 - (void)setDefaultColorsBackground:(int)bg foreground:(int)fg
205 defaultBackgroundColor = MM_COLOR_WITH_TRANSP(bg,p_transp);
206 defaultForegroundColor = MM_COLOR(fg);
208 NSMutableData *data = [NSMutableData data];
210 [data appendBytes:&defaultBackgroundColor length:sizeof(unsigned)];
211 [data appendBytes:&defaultForegroundColor length:sizeof(unsigned)];
213 [self queueMessage:SetDefaultColorsMsgID data:data];
216 - (NSConnection *)connection
219 // NOTE! If the name of the connection changes here it must also be
220 // updated in MMAppController.m.
221 NSString *name = [NSString stringWithFormat:@"%@-connection",
222 [[NSBundle mainBundle] bundlePath]];
224 connection = [NSConnection connectionWithRegisteredName:name host:nil];
228 // NOTE: 'connection' may be nil here.
232 - (NSDictionary *)actionDict
237 - (int)initialWindowLayout
239 return initialWindowLayout;
242 - (void)queueMessage:(int)msgid properties:(NSDictionary *)props
244 [self queueMessage:msgid data:[props dictionaryAsData]];
249 if (![self connection]) {
251 // This is a preloaded process and as such should not cause the
252 // MacVim to be opened. We probably got here as a result of the
253 // user quitting MacVim while the process was preloading, so exit
255 // (Don't use mch_exit() since it assumes the process has properly
260 NSBundle *mainBundle = [NSBundle mainBundle];
265 // Launch MacVim using Launch Services (NSWorkspace would be nicer, but
266 // the API to pass Apple Event parameters is broken on 10.4).
267 NSString *path = [mainBundle bundlePath];
268 status = FSPathMakeRef((const UInt8 *)[path UTF8String], &ref, NULL);
269 if (noErr == status) {
270 // Pass parameter to the 'Open' Apple Event that tells MacVim not
271 // to open an untitled window.
272 NSAppleEventDescriptor *desc =
273 [NSAppleEventDescriptor recordDescriptor];
274 [desc setParamDescriptor:
275 [NSAppleEventDescriptor descriptorWithBoolean:NO]
276 forKeyword:keyMMUntitledWindow];
278 LSLaunchFSRefSpec spec = { &ref, 0, NULL, [desc aeDesc],
279 kLSLaunchDefaults, NULL };
280 status = LSOpenFromRefSpec(&spec, NULL);
283 if (noErr != status) {
284 NSLog(@"ERROR: Failed to launch MacVim (path=%@).%@",
285 path, MMSymlinkWarningString);
289 // Launch MacVim using NSTask. For some reason the above code using
290 // Launch Services sometimes fails on LSOpenFromRefSpec() (when it
291 // fails, the dock icon starts bouncing and never stops). It seems
292 // like rebuilding the Launch Services database takes care of this
293 // problem, but the NSTask way seems more stable so stick with it.
295 // NOTE! Using NSTask to launch the GUI has the negative side-effect
296 // that the GUI won't be activated (or raised) so there is a hack in
297 // MMAppController which raises the app when a new window is opened.
298 NSMutableArray *args = [NSMutableArray arrayWithObjects:
299 [NSString stringWithFormat:@"-%@", MMNoWindowKey], @"yes", nil];
300 NSString *exeName = [[mainBundle infoDictionary]
301 objectForKey:@"CFBundleExecutable"];
302 NSString *path = [mainBundle pathForAuxiliaryExecutable:exeName];
304 NSLog(@"ERROR: Could not find MacVim executable in bundle.%@",
305 MMSymlinkWarningString);
309 [NSTask launchedTaskWithLaunchPath:path arguments:args];
312 // HACK! Poll the mach bootstrap server until it returns a valid
313 // connection to detect that MacVim has finished launching. Also set a
314 // time-out date so that we don't get stuck doing this forever.
315 NSDate *timeOutDate = [NSDate dateWithTimeIntervalSinceNow:10];
316 while (![self connection] &&
317 NSOrderedDescending == [timeOutDate compare:[NSDate date]])
318 [[NSRunLoop currentRunLoop]
319 runMode:NSDefaultRunLoopMode
320 beforeDate:[NSDate dateWithTimeIntervalSinceNow:.1]];
322 // NOTE: [self connection] will set 'connection' as a side-effect.
324 NSLog(@"WARNING: Timed-out waiting for GUI to launch.");
331 [[NSNotificationCenter defaultCenter] addObserver:self
332 selector:@selector(connectionDidDie:)
333 name:NSConnectionDidDieNotification object:connection];
335 id proxy = [connection rootProxy];
336 [proxy setProtocolForProxy:@protocol(MMAppProtocol)];
338 int pid = [[NSProcessInfo processInfo] processIdentifier];
340 frontendProxy = [proxy connectBackend:self pid:pid];
342 [frontendProxy retain];
343 [frontendProxy setProtocolForProxy:@protocol(MMAppProtocol)];
347 @catch (NSException *e) {
348 NSLog(@"Exception caught when trying to connect backend: \"%@\"", e);
354 - (BOOL)openGUIWindow
356 [self queueMessage:OpenWindowMsgID data:nil];
362 int type = ClearAllDrawType;
364 // Any draw commands in queue are effectively obsolete since this clearAll
365 // will negate any effect they have, therefore we may as well clear the
367 [drawData setLength:0];
369 [drawData appendBytes:&type length:sizeof(int)];
372 - (void)clearBlockFromRow:(int)row1 column:(int)col1
373 toRow:(int)row2 column:(int)col2
375 int type = ClearBlockDrawType;
377 [drawData appendBytes:&type length:sizeof(int)];
379 [drawData appendBytes:&defaultBackgroundColor length:sizeof(unsigned)];
380 [drawData appendBytes:&row1 length:sizeof(int)];
381 [drawData appendBytes:&col1 length:sizeof(int)];
382 [drawData appendBytes:&row2 length:sizeof(int)];
383 [drawData appendBytes:&col2 length:sizeof(int)];
386 - (void)deleteLinesFromRow:(int)row count:(int)count
387 scrollBottom:(int)bottom left:(int)left right:(int)right
389 int type = DeleteLinesDrawType;
391 [drawData appendBytes:&type length:sizeof(int)];
393 [drawData appendBytes:&defaultBackgroundColor length:sizeof(unsigned)];
394 [drawData appendBytes:&row length:sizeof(int)];
395 [drawData appendBytes:&count length:sizeof(int)];
396 [drawData appendBytes:&bottom length:sizeof(int)];
397 [drawData appendBytes:&left length:sizeof(int)];
398 [drawData appendBytes:&right length:sizeof(int)];
401 - (void)drawString:(char*)s length:(int)len row:(int)row column:(int)col
402 cells:(int)cells flags:(int)flags
404 if (len <= 0 || cells <= 0) return;
406 int type = DrawStringDrawType;
408 [drawData appendBytes:&type length:sizeof(int)];
410 [drawData appendBytes:&backgroundColor length:sizeof(unsigned)];
411 [drawData appendBytes:&foregroundColor length:sizeof(unsigned)];
412 [drawData appendBytes:&specialColor length:sizeof(unsigned)];
413 [drawData appendBytes:&row length:sizeof(int)];
414 [drawData appendBytes:&col length:sizeof(int)];
415 [drawData appendBytes:&cells length:sizeof(int)];
416 [drawData appendBytes:&flags length:sizeof(int)];
417 [drawData appendBytes:&len length:sizeof(int)];
418 [drawData appendBytes:s length:len];
421 - (void)insertLinesFromRow:(int)row count:(int)count
422 scrollBottom:(int)bottom left:(int)left right:(int)right
424 int type = InsertLinesDrawType;
426 [drawData appendBytes:&type length:sizeof(int)];
428 [drawData appendBytes:&defaultBackgroundColor length:sizeof(unsigned)];
429 [drawData appendBytes:&row length:sizeof(int)];
430 [drawData appendBytes:&count length:sizeof(int)];
431 [drawData appendBytes:&bottom length:sizeof(int)];
432 [drawData appendBytes:&left length:sizeof(int)];
433 [drawData appendBytes:&right length:sizeof(int)];
436 - (void)drawCursorAtRow:(int)row column:(int)col shape:(int)shape
437 fraction:(int)percent color:(int)color
439 int type = DrawCursorDrawType;
440 unsigned uc = MM_COLOR(color);
442 [drawData appendBytes:&type length:sizeof(int)];
444 [drawData appendBytes:&uc length:sizeof(unsigned)];
445 [drawData appendBytes:&row length:sizeof(int)];
446 [drawData appendBytes:&col length:sizeof(int)];
447 [drawData appendBytes:&shape length:sizeof(int)];
448 [drawData appendBytes:&percent length:sizeof(int)];
451 - (void)drawInvertedRectAtRow:(int)row column:(int)col numRows:(int)nr
452 numColumns:(int)nc invert:(int)invert
454 int type = DrawInvertedRectDrawType;
455 [drawData appendBytes:&type length:sizeof(int)];
457 [drawData appendBytes:&row length:sizeof(int)];
458 [drawData appendBytes:&col length:sizeof(int)];
459 [drawData appendBytes:&nr length:sizeof(int)];
460 [drawData appendBytes:&nc length:sizeof(int)];
461 [drawData appendBytes:&invert length:sizeof(int)];
466 // Keep running the run-loop until there is no more input to process.
467 while (CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0, true)
468 == kCFRunLoopRunHandledSource)
472 - (void)flushQueue:(BOOL)force
474 // NOTE: This variable allows for better control over when the queue is
475 // flushed. It can be set to YES at the beginning of a sequence of calls
476 // that may potentially add items to the queue, and then restored back to
478 if (flushDisabled) return;
480 if ([drawData length] > 0) {
481 // HACK! Detect changes to 'guifontwide'.
482 if (gui.wide_font != oldWideFont) {
483 gui_mch_free_font(oldWideFont);
484 oldWideFont = gui_mch_retain_font(gui.wide_font);
485 [self setFont:oldWideFont wide:YES];
488 int type = SetCursorPosDrawType;
489 [drawData appendBytes:&type length:sizeof(type)];
490 [drawData appendBytes:&gui.row length:sizeof(gui.row)];
491 [drawData appendBytes:&gui.col length:sizeof(gui.col)];
493 [self queueMessage:BatchDrawMsgID data:[drawData copy]];
494 [drawData setLength:0];
497 if ([outputQueue count] > 0) {
498 [self insertVimStateMessage];
501 [frontendProxy processCommandQueue:outputQueue];
503 @catch (NSException *e) {
504 NSLog(@"Exception caught when processing command queue: \"%@\"", e);
505 NSLog(@"outputQueue(len:%d)=%@", [outputQueue count]/2,
507 if (![connection isValid]) {
508 NSLog(@"WARNING! Connection is invalid, exit now!");
509 NSLog(@"waitForAck=%d got_int=%d", waitForAck, got_int);
514 [outputQueue removeAllObjects];
518 - (BOOL)waitForInput:(int)milliseconds
520 // Return NO if we timed out waiting for input, otherwise return YES.
521 BOOL inputReceived = NO;
523 // Only start the run loop if the input queue is empty, otherwise process
524 // the input first so that the input on queue isn't delayed.
525 if ([inputQueue count]) {
528 // Wait for the specified amount of time, unless 'milliseconds' is
529 // negative in which case we wait "forever" (1e6 seconds translates to
530 // approximately 11 days).
531 CFTimeInterval dt = (milliseconds >= 0 ? .001*milliseconds : 1e6);
533 while (CFRunLoopRunInMode(kCFRunLoopDefaultMode, dt, true)
534 == kCFRunLoopRunHandledSource) {
535 // In order to ensure that all input on the run-loop has been
536 // processed we set the timeout to 0 and keep processing until the
537 // run-loop times out.
543 // The above calls may have placed messages on the input queue so process
544 // it now. This call may enter a blocking loop.
545 if ([inputQueue count] > 0)
546 [self processInputQueue];
548 return inputReceived;
553 // NOTE: This is called if mch_exit() is called. Since we assume here that
554 // the process has started properly, be sure to use exit() instead of
555 // mch_exit() to prematurely terminate a process (or set 'isTerminating'
558 // Make sure no connectionDidDie: notification is received now that we are
560 [[NSNotificationCenter defaultCenter] removeObserver:self];
562 // The 'isTerminating' flag indicates that the frontend is also exiting so
563 // there is no need to flush any more output since the frontend won't look
565 if (!isTerminating && [connection isValid]) {
567 // Flush the entire queue in case a VimLeave autocommand added
568 // something to the queue.
569 [self queueMessage:CloseWindowMsgID data:nil];
570 [frontendProxy processCommandQueue:outputQueue];
572 @catch (NSException *e) {
573 NSLog(@"Exception caught when sending CloseWindowMsgID: \"%@\"", e);
576 // NOTE: If Cmd-w was pressed to close the window the menu is briefly
577 // highlighted and during this pause the frontend won't receive any DO
578 // messages. If the Vim process exits before this highlighting has
579 // finished Cocoa will emit the following error message:
580 // *** -[NSMachPort handlePortMessage:]: dropping incoming DO message
581 // because the connection or ports are invalid
582 // To avoid this warning we delay here. If the warning still appears
583 // this delay may need to be increased.
587 #ifdef MAC_CLIENTSERVER
588 // The default connection is used for the client/server code.
589 [[NSConnection defaultConnection] setRootObject:nil];
590 [[NSConnection defaultConnection] invalidate];
594 - (void)selectTab:(int)index
596 //NSLog(@"%s%d", _cmd, index);
599 NSData *data = [NSData dataWithBytes:&index length:sizeof(int)];
600 [self queueMessage:SelectTabMsgID data:data];
605 //NSLog(@"%s", _cmd);
607 NSMutableData *data = [NSMutableData data];
609 int idx = tabpage_index(curtab) - 1;
610 [data appendBytes:&idx length:sizeof(int)];
613 for (tp = first_tabpage; tp != NULL; tp = tp->tp_next) {
614 // Count the number of windows in the tabpage.
615 //win_T *wp = tp->tp_firstwin;
617 //for (wincount = 0; wp != NULL; wp = wp->w_next, ++wincount);
618 //[data appendBytes:&wincount length:sizeof(int)];
620 int tabProp = MMTabInfoCount;
621 [data appendBytes:&tabProp length:sizeof(int)];
622 for (tabProp = MMTabLabel; tabProp < MMTabInfoCount; ++tabProp) {
623 // This function puts the label of the tab in the global 'NameBuff'.
624 get_tabline_label(tp, (tabProp == MMTabToolTip));
625 NSString *s = [NSString stringWithVimString:NameBuff];
626 int len = [s lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
630 [data appendBytes:&len length:sizeof(int)];
632 [data appendBytes:[s UTF8String] length:len];
636 [self queueMessage:UpdateTabBarMsgID data:data];
639 - (BOOL)tabBarVisible
641 return tabBarVisible;
644 - (void)showTabBar:(BOOL)enable
646 tabBarVisible = enable;
648 int msgid = enable ? ShowTabBarMsgID : HideTabBarMsgID;
649 [self queueMessage:msgid data:nil];
652 - (void)setRows:(int)rows columns:(int)cols
654 //NSLog(@"[VimTask] setRows:%d columns:%d", rows, cols);
656 int dim[] = { rows, cols };
657 NSData *data = [NSData dataWithBytes:&dim length:2*sizeof(int)];
659 [self queueMessage:SetTextDimensionsMsgID data:data];
662 - (void)setWindowTitle:(char *)title
664 NSMutableData *data = [NSMutableData data];
665 int len = strlen(title);
666 if (len <= 0) return;
668 [data appendBytes:&len length:sizeof(int)];
669 [data appendBytes:title length:len];
671 [self queueMessage:SetWindowTitleMsgID data:data];
674 - (void)setDocumentFilename:(char *)filename
676 NSMutableData *data = [NSMutableData data];
677 int len = filename ? strlen(filename) : 0;
679 [data appendBytes:&len length:sizeof(int)];
681 [data appendBytes:filename length:len];
683 [self queueMessage:SetDocumentFilenameMsgID data:data];
686 - (char *)browseForFileWithAttributes:(NSDictionary *)attr
691 [frontendProxy showSavePanelWithAttributes:attr];
693 [self waitForDialogReturn];
695 if (dialogReturn && [dialogReturn isKindOfClass:[NSString class]])
696 s = [dialogReturn vimStringSave];
698 [dialogReturn release]; dialogReturn = nil;
700 @catch (NSException *e) {
701 NSLog(@"Exception caught when showing save panel: \"%@\"", e);
707 - (oneway void)setDialogReturn:(in bycopy id)obj
709 // NOTE: This is called by
710 // - [MMVimController panelDidEnd:::], and
711 // - [MMVimController alertDidEnd:::],
712 // to indicate that a save/open panel or alert has finished.
714 // We want to distinguish between "no dialog return yet" and "dialog
715 // returned nothing". The former can be tested with dialogReturn == nil,
716 // the latter with dialogReturn == [NSNull null].
717 if (!obj) obj = [NSNull null];
719 if (obj != dialogReturn) {
720 [dialogReturn release];
721 dialogReturn = [obj retain];
725 - (int)showDialogWithAttributes:(NSDictionary *)attr textField:(char *)txtfield
730 [frontendProxy presentDialogWithAttributes:attr];
732 [self waitForDialogReturn];
734 if (dialogReturn && [dialogReturn isKindOfClass:[NSArray class]]
735 && [dialogReturn count]) {
736 retval = [[dialogReturn objectAtIndex:0] intValue];
737 if (txtfield && [dialogReturn count] > 1) {
738 NSString *retString = [dialogReturn objectAtIndex:1];
739 char_u *ret = (char_u*)[retString UTF8String];
741 ret = CONVERT_FROM_UTF8(ret);
743 vim_strncpy((char_u*)txtfield, ret, IOSIZE - 1);
745 CONVERT_FROM_UTF8_FREE(ret);
750 [dialogReturn release]; dialogReturn = nil;
752 @catch (NSException *e) {
753 NSLog(@"Exception caught while showing alert dialog: \"%@\"", e);
759 - (void)showToolbar:(int)enable flags:(int)flags
761 NSMutableData *data = [NSMutableData data];
763 [data appendBytes:&enable length:sizeof(int)];
764 [data appendBytes:&flags length:sizeof(int)];
766 [self queueMessage:ShowToolbarMsgID data:data];
769 - (void)createScrollbarWithIdentifier:(long)ident type:(int)type
771 NSMutableData *data = [NSMutableData data];
773 [data appendBytes:&ident length:sizeof(long)];
774 [data appendBytes:&type length:sizeof(int)];
776 [self queueMessage:CreateScrollbarMsgID data:data];
779 - (void)destroyScrollbarWithIdentifier:(long)ident
781 NSMutableData *data = [NSMutableData data];
782 [data appendBytes:&ident length:sizeof(long)];
784 [self queueMessage:DestroyScrollbarMsgID data:data];
787 - (void)showScrollbarWithIdentifier:(long)ident state:(int)visible
789 NSMutableData *data = [NSMutableData data];
791 [data appendBytes:&ident length:sizeof(long)];
792 [data appendBytes:&visible length:sizeof(int)];
794 [self queueMessage:ShowScrollbarMsgID data:data];
797 - (void)setScrollbarPosition:(int)pos length:(int)len identifier:(long)ident
799 NSMutableData *data = [NSMutableData data];
801 [data appendBytes:&ident length:sizeof(long)];
802 [data appendBytes:&pos length:sizeof(int)];
803 [data appendBytes:&len length:sizeof(int)];
805 [self queueMessage:SetScrollbarPositionMsgID data:data];
808 - (void)setScrollbarThumbValue:(long)val size:(long)size max:(long)max
809 identifier:(long)ident
811 float fval = max-size+1 > 0 ? (float)val/(max-size+1) : 0;
812 float prop = (float)size/(max+1);
813 if (fval < 0) fval = 0;
814 else if (fval > 1.0f) fval = 1.0f;
815 if (prop < 0) prop = 0;
816 else if (prop > 1.0f) prop = 1.0f;
818 NSMutableData *data = [NSMutableData data];
820 [data appendBytes:&ident length:sizeof(long)];
821 [data appendBytes:&fval length:sizeof(float)];
822 [data appendBytes:&prop length:sizeof(float)];
824 [self queueMessage:SetScrollbarThumbMsgID data:data];
827 - (void)setFont:(GuiFont)font wide:(BOOL)wide
829 NSString *fontName = (NSString *)font;
831 NSArray *components = [fontName componentsSeparatedByString:@":"];
832 if ([components count] == 2) {
833 size = [[components lastObject] floatValue];
834 fontName = [components objectAtIndex:0];
837 int len = [fontName lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
838 NSMutableData *data = [NSMutableData data];
839 [data appendBytes:&size length:sizeof(float)];
840 [data appendBytes:&len length:sizeof(int)];
843 [data appendBytes:[fontName UTF8String] length:len];
845 return; // Only the wide font can be set to nothing
847 [self queueMessage:(wide ? SetWideFontMsgID : SetFontMsgID) data:data];
850 - (void)executeActionWithName:(NSString *)name
852 int len = [name lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
855 NSMutableData *data = [NSMutableData data];
857 [data appendBytes:&len length:sizeof(int)];
858 [data appendBytes:[name UTF8String] length:len];
860 [self queueMessage:ExecuteActionMsgID data:data];
864 - (void)setMouseShape:(int)shape
866 NSMutableData *data = [NSMutableData data];
867 [data appendBytes:&shape length:sizeof(int)];
868 [self queueMessage:SetMouseShapeMsgID data:data];
871 - (void)setBlinkWait:(int)wait on:(int)on off:(int)off
873 // Vim specifies times in milliseconds, whereas Cocoa wants them in
875 blinkWaitInterval = .001f*wait;
876 blinkOnInterval = .001f*on;
877 blinkOffInterval = .001f*off;
883 [blinkTimer invalidate];
884 [blinkTimer release];
888 if (blinkWaitInterval > 0 && blinkOnInterval > 0 && blinkOffInterval > 0
890 blinkState = MMBlinkStateOn;
892 [[NSTimer scheduledTimerWithTimeInterval:blinkWaitInterval
894 selector:@selector(blinkTimerFired:)
895 userInfo:nil repeats:NO] retain];
896 gui_update_cursor(TRUE, FALSE);
897 [self flushQueue:YES];
903 if (MMBlinkStateOff == blinkState) {
904 gui_update_cursor(TRUE, FALSE);
905 [self flushQueue:YES];
908 blinkState = MMBlinkStateNone;
911 - (void)adjustLinespace:(int)linespace
913 NSMutableData *data = [NSMutableData data];
914 [data appendBytes:&linespace length:sizeof(int)];
915 [self queueMessage:AdjustLinespaceMsgID data:data];
920 [self queueMessage:ActivateMsgID data:nil];
923 - (void)setPreEditRow:(int)row column:(int)col
925 NSMutableData *data = [NSMutableData data];
926 [data appendBytes:&row length:sizeof(int)];
927 [data appendBytes:&col length:sizeof(int)];
928 [self queueMessage:SetPreEditPositionMsgID data:data];
931 - (int)lookupColorWithKey:(NSString *)key
933 if (!(key && [key length] > 0))
936 NSString *stripKey = [[[[key lowercaseString]
937 stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]
938 componentsSeparatedByString:@" "]
939 componentsJoinedByString:@""];
941 if (stripKey && [stripKey length] > 0) {
942 // First of all try to lookup key in the color dictionary; note that
943 // all keys in this dictionary are lowercase with no whitespace.
944 id obj = [colorDict objectForKey:stripKey];
945 if (obj) return [obj intValue];
947 // The key was not in the dictionary; is it perhaps of the form
949 if ([stripKey length] > 1 && [stripKey characterAtIndex:0] == '#') {
950 NSScanner *scanner = [NSScanner scannerWithString:stripKey];
951 [scanner setScanLocation:1];
953 if ([scanner scanHexInt:&hex]) {
958 // As a last resort, check if it is one of the system defined colors.
959 // The keys in this dictionary are also lowercase with no whitespace.
960 obj = [sysColorDict objectForKey:stripKey];
962 NSColor *col = [NSColor performSelector:NSSelectorFromString(obj)];
965 col = [col colorUsingColorSpaceName:NSCalibratedRGBColorSpace];
966 [col getRed:&r green:&g blue:&b alpha:&a];
967 return (((int)(r*255+.5f) & 0xff) << 16)
968 + (((int)(g*255+.5f) & 0xff) << 8)
969 + ((int)(b*255+.5f) & 0xff);
974 //NSLog(@"WARNING: No color with key %@ found.", stripKey);
978 - (BOOL)hasSpecialKeyWithValue:(NSString *)value
980 NSEnumerator *e = [[MMBackend specialKeys] objectEnumerator];
983 while ((obj = [e nextObject])) {
984 if ([value isEqual:obj])
991 - (void)enterFullscreen:(int)fuoptions background:(int)bg
993 NSMutableData *data = [NSMutableData data];
994 [data appendBytes:&fuoptions length:sizeof(int)];
996 [data appendBytes:&bg length:sizeof(int)];
997 [self queueMessage:EnterFullscreenMsgID data:data];
1000 - (void)leaveFullscreen
1002 [self queueMessage:LeaveFullscreenMsgID data:nil];
1005 - (void)setFullscreenBackgroundColor:(int)color
1007 NSMutableData *data = [NSMutableData data];
1008 color = MM_COLOR(color);
1009 [data appendBytes:&color length:sizeof(int)];
1011 [self queueMessage:SetFullscreenColorMsgID data:data];
1014 - (void)setAntialias:(BOOL)antialias
1016 int msgid = antialias ? EnableAntialiasMsgID : DisableAntialiasMsgID;
1018 [self queueMessage:msgid data:nil];
1021 - (void)updateModifiedFlag
1023 // Notify MacVim if _any_ buffer has changed from unmodified to modified or
1025 int msgid = [self checkForModifiedBuffers]
1026 ? BuffersModifiedMsgID : BuffersNotModifiedMsgID;
1028 [self queueMessage:msgid data:nil];
1031 - (oneway void)processInput:(int)msgid data:(in bycopy NSData *)data
1033 // Look for Cmd-. and Ctrl-C immediately instead of waiting until the input
1034 // queue is processed since that only happens in waitForInput: (and Vim
1035 // regularly checks for Ctrl-C in between waiting for input).
1036 // Similarly, TerminateNowMsgID must be checked immediately otherwise code
1037 // which waits on the run loop will fail to detect this message (e.g. in
1038 // waitForConnectionAcknowledgement).
1040 BOOL shouldClearQueue = NO;
1041 if (InterruptMsgID == msgid) {
1042 shouldClearQueue = YES;
1044 } else if (InsertTextMsgID == msgid && data != nil) {
1045 const void *bytes = [data bytes];
1046 bytes += sizeof(int);
1047 int len = *((int*)bytes); bytes += sizeof(int);
1049 char_u *str = (char_u*)bytes;
1050 if ((str[0] == Ctrl_C && ctrl_c_interrupts) ||
1051 (str[0] == intr_char && intr_char != Ctrl_C)) {
1052 shouldClearQueue = YES;
1056 } else if (TerminateNowMsgID == msgid) {
1057 // Terminate immediately (the frontend is about to quit or this process
1059 isTerminating = YES;
1064 if (shouldClearQueue) {
1065 [inputQueue removeAllObjects];
1069 // Remove all previous instances of this message from the input queue, else
1070 // the input queue may fill up as a result of Vim not being able to keep up
1071 // with the speed at which new messages are received.
1072 // Keyboard input is never dropped, unless the input represents and
1073 // auto-repeated key.
1075 BOOL isKeyRepeat = NO;
1076 BOOL isKeyboardInput = NO;
1078 if (data && (InsertTextMsgID == msgid || KeyDownMsgID == msgid ||
1079 CmdKeyMsgID == msgid)) {
1080 isKeyboardInput = YES;
1082 // The lowest bit of the first int is set if this key is a repeat.
1083 int flags = *((int*)[data bytes]);
1088 // Keyboard input is not removed from the queue; repeats are ignored if
1089 // there already is keyboard input on the input queue.
1090 if (isKeyRepeat || !isKeyboardInput) {
1091 int i, count = [inputQueue count];
1092 for (i = 1; i < count; i+=2) {
1093 if ([[inputQueue objectAtIndex:i-1] intValue] == msgid) {
1097 [inputQueue removeObjectAtIndex:i];
1098 [inputQueue removeObjectAtIndex:i-1];
1104 [inputQueue addObject:[NSNumber numberWithInt:msgid]];
1105 [inputQueue addObject:(data ? (id)data : [NSNull null])];
1108 - (oneway void)processInputAndData:(in bycopy NSArray *)messages
1110 // This is just a convenience method that allows the frontend to delay
1111 // sending messages.
1112 int i, count = [messages count];
1113 for (i = 1; i < count; i+=2)
1114 [self processInput:[[messages objectAtIndex:i-1] intValue]
1115 data:[messages objectAtIndex:i]];
1118 - (id)evaluateExpressionCocoa:(in bycopy NSString *)expr
1119 errorString:(out bycopy NSString **)errstr
1121 return evalExprCocoa(expr, errstr);
1125 - (NSString *)evaluateExpression:(in bycopy NSString *)expr
1127 NSString *eval = nil;
1128 char_u *s = (char_u*)[expr UTF8String];
1131 s = CONVERT_FROM_UTF8(s);
1134 char_u *res = eval_client_expr_to_string(s);
1137 CONVERT_FROM_UTF8_FREE(s);
1143 s = CONVERT_TO_UTF8(s);
1145 eval = [NSString stringWithUTF8String:(char*)s];
1147 CONVERT_TO_UTF8_FREE(s);
1155 - (BOOL)starRegisterToPasteboard:(byref NSPasteboard *)pboard
1157 // TODO: This method should share code with clip_mch_request_selection().
1159 if (VIsual_active && (State & NORMAL) && clip_star.available) {
1160 // If there is no pasteboard, return YES to indicate that there is text
1165 clip_copy_selection();
1167 // Get the text to put on the pasteboard.
1168 long_u llen = 0; char_u *str = 0;
1169 int type = clip_convert_selection(&str, &llen, &clip_star);
1173 // TODO: Avoid overflow.
1174 int len = (int)llen;
1176 if (output_conv.vc_type != CONV_NONE) {
1177 char_u *conv_str = string_convert(&output_conv, str, &len);
1185 NSString *string = [[NSString alloc]
1186 initWithBytes:str length:len encoding:NSUTF8StringEncoding];
1188 NSArray *types = [NSArray arrayWithObject:NSStringPboardType];
1189 [pboard declareTypes:types owner:nil];
1190 BOOL ok = [pboard setString:string forType:NSStringPboardType];
1201 - (oneway void)addReply:(in bycopy NSString *)reply
1202 server:(in byref id <MMVimServerProtocol>)server
1204 //NSLog(@"addReply:%@ server:%@", reply, (id)server);
1206 // Replies might come at any time and in any order so we keep them in an
1207 // array inside a dictionary with the send port used as key.
1209 NSConnection *conn = [(NSDistantObject*)server connectionForProxy];
1210 // HACK! Assume connection uses mach ports.
1211 int port = [(NSMachPort*)[conn sendPort] machPort];
1212 NSNumber *key = [NSNumber numberWithInt:port];
1214 NSMutableArray *replies = [serverReplyDict objectForKey:key];
1216 replies = [NSMutableArray array];
1217 [serverReplyDict setObject:replies forKey:key];
1220 [replies addObject:reply];
1223 - (void)addInput:(in bycopy NSString *)input
1224 client:(in byref id <MMVimClientProtocol>)client
1226 //NSLog(@"addInput:%@ client:%@", input, (id)client);
1228 // NOTE: We don't call addInput: here because it differs from
1229 // server_to_input_buf() in that it always sets the 'silent' flag and we
1230 // don't want the MacVim client/server code to behave differently from
1232 char_u *s = [input vimStringSave];
1233 server_to_input_buf(s);
1236 [self addClient:(id)client];
1239 - (NSString *)evaluateExpression:(in bycopy NSString *)expr
1240 client:(in byref id <MMVimClientProtocol>)client
1242 [self addClient:(id)client];
1243 return [self evaluateExpression:expr];
1246 - (void)registerServerWithName:(NSString *)name
1248 NSString *svrName = name;
1249 NSConnection *svrConn = [NSConnection defaultConnection];
1252 for (i = 0; i < MMServerMax; ++i) {
1253 NSString *connName = [self connectionNameFromServerName:svrName];
1255 if ([svrConn registerName:connName]) {
1256 //NSLog(@"Registered server with name: %@", svrName);
1258 // TODO: Set request/reply time-outs to something else?
1260 // Don't wait for requests (time-out means that the message is
1262 [svrConn setRequestTimeout:0];
1263 //[svrConn setReplyTimeout:MMReplyTimeout];
1264 [svrConn setRootObject:self];
1266 // NOTE: 'serverName' is a global variable
1267 serverName = [svrName vimStringSave];
1269 set_vim_var_string(VV_SEND_SERVER, serverName, -1);
1272 need_maketitle = TRUE;
1274 [self queueMessage:SetServerNameMsgID data:
1275 [svrName dataUsingEncoding:NSUTF8StringEncoding]];
1279 svrName = [NSString stringWithFormat:@"%@%d", name, i+1];
1283 - (BOOL)sendToServer:(NSString *)name string:(NSString *)string
1284 reply:(char_u **)reply port:(int *)port expression:(BOOL)expr
1287 // NOTE: If 'name' equals 'serverName' then the request is local (client
1288 // and server are the same). This case is not handled separately, so a
1289 // connection will be set up anyway (this simplifies the code).
1291 NSConnection *conn = [self connectionForServerName:name];
1294 char_u *s = (char_u*)[name UTF8String];
1296 s = CONVERT_FROM_UTF8(s);
1298 EMSG2(_(e_noserver), s);
1300 CONVERT_FROM_UTF8_FREE(s);
1307 // HACK! Assume connection uses mach ports.
1308 *port = [(NSMachPort*)[conn sendPort] machPort];
1311 id proxy = [conn rootProxy];
1312 [proxy setProtocolForProxy:@protocol(MMVimServerProtocol)];
1316 NSString *eval = [proxy evaluateExpression:string client:self];
1319 *reply = [eval vimStringSave];
1321 *reply = vim_strsave((char_u*)_(e_invexprmsg));
1328 [proxy addInput:string client:self];
1331 @catch (NSException *e) {
1332 NSLog(@"WARNING: Caught exception in %s: \"%@\"", _cmd, e);
1339 - (NSArray *)serverList
1341 NSArray *list = nil;
1343 if ([self connection]) {
1344 id proxy = [connection rootProxy];
1345 [proxy setProtocolForProxy:@protocol(MMAppProtocol)];
1348 list = [proxy serverList];
1350 @catch (NSException *e) {
1351 NSLog(@"Exception caught when listing servers: \"%@\"", e);
1354 EMSG(_("E???: No connection to MacVim, server listing not possible."));
1360 - (NSString *)peekForReplyOnPort:(int)port
1362 //NSLog(@"%s%d", _cmd, port);
1364 NSNumber *key = [NSNumber numberWithInt:port];
1365 NSMutableArray *replies = [serverReplyDict objectForKey:key];
1366 if (replies && [replies count]) {
1367 //NSLog(@" %d replies, topmost is: %@", [replies count],
1368 // [replies objectAtIndex:0]);
1369 return [replies objectAtIndex:0];
1372 //NSLog(@" No replies");
1376 - (NSString *)waitForReplyOnPort:(int)port
1378 //NSLog(@"%s%d", _cmd, port);
1380 NSConnection *conn = [self connectionForServerPort:port];
1384 NSNumber *key = [NSNumber numberWithInt:port];
1385 NSMutableArray *replies = nil;
1386 NSString *reply = nil;
1388 // Wait for reply as long as the connection to the server is valid (unless
1389 // user interrupts wait with Ctrl-C).
1390 while (!got_int && [conn isValid] &&
1391 !(replies = [serverReplyDict objectForKey:key])) {
1392 [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
1393 beforeDate:[NSDate distantFuture]];
1397 if ([replies count] > 0) {
1398 reply = [[replies objectAtIndex:0] retain];
1399 //NSLog(@" Got reply: %@", reply);
1400 [replies removeObjectAtIndex:0];
1401 [reply autorelease];
1404 if ([replies count] == 0)
1405 [serverReplyDict removeObjectForKey:key];
1411 - (BOOL)sendReply:(NSString *)reply toPort:(int)port
1413 id client = [clientProxyDict objectForKey:[NSNumber numberWithInt:port]];
1416 //NSLog(@"sendReply:%@ toPort:%d", reply, port);
1417 [client addReply:reply server:self];
1420 @catch (NSException *e) {
1421 NSLog(@"WARNING: Exception caught in %s: \"%@\"", _cmd, e);
1424 EMSG2(_("E???: server2client failed; no client with id 0x%x"), port);
1435 - (void)setWaitForAck:(BOOL)yn
1440 - (void)waitForConnectionAcknowledgement
1442 if (!waitForAck) return;
1444 while (waitForAck && !got_int && [connection isValid]) {
1445 [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
1446 beforeDate:[NSDate distantFuture]];
1447 //NSLog(@" waitForAck=%d got_int=%d isValid=%d",
1448 // waitForAck, got_int, [connection isValid]);
1452 // Never received a connection acknowledgement, so die.
1453 [[NSNotificationCenter defaultCenter] removeObserver:self];
1454 [frontendProxy release]; frontendProxy = nil;
1456 // NOTE: We intentionally do not call mch_exit() since this in turn
1457 // will lead to -[MMBackend exit] getting called which we want to
1462 [self processInputQueue];
1465 - (oneway void)acknowledgeConnection
1467 //NSLog(@"%s", _cmd);
1475 @implementation MMBackend (Private)
1477 - (void)waitForDialogReturn
1479 // Keep processing the run loop until a dialog returns. To avoid getting
1480 // stuck in an endless loop (could happen if the setDialogReturn: message
1481 // was lost) we also do some paranoia checks.
1483 // Note that in Cocoa the user can still resize windows and select menu
1484 // items while a sheet is being displayed, so we can't just wait for the
1485 // first message to arrive and assume that is the setDialogReturn: call.
1487 while (nil == dialogReturn && !got_int && [connection isValid])
1488 [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
1489 beforeDate:[NSDate distantFuture]];
1491 // Search for any resize messages on the input queue. All other messages
1492 // on the input queue are dropped. The reason why we single out resize
1493 // messages is because the user may have resized the window while a sheet
1495 int i, count = [inputQueue count];
1497 id textDimData = nil;
1499 for (i = count-2; i >= 0; i -= 2) {
1500 int msgid = [[inputQueue objectAtIndex:i] intValue];
1501 if (SetTextDimensionsMsgID == msgid) {
1502 textDimData = [[inputQueue objectAtIndex:i+1] retain];
1508 [inputQueue removeAllObjects];
1511 [inputQueue addObject:
1512 [NSNumber numberWithInt:SetTextDimensionsMsgID]];
1513 [inputQueue addObject:textDimData];
1514 [textDimData release];
1519 - (void)insertVimStateMessage
1521 // NOTE: This is the place to add Vim state that needs to be accessed from
1522 // MacVim. Do not add state that could potentially require lots of memory
1523 // since this message gets sent each time the output queue is forcibly
1524 // flushed (e.g. storing the currently selected text would be a bad idea).
1525 // We take this approach of "pushing" the state to MacVim to avoid having
1526 // to make synchronous calls from MacVim to Vim in order to get state.
1528 BOOL mmta = curbuf ? curbuf->b_p_mmta : NO;
1530 NSDictionary *vimState = [NSDictionary dictionaryWithObjectsAndKeys:
1531 [[NSFileManager defaultManager] currentDirectoryPath], @"pwd",
1532 [NSNumber numberWithInt:p_mh], @"p_mh",
1533 [NSNumber numberWithBool:[self unusedEditor]], @"unusedEditor",
1534 [NSNumber numberWithBool:mmta], @"p_mmta",
1537 // Put the state before all other messages.
1538 int msgid = SetVimStateMsgID;
1539 [outputQueue insertObject:[vimState dictionaryAsData] atIndex:0];
1540 [outputQueue insertObject:[NSData dataWithBytes:&msgid length:sizeof(int)]
1544 - (void)processInputQueue
1546 if ([inputQueue count] == 0) return;
1548 // NOTE: One of the input events may cause this method to be called
1549 // recursively, so copy the input queue to a local variable and clear the
1550 // queue before starting to process input events (otherwise we could get
1551 // stuck in an endless loop).
1552 NSArray *q = [inputQueue copy];
1553 unsigned i, count = [q count];
1555 [inputQueue removeAllObjects];
1557 for (i = 1; i < count; i+=2) {
1558 int msgid = [[q objectAtIndex:i-1] intValue];
1559 id data = [q objectAtIndex:i];
1560 if ([data isEqual:[NSNull null]])
1563 //NSLog(@"(%d) %s:%s", i, _cmd, MessageStrings[msgid]);
1564 [self handleInputEvent:msgid data:data];
1568 //NSLog(@"Clear input event queue");
1571 - (void)handleInputEvent:(int)msgid data:(NSData *)data
1573 if (InsertTextMsgID == msgid || KeyDownMsgID == msgid ||
1574 CmdKeyMsgID == msgid) {
1576 const void *bytes = [data bytes];
1577 int mods = *((int*)bytes); bytes += sizeof(int);
1578 int len = *((int*)bytes); bytes += sizeof(int);
1579 NSString *key = [[NSString alloc] initWithBytes:bytes
1581 encoding:NSUTF8StringEncoding];
1582 mods = eventModifierFlagsToVimModMask(mods);
1584 if (InsertTextMsgID == msgid)
1585 [self handleInsertText:key];
1587 [self handleKeyDown:key modifiers:mods];
1590 } else if (ScrollWheelMsgID == msgid) {
1592 const void *bytes = [data bytes];
1594 int row = *((int*)bytes); bytes += sizeof(int);
1595 int col = *((int*)bytes); bytes += sizeof(int);
1596 int flags = *((int*)bytes); bytes += sizeof(int);
1597 float dy = *((float*)bytes); bytes += sizeof(float);
1599 int button = MOUSE_5;
1600 if (dy > 0) button = MOUSE_4;
1602 flags = eventModifierFlagsToVimMouseModMask(flags);
1604 int numLines = (int)round(dy);
1605 if (numLines < 0) numLines = -numLines;
1606 if (numLines == 0) numLines = 1;
1608 #ifdef FEAT_GUI_SCROLL_WHEEL_FORCE
1609 gui.scroll_wheel_force = numLines;
1612 gui_send_mouse_event(button, col, row, NO, flags);
1613 } else if (MouseDownMsgID == msgid) {
1615 const void *bytes = [data bytes];
1617 int row = *((int*)bytes); bytes += sizeof(int);
1618 int col = *((int*)bytes); bytes += sizeof(int);
1619 int button = *((int*)bytes); bytes += sizeof(int);
1620 int flags = *((int*)bytes); bytes += sizeof(int);
1621 int count = *((int*)bytes); bytes += sizeof(int);
1623 button = eventButtonNumberToVimMouseButton(button);
1625 flags = eventModifierFlagsToVimMouseModMask(flags);
1626 gui_send_mouse_event(button, col, row, count>1, flags);
1628 } else if (MouseUpMsgID == msgid) {
1630 const void *bytes = [data bytes];
1632 int row = *((int*)bytes); bytes += sizeof(int);
1633 int col = *((int*)bytes); bytes += sizeof(int);
1634 int flags = *((int*)bytes); bytes += sizeof(int);
1636 flags = eventModifierFlagsToVimMouseModMask(flags);
1638 gui_send_mouse_event(MOUSE_RELEASE, col, row, NO, flags);
1639 } else if (MouseDraggedMsgID == msgid) {
1641 const void *bytes = [data bytes];
1643 int row = *((int*)bytes); bytes += sizeof(int);
1644 int col = *((int*)bytes); bytes += sizeof(int);
1645 int flags = *((int*)bytes); bytes += sizeof(int);
1647 flags = eventModifierFlagsToVimMouseModMask(flags);
1649 gui_send_mouse_event(MOUSE_DRAG, col, row, NO, flags);
1650 } else if (MouseMovedMsgID == msgid) {
1651 const void *bytes = [data bytes];
1652 int row = *((int*)bytes); bytes += sizeof(int);
1653 int col = *((int*)bytes); bytes += sizeof(int);
1655 gui_mouse_moved(col, row);
1656 } else if (AddInputMsgID == msgid) {
1657 NSString *string = [[NSString alloc] initWithData:data
1658 encoding:NSUTF8StringEncoding];
1660 [self addInput:string];
1663 } else if (SelectTabMsgID == msgid) {
1665 const void *bytes = [data bytes];
1666 int idx = *((int*)bytes) + 1;
1667 //NSLog(@"Selecting tab %d", idx);
1668 send_tabline_event(idx);
1669 } else if (CloseTabMsgID == msgid) {
1671 const void *bytes = [data bytes];
1672 int idx = *((int*)bytes) + 1;
1673 //NSLog(@"Closing tab %d", idx);
1674 send_tabline_menu_event(idx, TABLINE_MENU_CLOSE);
1675 } else if (AddNewTabMsgID == msgid) {
1676 //NSLog(@"Adding new tab");
1677 send_tabline_menu_event(0, TABLINE_MENU_NEW);
1678 } else if (DraggedTabMsgID == msgid) {
1680 const void *bytes = [data bytes];
1681 // NOTE! The destination index is 0 based, so do not add 1 to make it 1
1683 int idx = *((int*)bytes);
1686 } else if (SetTextDimensionsMsgID == msgid || LiveResizeMsgID == msgid
1687 || SetTextRowsMsgID == msgid || SetTextColumnsMsgID == msgid) {
1689 const void *bytes = [data bytes];
1691 if (SetTextColumnsMsgID != msgid) {
1692 rows = *((int*)bytes); bytes += sizeof(int);
1695 if (SetTextRowsMsgID != msgid) {
1696 cols = *((int*)bytes); bytes += sizeof(int);
1700 if (SetTextRowsMsgID == msgid || SetTextColumnsMsgID == msgid) {
1701 int dim[2] = { rows, cols };
1702 d = [NSData dataWithBytes:dim length:2*sizeof(int)];
1703 msgid = SetTextDimensionsReplyMsgID;
1706 if (SetTextDimensionsMsgID == msgid)
1707 msgid = SetTextDimensionsReplyMsgID;
1709 // NOTE! Vim doesn't call gui_mch_set_shellsize() after
1710 // gui_resize_shell(), so we have to manually set the rows and columns
1711 // here since MacVim doesn't change the rows and columns to avoid
1712 // inconsistent states between Vim and MacVim. The message sent back
1713 // indicates that it is a reply to a message that originated in MacVim
1714 // since we need to be able to determine where a message originated.
1715 [self queueMessage:msgid data:d];
1717 //NSLog(@"[VimTask] Resizing shell to %dx%d.", cols, rows);
1718 gui_resize_shell(cols, rows);
1719 } else if (ExecuteMenuMsgID == msgid) {
1720 NSDictionary *attrs = [NSDictionary dictionaryWithData:data];
1722 NSArray *desc = [attrs objectForKey:@"descriptor"];
1723 vimmenu_T *menu = menu_for_descriptor(desc);
1727 } else if (ToggleToolbarMsgID == msgid) {
1728 [self handleToggleToolbar];
1729 } else if (ScrollbarEventMsgID == msgid) {
1730 [self handleScrollbarEvent:data];
1731 } else if (SetFontMsgID == msgid) {
1732 [self handleSetFont:data];
1733 } else if (VimShouldCloseMsgID == msgid) {
1735 } else if (DropFilesMsgID == msgid) {
1736 [self handleDropFiles:data];
1737 } else if (DropStringMsgID == msgid) {
1738 [self handleDropString:data];
1739 } else if (GotFocusMsgID == msgid) {
1741 [self focusChange:YES];
1742 } else if (LostFocusMsgID == msgid) {
1744 [self focusChange:NO];
1745 } else if (SetMouseShapeMsgID == msgid) {
1746 const void *bytes = [data bytes];
1747 int shape = *((int*)bytes); bytes += sizeof(int);
1748 update_mouseshape(shape);
1749 } else if (XcodeModMsgID == msgid) {
1750 [self handleXcodeMod:data];
1751 } else if (OpenWithArgumentsMsgID == msgid) {
1752 [self handleOpenWithArguments:[NSDictionary dictionaryWithData:data]];
1753 } else if (FindReplaceMsgID == msgid) {
1754 [self handleFindReplace:[NSDictionary dictionaryWithData:data]];
1756 NSLog(@"WARNING: Unknown message received (msgid=%d)", msgid);
1760 + (NSDictionary *)specialKeys
1762 static NSDictionary *specialKeys = nil;
1765 NSBundle *mainBundle = [NSBundle mainBundle];
1766 NSString *path = [mainBundle pathForResource:@"SpecialKeys"
1768 specialKeys = [[NSDictionary alloc] initWithContentsOfFile:path];
1774 - (void)handleInsertText:(NSString *)text
1778 char_u *str = (char_u*)[text UTF8String];
1779 int i, len = [text lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
1782 char_u *conv_str = NULL;
1783 if (input_conv.vc_type != CONV_NONE) {
1784 conv_str = string_convert(&input_conv, str, &len);
1790 for (i = 0; i < len; ++i) {
1791 add_to_input_buf(str+i, 1);
1792 if (CSI == str[i]) {
1793 // NOTE: If the converted string contains the byte CSI, then it
1794 // must be followed by the bytes KS_EXTRA, KE_CSI or things
1796 static char_u extra[2] = { KS_EXTRA, KE_CSI };
1797 add_to_input_buf(extra, 2);
1807 - (void)handleKeyDown:(NSString *)key modifiers:(int)mods
1809 // TODO: This code is a horrible mess -- clean up!
1812 char_u *chars = (char_u*)[key UTF8String];
1814 char_u *conv_str = NULL;
1816 int length = [key lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
1818 // Special keys (arrow keys, function keys, etc.) are stored in a plist so
1819 // that new keys can easily be added.
1820 NSString *specialString = [[MMBackend specialKeys]
1822 if (specialString && [specialString length] > 1) {
1823 //NSLog(@"special key: %@", specialString);
1824 int ikey = TO_SPECIAL([specialString characterAtIndex:0],
1825 [specialString characterAtIndex:1]);
1827 ikey = simplify_key(ikey, &mods);
1832 special[1] = K_SECOND(ikey);
1833 special[2] = K_THIRD(ikey);
1837 } else if (1 == length && TAB == chars[0]) {
1838 // Tab is a trouble child:
1839 // - <Tab> is added to the input buffer as is
1840 // - <S-Tab> is translated to, {CSI,'k','B'} (i.e. 'Back-tab')
1841 // - <M-Tab> should be 0x80|TAB but this is not valid utf-8 so it needs
1842 // to be converted to utf-8
1843 // - <S-M-Tab> is translated to <S-Tab> with ALT modifier
1844 // - <C-Tab> is reserved by Mac OS X
1845 // - <D-Tab> is reserved by Mac OS X
1850 if (mods & MOD_MASK_SHIFT) {
1851 mods &= ~MOD_MASK_SHIFT;
1853 special[1] = K_SECOND(K_S_TAB);
1854 special[2] = K_THIRD(K_S_TAB);
1856 } else if (mods & MOD_MASK_ALT) {
1857 int mtab = 0x80 | TAB;
1861 special[0] = (mtab >> 6) + 0xc0;
1862 special[1] = mtab & 0xbf;
1870 mods &= ~MOD_MASK_ALT;
1872 } else if (1 == length && chars[0] < 0x80 && (mods & MOD_MASK_ALT)) {
1873 // META key is treated separately. This code was taken from gui_w48.c
1874 // and gui_gtk_x11.c.
1876 int ch = simplify_key(chars[0], &mods);
1878 // Remove the SHIFT modifier for keys where it's already included,
1879 // e.g., '(' and '*'
1880 if (ch < 0x100 && !isalpha(ch) && isprint(ch))
1881 mods &= ~MOD_MASK_SHIFT;
1883 // Interpret the ALT key as making the key META, include SHIFT, etc.
1884 ch = extract_modifiers(ch, &mods);
1890 string[len++] = CSI;
1891 string[len++] = KS_MODIFIER;
1892 string[len++] = mods;
1895 if (IS_SPECIAL(ch)) {
1896 string[len++] = CSI;
1897 string[len++] = K_SECOND(ch);
1898 string[len++] = K_THIRD(ch);
1902 // TODO: What if 'enc' is not "utf-8"?
1903 if (enc_utf8 && (ch & 0x80)) { // convert to utf-8
1904 string[len++] = ch & 0xbf;
1905 string[len-2] = ((unsigned)ch >> 6) + 0xc0;
1906 if (string[len-1] == CSI) {
1907 string[len++] = KS_EXTRA;
1908 string[len++] = (int)KE_CSI;
1914 add_to_input_buf(string, len);
1916 } else if (length > 0) {
1917 unichar c = [key characterAtIndex:0];
1918 //NSLog(@"non-special: %@ (hex=%x, mods=%d)", key,
1919 // [key characterAtIndex:0], mods);
1921 // HACK! In most circumstances the Ctrl and Shift modifiers should be
1922 // cleared since they are already added to the key by the AppKit.
1923 // Unfortunately, the only way to deal with when to clear the modifiers
1924 // or not seems to be to have hard-wired rules like this.
1925 if ( !((' ' == c) || (0xa0 == c) || (mods & MOD_MASK_CMD)
1926 || 0x9 == c || 0xd == c || ESC == c) ) {
1927 mods &= ~MOD_MASK_SHIFT;
1928 mods &= ~MOD_MASK_CTRL;
1929 //NSLog(@"clear shift ctrl");
1933 if (input_conv.vc_type != CONV_NONE) {
1934 conv_str = string_convert(&input_conv, chars, &length);
1941 if (chars && length > 0) {
1943 //NSLog(@"adding mods: %d", mods);
1945 modChars[1] = KS_MODIFIER;
1947 add_to_input_buf(modChars, 3);
1950 //NSLog(@"add to input buf: 0x%x", chars[0]);
1951 // TODO: Check for CSI bytes?
1952 add_to_input_buf(chars, length);
1961 - (void)queueMessage:(int)msgid data:(NSData *)data
1963 //if (msgid != EnableMenuItemMsgID)
1964 // NSLog(@"queueMessage:%s", MessageStrings[msgid]);
1966 [outputQueue addObject:[NSData dataWithBytes:&msgid length:sizeof(int)]];
1968 [outputQueue addObject:data];
1970 [outputQueue addObject:[NSData data]];
1973 - (void)connectionDidDie:(NSNotification *)notification
1975 // If the main connection to MacVim is lost this means that either MacVim
1976 // has crashed or this process did not receive its termination message
1977 // properly (e.g. if the TerminateNowMsgID was dropped).
1979 // NOTE: This is not called if a Vim controller invalidates its connection.
1981 NSLog(@"WARNING[%s]: Main connection was lost before process had a chance "
1982 "to terminate; preserving swap files.", _cmd);
1983 getout_preserve_modified(1);
1986 - (void)blinkTimerFired:(NSTimer *)timer
1988 NSTimeInterval timeInterval = 0;
1990 [blinkTimer release];
1993 if (MMBlinkStateOn == blinkState) {
1994 gui_undraw_cursor();
1995 blinkState = MMBlinkStateOff;
1996 timeInterval = blinkOffInterval;
1997 } else if (MMBlinkStateOff == blinkState) {
1998 gui_update_cursor(TRUE, FALSE);
1999 blinkState = MMBlinkStateOn;
2000 timeInterval = blinkOnInterval;
2003 if (timeInterval > 0) {
2005 [[NSTimer scheduledTimerWithTimeInterval:timeInterval target:self
2006 selector:@selector(blinkTimerFired:)
2007 userInfo:nil repeats:NO] retain];
2008 [self flushQueue:YES];
2012 - (void)focusChange:(BOOL)on
2014 gui_focus_change(on);
2017 - (void)handleToggleToolbar
2019 // If 'go' contains 'T', then remove it, else add it.
2021 char_u go[sizeof(GO_ALL)+2];
2026 p = vim_strchr(go, GO_TOOLBAR);
2030 char_u *end = go + len;
2036 go[len] = GO_TOOLBAR;
2040 set_option_value((char_u*)"guioptions", 0, go, 0);
2042 [self redrawScreen];
2045 - (void)handleScrollbarEvent:(NSData *)data
2049 const void *bytes = [data bytes];
2050 long ident = *((long*)bytes); bytes += sizeof(long);
2051 int hitPart = *((int*)bytes); bytes += sizeof(int);
2052 float fval = *((float*)bytes); bytes += sizeof(float);
2053 scrollbar_T *sb = gui_find_scrollbar(ident);
2056 scrollbar_T *sb_info = sb->wp ? &sb->wp->w_scrollbars[0] : sb;
2057 long value = sb_info->value;
2058 long size = sb_info->size;
2059 long max = sb_info->max;
2060 BOOL isStillDragging = NO;
2061 BOOL updateKnob = YES;
2064 case NSScrollerDecrementPage:
2065 value -= (size > 2 ? size - 2 : 1);
2067 case NSScrollerIncrementPage:
2068 value += (size > 2 ? size - 2 : 1);
2070 case NSScrollerDecrementLine:
2073 case NSScrollerIncrementLine:
2076 case NSScrollerKnob:
2077 isStillDragging = YES;
2079 case NSScrollerKnobSlot:
2080 value = (long)(fval * (max - size + 1));
2087 //NSLog(@"value %d -> %d", sb_info->value, value);
2088 gui_drag_scrollbar(sb, value, isStillDragging);
2091 // Dragging the knob or option+clicking automatically updates
2092 // the knob position (on the actual NSScroller), so we only
2093 // need to set the knob position in the other cases.
2095 // Update both the left&right vertical scrollbars.
2096 long identLeft = sb->wp->w_scrollbars[SBAR_LEFT].ident;
2097 long identRight = sb->wp->w_scrollbars[SBAR_RIGHT].ident;
2098 [self setScrollbarThumbValue:value size:size max:max
2099 identifier:identLeft];
2100 [self setScrollbarThumbValue:value size:size max:max
2101 identifier:identRight];
2103 // Update the horizontal scrollbar.
2104 [self setScrollbarThumbValue:value size:size max:max
2111 - (void)handleSetFont:(NSData *)data
2115 const void *bytes = [data bytes];
2116 float pointSize = *((float*)bytes); bytes += sizeof(float);
2117 //unsigned len = *((unsigned*)bytes); bytes += sizeof(unsigned);
2118 bytes += sizeof(unsigned); // len not used
2120 NSMutableString *name = [NSMutableString stringWithUTF8String:bytes];
2121 [name appendString:[NSString stringWithFormat:@":h%d", (int)pointSize]];
2122 char_u *s = (char_u*)[name UTF8String];
2125 s = CONVERT_FROM_UTF8(s);
2128 set_option_value((char_u*)"guifont", 0, s, 0);
2131 CONVERT_FROM_UTF8_FREE(s);
2134 [self redrawScreen];
2137 - (void)handleDropFiles:(NSData *)data
2139 // TODO: Get rid of this method; instead use Vim script directly. At the
2140 // moment I know how to do this to open files in tabs, but I'm not sure how
2141 // to add the filenames to the command line when in command line mode.
2145 NSMutableDictionary *args = [NSMutableDictionary dictionaryWithData:data];
2148 id obj = [args objectForKey:@"forceOpen"];
2149 BOOL forceOpen = YES;
2151 forceOpen = [obj boolValue];
2153 NSArray *filenames = [args objectForKey:@"filenames"];
2154 if (!(filenames && [filenames count] > 0)) return;
2157 if (!forceOpen && (State & CMDLINE)) {
2158 // HACK! If Vim is in command line mode then the files names
2159 // should be added to the command line, instead of opening the
2160 // files in tabs (unless forceOpen is set). This is taken care of by
2161 // gui_handle_drop().
2162 int n = [filenames count];
2163 char_u **fnames = (char_u **)alloc(n * sizeof(char_u *));
2166 for (i = 0; i < n; ++i)
2167 fnames[i] = [[filenames objectAtIndex:i] vimStringSave];
2169 // NOTE! This function will free 'fnames'.
2170 // HACK! It is assumed that the 'x' and 'y' arguments are
2171 // unused when in command line mode.
2172 gui_handle_drop(0, 0, 0, fnames, n);
2177 [self handleOpenWithArguments:args];
2181 - (void)handleDropString:(NSData *)data
2186 char_u dropkey[3] = { CSI, KS_EXTRA, (char_u)KE_DROP };
2187 const void *bytes = [data bytes];
2188 int len = *((int*)bytes); bytes += sizeof(int);
2189 NSMutableString *string = [NSMutableString stringWithUTF8String:bytes];
2191 // Replace unrecognized end-of-line sequences with \x0a (line feed).
2192 NSRange range = { 0, [string length] };
2193 unsigned n = [string replaceOccurrencesOfString:@"\x0d\x0a"
2194 withString:@"\x0a" options:0
2197 n = [string replaceOccurrencesOfString:@"\x0d" withString:@"\x0a"
2198 options:0 range:range];
2201 len = [string lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
2202 char_u *s = (char_u*)[string UTF8String];
2204 if (input_conv.vc_type != CONV_NONE)
2205 s = string_convert(&input_conv, s, &len);
2207 dnd_yank_drag_data(s, len);
2209 if (input_conv.vc_type != CONV_NONE)
2212 add_to_input_buf(dropkey, sizeof(dropkey));
2216 - (void)startOdbEditWithArguments:(NSDictionary *)args
2218 #ifdef FEAT_ODB_EDITOR
2219 id obj = [args objectForKey:@"remoteID"];
2222 OSType serverID = [obj unsignedIntValue];
2223 NSString *remotePath = [args objectForKey:@"remotePath"];
2225 NSAppleEventDescriptor *token = nil;
2226 NSData *tokenData = [args objectForKey:@"remoteTokenData"];
2227 obj = [args objectForKey:@"remoteTokenDescType"];
2228 if (tokenData && obj) {
2229 DescType tokenType = [obj unsignedLongValue];
2230 token = [NSAppleEventDescriptor descriptorWithDescriptorType:tokenType
2234 NSArray *filenames = [args objectForKey:@"filenames"];
2235 unsigned i, numFiles = [filenames count];
2236 for (i = 0; i < numFiles; ++i) {
2237 NSString *filename = [filenames objectAtIndex:i];
2238 char_u *s = [filename vimStringSave];
2239 buf_T *buf = buflist_findname(s);
2243 if (buf->b_odb_token) {
2244 [(NSAppleEventDescriptor*)(buf->b_odb_token) release];
2245 buf->b_odb_token = NULL;
2248 if (buf->b_odb_fname) {
2249 vim_free(buf->b_odb_fname);
2250 buf->b_odb_fname = NULL;
2253 buf->b_odb_server_id = serverID;
2256 buf->b_odb_token = [token retain];
2258 buf->b_odb_fname = [remotePath vimStringSave];
2260 NSLog(@"WARNING: Could not find buffer '%@' for ODB editing.",
2264 #endif // FEAT_ODB_EDITOR
2267 - (void)handleXcodeMod:(NSData *)data
2270 const void *bytes = [data bytes];
2271 DescType type = *((DescType*)bytes); bytes += sizeof(DescType);
2272 unsigned len = *((unsigned*)bytes); bytes += sizeof(unsigned);
2276 NSAppleEventDescriptor *replyEvent = [NSAppleEventDescriptor
2277 descriptorWithDescriptorType:type
2283 - (void)handleOpenWithArguments:(NSDictionary *)args
2285 // ARGUMENT: DESCRIPTION:
2286 // -------------------------------------------------------------
2287 // filenames list of filenames
2288 // dontOpen don't open files specified in above argument
2289 // layout which layout to use to open files
2290 // selectionRange range of lines to select
2291 // searchText string to search for
2292 // cursorLine line to position the cursor on
2293 // cursorColumn column to position the cursor on
2294 // (only valid when "cursorLine" is set)
2295 // remoteID ODB parameter
2296 // remotePath ODB parameter
2297 // remoteTokenDescType ODB parameter
2298 // remoteTokenData ODB parameter
2300 //NSLog(@"%s%@ (starting=%d)", _cmd, args, starting);
2302 NSArray *filenames = [args objectForKey:@"filenames"];
2303 int i, numFiles = filenames ? [filenames count] : 0;
2304 BOOL openFiles = ![[args objectForKey:@"dontOpen"] boolValue];
2305 int layout = [[args objectForKey:@"layout"] intValue];
2307 // Change to directory of first file to open if this is an "unused" editor
2308 // (but do not do this if editing remotely).
2309 if (openFiles && numFiles > 0 && ![args objectForKey:@"remoteID"]
2310 && (starting || [self unusedEditor]) ) {
2311 char_u *s = [[filenames objectAtIndex:0] vimStringSave];
2317 // When Vim is starting we simply add the files to be opened to the
2318 // global arglist and Vim will take care of opening them for us.
2319 if (openFiles && numFiles > 0) {
2320 for (i = 0; i < numFiles; i++) {
2321 NSString *fname = [filenames objectAtIndex:i];
2324 if (ga_grow(&global_alist.al_ga, 1) == FAIL
2325 || (p = [fname vimStringSave]) == NULL)
2326 exit(2); // See comment in -[MMBackend exit]
2328 alist_add(&global_alist, p, 2);
2331 // Vim will take care of arranging the files added to the arglist
2332 // in windows or tabs; all we must do is to specify which layout to
2334 initialWindowLayout = layout;
2337 // When Vim is already open we resort to some trickery to open the
2338 // files with the specified layout.
2340 // TODO: Figure out a better way to handle this?
2341 if (openFiles && numFiles > 0) {
2342 BOOL oneWindowInTab = topframe ? YES
2343 : (topframe->fr_layout == FR_LEAF);
2344 BOOL bufChanged = NO;
2345 BOOL bufHasFilename = NO;
2347 bufChanged = curbufIsChanged();
2348 bufHasFilename = curbuf->b_ffname != NULL;
2351 // Temporarily disable flushing since the following code may
2352 // potentially cause multiple redraws.
2353 flushDisabled = YES;
2355 BOOL onlyOneTab = (first_tabpage->tp_next == NULL);
2356 if (WIN_TABS == layout && !onlyOneTab) {
2357 // By going to the last tabpage we ensure that the new tabs
2358 // will appear last (if this call is left out, the taborder
2363 // Make sure we're in normal mode first.
2364 [self addInput:@"<C-\\><C-N>"];
2367 // With "split layout" we open a new tab before opening
2368 // multiple files if the current tab has more than one window
2369 // or if there is exactly one window but whose buffer has a
2370 // filename. (The :drop command ensures modified buffers get
2371 // their own window.)
2372 if ((WIN_HOR == layout || WIN_VER == layout) &&
2373 (!oneWindowInTab || bufHasFilename))
2374 [self addInput:@":tabnew<CR>"];
2376 // The files are opened by constructing a ":drop ..." command
2377 // and executing it.
2378 NSMutableString *cmd = (WIN_TABS == layout)
2379 ? [NSMutableString stringWithString:@":tab drop"]
2380 : [NSMutableString stringWithString:@":drop"];
2382 for (i = 0; i < numFiles; ++i) {
2383 NSString *file = [filenames objectAtIndex:i];
2384 file = [file stringByEscapingSpecialFilenameCharacters];
2385 [cmd appendString:@" "];
2386 [cmd appendString:file];
2389 // Temporarily clear 'suffixes' so that the files are opened in
2390 // the same order as they appear in the "filenames" array.
2391 [self addInput:@":let mvim_oldsu=&su|set su=<CR>"];
2393 [self addInput:cmd];
2395 // Split the view into multiple windows if requested.
2396 if (WIN_HOR == layout)
2397 [self addInput:@"|sall"];
2398 else if (WIN_VER == layout)
2399 [self addInput:@"|vert sall"];
2401 // Restore the old value of 'suffixes'.
2402 [self addInput:@"|let &su=mvim_oldsu|unlet mvim_oldsu<CR>"];
2404 // When opening one file we try to reuse the current window,
2405 // but not if its buffer is modified or has a filename.
2406 // However, the 'arglist' layout always opens the file in the
2408 NSString *file = [[filenames lastObject]
2409 stringByEscapingSpecialFilenameCharacters];
2411 if (WIN_HOR == layout) {
2412 if (!(bufHasFilename || bufChanged))
2413 cmd = [NSString stringWithFormat:@":e %@", file];
2415 cmd = [NSString stringWithFormat:@":sp %@", file];
2416 } else if (WIN_VER == layout) {
2417 if (!(bufHasFilename || bufChanged))
2418 cmd = [NSString stringWithFormat:@":e %@", file];
2420 cmd = [NSString stringWithFormat:@":vsp %@", file];
2421 } else if (WIN_TABS == layout) {
2422 if (oneWindowInTab && !(bufHasFilename || bufChanged))
2423 cmd = [NSString stringWithFormat:@":e %@", file];
2425 cmd = [NSString stringWithFormat:@":tabe %@", file];
2427 // (The :drop command will split if there is a modified
2429 cmd = [NSString stringWithFormat:@":drop %@", file];
2432 [self addInput:cmd];
2433 [self addInput:@"<CR>"];
2436 // Force screen redraw (does it have to be this complicated?).
2437 // (This code was taken from the end of gui_handle_drop().)
2438 update_screen(NOT_VALID);
2441 gui_update_cursor(FALSE, FALSE);
2448 if ([args objectForKey:@"remoteID"]) {
2449 // NOTE: We have to delay processing any ODB related arguments since
2450 // the file(s) may not be opened until the input buffer is processed.
2451 [self performSelectorOnMainThread:@selector(startOdbEditWithArguments:)
2456 NSString *lineString = [args objectForKey:@"cursorLine"];
2457 if (lineString && [lineString intValue] > 0) {
2458 NSString *columnString = [args objectForKey:@"cursorColumn"];
2459 if (!(columnString && [columnString intValue] > 0))
2460 columnString = @"1";
2462 NSString *cmd = [NSString stringWithFormat:@"<C-\\><C-N>:cal "
2463 "cursor(%@,%@)|norm! zz<CR>:f<CR>", lineString, columnString];
2464 [self addInput:cmd];
2467 NSString *rangeString = [args objectForKey:@"selectionRange"];
2469 // Build a command line string that will select the given range of
2470 // lines. If range.length == 0, then position the cursor on the given
2471 // line but do not select.
2472 NSRange range = NSRangeFromString(rangeString);
2474 if (range.length > 0) {
2475 cmd = [NSString stringWithFormat:@"<C-\\><C-N>%dGV%dGz.0",
2476 NSMaxRange(range), range.location];
2478 cmd = [NSString stringWithFormat:@"<C-\\><C-N>%dGz.0",
2482 [self addInput:cmd];
2485 NSString *searchText = [args objectForKey:@"searchText"];
2487 // TODO: Searching is an exclusive motion, so if the pattern would
2488 // match on row 0 column 0 then this pattern will miss that match.
2489 [self addInput:[NSString stringWithFormat:@"<C-\\><C-N>gg/\\c%@<CR>",
2494 - (BOOL)checkForModifiedBuffers
2497 for (buf = firstbuf; buf != NULL; buf = buf->b_next) {
2498 if (bufIsChanged(buf)) {
2506 - (void)addInput:(NSString *)input
2508 // NOTE: This code is essentially identical to server_to_input_buf(),
2509 // except the 'silent' flag is TRUE in the call to ins_typebuf() below.
2510 char_u *string = [input vimStringSave];
2511 if (!string) return;
2513 /* Set 'cpoptions' the way we want it.
2514 * B set - backslashes are *not* treated specially
2515 * k set - keycodes are *not* reverse-engineered
2516 * < unset - <Key> sequences *are* interpreted
2517 * The last but one parameter of replace_termcodes() is TRUE so that the
2518 * <lt> sequence is recognised - needed for a real backslash.
2521 char_u *cpo_save = p_cpo;
2522 p_cpo = (char_u *)"Bk";
2523 char_u *str = replace_termcodes((char_u *)string, &ptr, FALSE, TRUE, FALSE);
2526 if (*ptr != NUL) /* trailing CTRL-V results in nothing */
2529 * Add the string to the input stream.
2530 * Can't use add_to_input_buf() here, we now have K_SPECIAL bytes.
2532 * First clear typed characters from the typeahead buffer, there could
2533 * be half a mapping there. Then append to the existing string, so
2534 * that multiple commands from a client are concatenated.
2536 if (typebuf.tb_maplen < typebuf.tb_len)
2537 del_typebuf(typebuf.tb_len - typebuf.tb_maplen, typebuf.tb_maplen);
2538 (void)ins_typebuf(str, REMAP_NONE, typebuf.tb_len, TRUE, TRUE);
2540 /* Let input_available() know we inserted text in the typeahead
2542 typebuf_was_filled = TRUE;
2548 - (BOOL)unusedEditor
2550 BOOL oneWindowInTab = topframe ? YES
2551 : (topframe->fr_layout == FR_LEAF);
2552 BOOL bufChanged = NO;
2553 BOOL bufHasFilename = NO;
2555 bufChanged = curbufIsChanged();
2556 bufHasFilename = curbuf->b_ffname != NULL;
2559 BOOL onlyOneTab = (first_tabpage->tp_next == NULL);
2561 return onlyOneTab && oneWindowInTab && !bufChanged && !bufHasFilename;
2564 - (void)redrawScreen
2566 // Force screen redraw (does it have to be this complicated?).
2567 redraw_all_later(CLEAR);
2568 update_screen(NOT_VALID);
2571 gui_update_cursor(FALSE, FALSE);
2573 // HACK! The cursor is not put back at the command line by the above
2574 // "redraw commands". The following test seems to do the trick though.
2575 if (State & CMDLINE)
2579 - (void)handleFindReplace:(NSDictionary *)args
2583 NSString *findString = [args objectForKey:@"find"];
2584 if (!findString) return;
2586 char_u *find = [findString vimStringSave];
2587 char_u *replace = [[args objectForKey:@"replace"] vimStringSave];
2588 int flags = [[args objectForKey:@"flags"] intValue];
2590 // NOTE: The flag 0x100 is used to indicate a backward search.
2591 gui_do_findrepl(flags, find, replace, (flags & 0x100) == 0);
2597 @end // MMBackend (Private)
2602 @implementation MMBackend (ClientServer)
2604 - (NSString *)connectionNameFromServerName:(NSString *)name
2606 NSString *bundlePath = [[NSBundle mainBundle] bundlePath];
2608 return [[NSString stringWithFormat:@"%@.%@", bundlePath, name]
2612 - (NSConnection *)connectionForServerName:(NSString *)name
2614 // TODO: Try 'name%d' if 'name' fails.
2615 NSString *connName = [self connectionNameFromServerName:name];
2616 NSConnection *svrConn = [connectionNameDict objectForKey:connName];
2619 svrConn = [NSConnection connectionWithRegisteredName:connName
2621 // Try alternate server...
2622 if (!svrConn && alternateServerName) {
2623 //NSLog(@" trying to connect to alternate server: %@",
2624 // alternateServerName);
2625 connName = [self connectionNameFromServerName:alternateServerName];
2626 svrConn = [NSConnection connectionWithRegisteredName:connName
2630 // Try looking for alternate servers...
2632 //NSLog(@" looking for alternate servers...");
2633 NSString *alt = [self alternateServerNameForName:name];
2634 if (alt != alternateServerName) {
2635 //NSLog(@" found alternate server: %@", string);
2636 [alternateServerName release];
2637 alternateServerName = [alt copy];
2641 // Try alternate server again...
2642 if (!svrConn && alternateServerName) {
2643 //NSLog(@" trying to connect to alternate server: %@",
2644 // alternateServerName);
2645 connName = [self connectionNameFromServerName:alternateServerName];
2646 svrConn = [NSConnection connectionWithRegisteredName:connName
2651 [connectionNameDict setObject:svrConn forKey:connName];
2653 //NSLog(@"Adding %@ as connection observer for %@", self, svrConn);
2654 [[NSNotificationCenter defaultCenter] addObserver:self
2655 selector:@selector(serverConnectionDidDie:)
2656 name:NSConnectionDidDieNotification object:svrConn];
2663 - (NSConnection *)connectionForServerPort:(int)port
2666 NSEnumerator *e = [connectionNameDict objectEnumerator];
2668 while ((conn = [e nextObject])) {
2669 // HACK! Assume connection uses mach ports.
2670 if (port == [(NSMachPort*)[conn sendPort] machPort])
2677 - (void)serverConnectionDidDie:(NSNotification *)notification
2679 //NSLog(@"%s%@", _cmd, notification);
2681 NSConnection *svrConn = [notification object];
2683 //NSLog(@"Removing %@ as connection observer from %@", self, svrConn);
2684 [[NSNotificationCenter defaultCenter]
2686 name:NSConnectionDidDieNotification
2689 [connectionNameDict removeObjectsForKeys:
2690 [connectionNameDict allKeysForObject:svrConn]];
2692 // HACK! Assume connection uses mach ports.
2693 int port = [(NSMachPort*)[svrConn sendPort] machPort];
2694 NSNumber *key = [NSNumber numberWithInt:port];
2696 [clientProxyDict removeObjectForKey:key];
2697 [serverReplyDict removeObjectForKey:key];
2700 - (void)addClient:(NSDistantObject *)client
2702 NSConnection *conn = [client connectionForProxy];
2703 // HACK! Assume connection uses mach ports.
2704 int port = [(NSMachPort*)[conn sendPort] machPort];
2705 NSNumber *key = [NSNumber numberWithInt:port];
2707 if (![clientProxyDict objectForKey:key]) {
2708 [client setProtocolForProxy:@protocol(MMVimClientProtocol)];
2709 [clientProxyDict setObject:client forKey:key];
2712 // NOTE: 'clientWindow' is a global variable which is used by <client>
2713 clientWindow = port;
2716 - (NSString *)alternateServerNameForName:(NSString *)name
2718 if (!(name && [name length] > 0))
2721 // Only look for alternates if 'name' doesn't end in a digit.
2722 unichar lastChar = [name characterAtIndex:[name length]-1];
2723 if (lastChar >= '0' && lastChar <= '9')
2726 // Look for alternates among all current servers.
2727 NSArray *list = [self serverList];
2728 if (!(list && [list count] > 0))
2731 // Filter out servers starting with 'name' and ending with a number. The
2732 // (?i) pattern ensures that the match is case insensitive.
2733 NSString *pat = [NSString stringWithFormat:@"(?i)%@[0-9]+\\z", name];
2734 NSPredicate *pred = [NSPredicate predicateWithFormat:
2735 @"SELF MATCHES %@", pat];
2736 list = [list filteredArrayUsingPredicate:pred];
2737 if ([list count] > 0) {
2738 list = [list sortedArrayUsingSelector:@selector(serverNameCompare:)];
2739 return [list objectAtIndex:0];
2745 @end // MMBackend (ClientServer)
2750 @implementation NSString (MMServerNameCompare)
2751 - (NSComparisonResult)serverNameCompare:(NSString *)string
2753 return [self compare:string
2754 options:NSCaseInsensitiveSearch|NSNumericSearch];
2761 static int eventModifierFlagsToVimModMask(int modifierFlags)
2765 if (modifierFlags & NSShiftKeyMask)
2766 modMask |= MOD_MASK_SHIFT;
2767 if (modifierFlags & NSControlKeyMask)
2768 modMask |= MOD_MASK_CTRL;
2769 if (modifierFlags & NSAlternateKeyMask)
2770 modMask |= MOD_MASK_ALT;
2771 if (modifierFlags & NSCommandKeyMask)
2772 modMask |= MOD_MASK_CMD;
2777 static int eventModifierFlagsToVimMouseModMask(int modifierFlags)
2781 if (modifierFlags & NSShiftKeyMask)
2782 modMask |= MOUSE_SHIFT;
2783 if (modifierFlags & NSControlKeyMask)
2784 modMask |= MOUSE_CTRL;
2785 if (modifierFlags & NSAlternateKeyMask)
2786 modMask |= MOUSE_ALT;
2791 static int eventButtonNumberToVimMouseButton(int buttonNumber)
2793 static int mouseButton[] = { MOUSE_LEFT, MOUSE_RIGHT, MOUSE_MIDDLE };
2795 return (buttonNumber >= 0 && buttonNumber < 3)
2796 ? mouseButton[buttonNumber] : -1;
2801 // This function is modeled after the VimToPython function found in if_python.c
2802 // NB This does a deep copy by value, it does not lookup references like the
2803 // VimToPython function does. This is because I didn't want to deal with the
2804 // retain cycles that this would create, and we can cover 99% of the use cases
2805 // by ignoring it. If we ever switch to using GC in MacVim then this
2806 // functionality can be implemented easily.
2807 static id vimToCocoa(typval_T * tv, int depth)
2813 // Avoid infinite recursion
2818 if (tv->v_type == VAR_STRING) {
2819 char_u * val = tv->vval.v_string;
2820 // val can be NULL if the string is empty
2822 result = [NSString string];
2825 val = CONVERT_TO_UTF8(val);
2827 result = [NSString stringWithUTF8String:(char*)val];
2829 CONVERT_TO_UTF8_FREE(val);
2832 } else if (tv->v_type == VAR_NUMBER) {
2833 // looks like sizeof(varnumber_T) is always <= sizeof(long)
2834 result = [NSNumber numberWithLong:(long)tv->vval.v_number];
2835 } else if (tv->v_type == VAR_LIST) {
2836 list_T * list = tv->vval.v_list;
2839 NSMutableArray * arr = result = [NSMutableArray array];
2842 for (curr = list->lv_first; curr != NULL; curr = curr->li_next) {
2843 newObj = vimToCocoa(&curr->li_tv, depth + 1);
2844 [arr addObject:newObj];
2847 } else if (tv->v_type == VAR_DICT) {
2848 NSMutableDictionary * dict = result = [NSMutableDictionary dictionary];
2850 if (tv->vval.v_dict != NULL) {
2851 hashtab_T * ht = &tv->vval.v_dict->dv_hashtab;
2852 int todo = ht->ht_used;
2856 for (hi = ht->ht_array; todo > 0; ++hi) {
2857 if (!HASHITEM_EMPTY(hi)) {
2860 di = dict_lookup(hi);
2861 newObj = vimToCocoa(&di->di_tv, depth + 1);
2863 char_u * keyval = hi->hi_key;
2865 keyval = CONVERT_TO_UTF8(keyval);
2867 NSString * key = [NSString stringWithUTF8String:(char*)keyval];
2869 CONVERT_TO_UTF8_FREE(keyval);
2871 [dict setObject:newObj forKey:key];
2875 } else { // only func refs should fall into this category?
2883 // This function is modeled after eval_client_expr_to_string found in main.c
2884 // Returns nil if there was an error evaluating the expression, and writes a
2885 // message to errorStr.
2886 // TODO Get the error that occurred while evaluating the expression in vim
2888 static id evalExprCocoa(NSString * expr, NSString ** errstr)
2891 char_u *s = (char_u*)[expr UTF8String];
2894 s = CONVERT_FROM_UTF8(s);
2897 int save_dbl = debug_break_level;
2898 int save_ro = redir_off;
2900 debug_break_level = -1;
2904 typval_T * tvres = eval_expr(s, NULL);
2906 debug_break_level = save_dbl;
2907 redir_off = save_ro;
2914 CONVERT_FROM_UTF8_FREE(s);
2919 gui_update_cursor(FALSE, FALSE);
2922 if (tvres == NULL) {
2924 *errstr = @"Expression evaluation failed.";
2927 id res = vimToCocoa(tvres, 1);
2932 *errstr = @"Conversion to cocoa values failed.";
2940 @implementation NSString (VimStrings)
2942 + (id)stringWithVimString:(char_u *)s
2944 // This method ensures a non-nil string is returned. If 's' cannot be
2945 // converted to a utf-8 string it is assumed to be latin-1. If conversion
2946 // still fails an empty NSString is returned.
2947 NSString *string = nil;
2950 s = CONVERT_TO_UTF8(s);
2952 string = [NSString stringWithUTF8String:(char*)s];
2954 // HACK! Apparently 's' is not a valid utf-8 string, maybe it is
2956 string = [NSString stringWithCString:(char*)s
2957 encoding:NSISOLatin1StringEncoding];
2960 CONVERT_TO_UTF8_FREE(s);
2964 return string != nil ? string : [NSString string];
2967 - (char_u *)vimStringSave
2969 char_u *s = (char_u*)[self UTF8String], *ret = NULL;
2972 s = CONVERT_FROM_UTF8(s);
2974 ret = vim_strsave(s);
2976 CONVERT_FROM_UTF8_FREE(s);
2982 @end // NSString (VimStrings)