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 if (InsertTextMsgID == msgid && data != nil) {
1041 const void *bytes = [data bytes];
1042 bytes += sizeof(int);
1043 int len = *((int*)bytes); bytes += sizeof(int);
1045 char_u *str = (char_u*)bytes;
1046 if ((str[0] == Ctrl_C && ctrl_c_interrupts) ||
1047 (str[0] == intr_char && intr_char != Ctrl_C)) {
1049 [inputQueue removeAllObjects];
1053 } else if (TerminateNowMsgID == msgid) {
1054 // Terminate immediately (the frontend is about to quit or this process
1056 isTerminating = YES;
1061 // Remove all previous instances of this message from the input queue, else
1062 // the input queue may fill up as a result of Vim not being able to keep up
1063 // with the speed at which new messages are received.
1064 // Keyboard input is never dropped, unless the input represents and
1065 // auto-repeated key.
1067 BOOL isKeyRepeat = NO;
1068 BOOL isKeyboardInput = NO;
1070 if (data && (InsertTextMsgID == msgid || KeyDownMsgID == msgid ||
1071 CmdKeyMsgID == msgid)) {
1072 isKeyboardInput = YES;
1074 // The lowest bit of the first int is set if this key is a repeat.
1075 int flags = *((int*)[data bytes]);
1080 // Keyboard input is not removed from the queue; repeats are ignored if
1081 // there already is keyboard input on the input queue.
1082 if (isKeyRepeat || !isKeyboardInput) {
1083 int i, count = [inputQueue count];
1084 for (i = 1; i < count; i+=2) {
1085 if ([[inputQueue objectAtIndex:i-1] intValue] == msgid) {
1089 [inputQueue removeObjectAtIndex:i];
1090 [inputQueue removeObjectAtIndex:i-1];
1096 [inputQueue addObject:[NSNumber numberWithInt:msgid]];
1097 [inputQueue addObject:(data ? (id)data : [NSNull null])];
1100 - (oneway void)processInputAndData:(in bycopy NSArray *)messages
1102 // This is just a convenience method that allows the frontend to delay
1103 // sending messages.
1104 int i, count = [messages count];
1105 for (i = 1; i < count; i+=2)
1106 [self processInput:[[messages objectAtIndex:i-1] intValue]
1107 data:[messages objectAtIndex:i]];
1110 - (id)evaluateExpressionCocoa:(in bycopy NSString *)expr
1111 errorString:(out bycopy NSString **)errstr
1113 return evalExprCocoa(expr, errstr);
1117 - (NSString *)evaluateExpression:(in bycopy NSString *)expr
1119 NSString *eval = nil;
1120 char_u *s = (char_u*)[expr UTF8String];
1123 s = CONVERT_FROM_UTF8(s);
1126 char_u *res = eval_client_expr_to_string(s);
1129 CONVERT_FROM_UTF8_FREE(s);
1135 s = CONVERT_TO_UTF8(s);
1137 eval = [NSString stringWithUTF8String:(char*)s];
1139 CONVERT_TO_UTF8_FREE(s);
1147 - (BOOL)starRegisterToPasteboard:(byref NSPasteboard *)pboard
1149 // TODO: This method should share code with clip_mch_request_selection().
1151 if (VIsual_active && (State & NORMAL) && clip_star.available) {
1152 // If there is no pasteboard, return YES to indicate that there is text
1157 clip_copy_selection();
1159 // Get the text to put on the pasteboard.
1160 long_u llen = 0; char_u *str = 0;
1161 int type = clip_convert_selection(&str, &llen, &clip_star);
1165 // TODO: Avoid overflow.
1166 int len = (int)llen;
1168 if (output_conv.vc_type != CONV_NONE) {
1169 char_u *conv_str = string_convert(&output_conv, str, &len);
1177 NSString *string = [[NSString alloc]
1178 initWithBytes:str length:len encoding:NSUTF8StringEncoding];
1180 NSArray *types = [NSArray arrayWithObject:NSStringPboardType];
1181 [pboard declareTypes:types owner:nil];
1182 BOOL ok = [pboard setString:string forType:NSStringPboardType];
1193 - (oneway void)addReply:(in bycopy NSString *)reply
1194 server:(in byref id <MMVimServerProtocol>)server
1196 //NSLog(@"addReply:%@ server:%@", reply, (id)server);
1198 // Replies might come at any time and in any order so we keep them in an
1199 // array inside a dictionary with the send port used as key.
1201 NSConnection *conn = [(NSDistantObject*)server connectionForProxy];
1202 // HACK! Assume connection uses mach ports.
1203 int port = [(NSMachPort*)[conn sendPort] machPort];
1204 NSNumber *key = [NSNumber numberWithInt:port];
1206 NSMutableArray *replies = [serverReplyDict objectForKey:key];
1208 replies = [NSMutableArray array];
1209 [serverReplyDict setObject:replies forKey:key];
1212 [replies addObject:reply];
1215 - (void)addInput:(in bycopy NSString *)input
1216 client:(in byref id <MMVimClientProtocol>)client
1218 //NSLog(@"addInput:%@ client:%@", input, (id)client);
1220 // NOTE: We don't call addInput: here because it differs from
1221 // server_to_input_buf() in that it always sets the 'silent' flag and we
1222 // don't want the MacVim client/server code to behave differently from
1224 char_u *s = [input vimStringSave];
1225 server_to_input_buf(s);
1228 [self addClient:(id)client];
1231 - (NSString *)evaluateExpression:(in bycopy NSString *)expr
1232 client:(in byref id <MMVimClientProtocol>)client
1234 [self addClient:(id)client];
1235 return [self evaluateExpression:expr];
1238 - (void)registerServerWithName:(NSString *)name
1240 NSString *svrName = name;
1241 NSConnection *svrConn = [NSConnection defaultConnection];
1244 for (i = 0; i < MMServerMax; ++i) {
1245 NSString *connName = [self connectionNameFromServerName:svrName];
1247 if ([svrConn registerName:connName]) {
1248 //NSLog(@"Registered server with name: %@", svrName);
1250 // TODO: Set request/reply time-outs to something else?
1252 // Don't wait for requests (time-out means that the message is
1254 [svrConn setRequestTimeout:0];
1255 //[svrConn setReplyTimeout:MMReplyTimeout];
1256 [svrConn setRootObject:self];
1258 // NOTE: 'serverName' is a global variable
1259 serverName = [svrName vimStringSave];
1261 set_vim_var_string(VV_SEND_SERVER, serverName, -1);
1264 need_maketitle = TRUE;
1266 [self queueMessage:SetServerNameMsgID data:
1267 [svrName dataUsingEncoding:NSUTF8StringEncoding]];
1271 svrName = [NSString stringWithFormat:@"%@%d", name, i+1];
1275 - (BOOL)sendToServer:(NSString *)name string:(NSString *)string
1276 reply:(char_u **)reply port:(int *)port expression:(BOOL)expr
1279 // NOTE: If 'name' equals 'serverName' then the request is local (client
1280 // and server are the same). This case is not handled separately, so a
1281 // connection will be set up anyway (this simplifies the code).
1283 NSConnection *conn = [self connectionForServerName:name];
1286 char_u *s = (char_u*)[name UTF8String];
1288 s = CONVERT_FROM_UTF8(s);
1290 EMSG2(_(e_noserver), s);
1292 CONVERT_FROM_UTF8_FREE(s);
1299 // HACK! Assume connection uses mach ports.
1300 *port = [(NSMachPort*)[conn sendPort] machPort];
1303 id proxy = [conn rootProxy];
1304 [proxy setProtocolForProxy:@protocol(MMVimServerProtocol)];
1308 NSString *eval = [proxy evaluateExpression:string client:self];
1311 *reply = [eval vimStringSave];
1313 *reply = vim_strsave((char_u*)_(e_invexprmsg));
1320 [proxy addInput:string client:self];
1323 @catch (NSException *e) {
1324 NSLog(@"WARNING: Caught exception in %s: \"%@\"", _cmd, e);
1331 - (NSArray *)serverList
1333 NSArray *list = nil;
1335 if ([self connection]) {
1336 id proxy = [connection rootProxy];
1337 [proxy setProtocolForProxy:@protocol(MMAppProtocol)];
1340 list = [proxy serverList];
1342 @catch (NSException *e) {
1343 NSLog(@"Exception caught when listing servers: \"%@\"", e);
1346 EMSG(_("E???: No connection to MacVim, server listing not possible."));
1352 - (NSString *)peekForReplyOnPort:(int)port
1354 //NSLog(@"%s%d", _cmd, port);
1356 NSNumber *key = [NSNumber numberWithInt:port];
1357 NSMutableArray *replies = [serverReplyDict objectForKey:key];
1358 if (replies && [replies count]) {
1359 //NSLog(@" %d replies, topmost is: %@", [replies count],
1360 // [replies objectAtIndex:0]);
1361 return [replies objectAtIndex:0];
1364 //NSLog(@" No replies");
1368 - (NSString *)waitForReplyOnPort:(int)port
1370 //NSLog(@"%s%d", _cmd, port);
1372 NSConnection *conn = [self connectionForServerPort:port];
1376 NSNumber *key = [NSNumber numberWithInt:port];
1377 NSMutableArray *replies = nil;
1378 NSString *reply = nil;
1380 // Wait for reply as long as the connection to the server is valid (unless
1381 // user interrupts wait with Ctrl-C).
1382 while (!got_int && [conn isValid] &&
1383 !(replies = [serverReplyDict objectForKey:key])) {
1384 [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
1385 beforeDate:[NSDate distantFuture]];
1389 if ([replies count] > 0) {
1390 reply = [[replies objectAtIndex:0] retain];
1391 //NSLog(@" Got reply: %@", reply);
1392 [replies removeObjectAtIndex:0];
1393 [reply autorelease];
1396 if ([replies count] == 0)
1397 [serverReplyDict removeObjectForKey:key];
1403 - (BOOL)sendReply:(NSString *)reply toPort:(int)port
1405 id client = [clientProxyDict objectForKey:[NSNumber numberWithInt:port]];
1408 //NSLog(@"sendReply:%@ toPort:%d", reply, port);
1409 [client addReply:reply server:self];
1412 @catch (NSException *e) {
1413 NSLog(@"WARNING: Exception caught in %s: \"%@\"", _cmd, e);
1416 EMSG2(_("E???: server2client failed; no client with id 0x%x"), port);
1427 - (void)setWaitForAck:(BOOL)yn
1432 - (void)waitForConnectionAcknowledgement
1434 if (!waitForAck) return;
1436 while (waitForAck && !got_int && [connection isValid]) {
1437 [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
1438 beforeDate:[NSDate distantFuture]];
1439 //NSLog(@" waitForAck=%d got_int=%d isValid=%d",
1440 // waitForAck, got_int, [connection isValid]);
1444 // Never received a connection acknowledgement, so die.
1445 [[NSNotificationCenter defaultCenter] removeObserver:self];
1446 [frontendProxy release]; frontendProxy = nil;
1448 // NOTE: We intentionally do not call mch_exit() since this in turn
1449 // will lead to -[MMBackend exit] getting called which we want to
1454 [self processInputQueue];
1457 - (oneway void)acknowledgeConnection
1459 //NSLog(@"%s", _cmd);
1467 @implementation MMBackend (Private)
1469 - (void)waitForDialogReturn
1471 // Keep processing the run loop until a dialog returns. To avoid getting
1472 // stuck in an endless loop (could happen if the setDialogReturn: message
1473 // was lost) we also do some paranoia checks.
1475 // Note that in Cocoa the user can still resize windows and select menu
1476 // items while a sheet is being displayed, so we can't just wait for the
1477 // first message to arrive and assume that is the setDialogReturn: call.
1479 while (nil == dialogReturn && !got_int && [connection isValid])
1480 [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
1481 beforeDate:[NSDate distantFuture]];
1483 // Search for any resize messages on the input queue. All other messages
1484 // on the input queue are dropped. The reason why we single out resize
1485 // messages is because the user may have resized the window while a sheet
1487 int i, count = [inputQueue count];
1489 id textDimData = nil;
1491 for (i = count-2; i >= 0; i -= 2) {
1492 int msgid = [[inputQueue objectAtIndex:i] intValue];
1493 if (SetTextDimensionsMsgID == msgid) {
1494 textDimData = [[inputQueue objectAtIndex:i+1] retain];
1500 [inputQueue removeAllObjects];
1503 [inputQueue addObject:
1504 [NSNumber numberWithInt:SetTextDimensionsMsgID]];
1505 [inputQueue addObject:textDimData];
1506 [textDimData release];
1511 - (void)insertVimStateMessage
1513 // NOTE: This is the place to add Vim state that needs to be accessed from
1514 // MacVim. Do not add state that could potentially require lots of memory
1515 // since this message gets sent each time the output queue is forcibly
1516 // flushed (e.g. storing the currently selected text would be a bad idea).
1517 // We take this approach of "pushing" the state to MacVim to avoid having
1518 // to make synchronous calls from MacVim to Vim in order to get state.
1520 BOOL mmta = curbuf ? curbuf->b_p_mmta : NO;
1521 int numTabs = tabpage_index(NULL) - 1;
1525 NSDictionary *vimState = [NSDictionary dictionaryWithObjectsAndKeys:
1526 [[NSFileManager defaultManager] currentDirectoryPath], @"pwd",
1527 [NSNumber numberWithInt:p_mh], @"p_mh",
1528 [NSNumber numberWithBool:[self unusedEditor]], @"unusedEditor",
1529 [NSNumber numberWithBool:mmta], @"p_mmta",
1530 [NSNumber numberWithInt:numTabs], @"numTabs",
1533 // Put the state before all other messages.
1534 int msgid = SetVimStateMsgID;
1535 [outputQueue insertObject:[vimState dictionaryAsData] atIndex:0];
1536 [outputQueue insertObject:[NSData dataWithBytes:&msgid length:sizeof(int)]
1540 - (void)processInputQueue
1542 if ([inputQueue count] == 0) return;
1544 // NOTE: One of the input events may cause this method to be called
1545 // recursively, so copy the input queue to a local variable and clear the
1546 // queue before starting to process input events (otherwise we could get
1547 // stuck in an endless loop).
1548 NSArray *q = [inputQueue copy];
1549 unsigned i, count = [q count];
1551 [inputQueue removeAllObjects];
1553 for (i = 1; i < count; i+=2) {
1554 int msgid = [[q objectAtIndex:i-1] intValue];
1555 id data = [q objectAtIndex:i];
1556 if ([data isEqual:[NSNull null]])
1559 //NSLog(@"(%d) %s:%s", i, _cmd, MessageStrings[msgid]);
1560 [self handleInputEvent:msgid data:data];
1564 //NSLog(@"Clear input event queue");
1567 - (void)handleInputEvent:(int)msgid data:(NSData *)data
1569 if (InsertTextMsgID == msgid || KeyDownMsgID == msgid ||
1570 CmdKeyMsgID == msgid) {
1572 const void *bytes = [data bytes];
1573 int mods = *((int*)bytes); bytes += sizeof(int);
1574 int len = *((int*)bytes); bytes += sizeof(int);
1575 NSString *key = [[NSString alloc] initWithBytes:bytes
1577 encoding:NSUTF8StringEncoding];
1578 mods = eventModifierFlagsToVimModMask(mods);
1580 if (InsertTextMsgID == msgid)
1581 [self handleInsertText:key];
1583 [self handleKeyDown:key modifiers:mods];
1586 } else if (ScrollWheelMsgID == msgid) {
1588 const void *bytes = [data bytes];
1590 int row = *((int*)bytes); bytes += sizeof(int);
1591 int col = *((int*)bytes); bytes += sizeof(int);
1592 int flags = *((int*)bytes); bytes += sizeof(int);
1593 float dy = *((float*)bytes); bytes += sizeof(float);
1595 int button = MOUSE_5;
1596 if (dy > 0) button = MOUSE_4;
1598 flags = eventModifierFlagsToVimMouseModMask(flags);
1600 int numLines = (int)round(dy);
1601 if (numLines < 0) numLines = -numLines;
1602 if (numLines == 0) numLines = 1;
1604 #ifdef FEAT_GUI_SCROLL_WHEEL_FORCE
1605 gui.scroll_wheel_force = numLines;
1608 gui_send_mouse_event(button, col, row, NO, flags);
1609 } else if (MouseDownMsgID == msgid) {
1611 const void *bytes = [data bytes];
1613 int row = *((int*)bytes); bytes += sizeof(int);
1614 int col = *((int*)bytes); bytes += sizeof(int);
1615 int button = *((int*)bytes); bytes += sizeof(int);
1616 int flags = *((int*)bytes); bytes += sizeof(int);
1617 int count = *((int*)bytes); bytes += sizeof(int);
1619 button = eventButtonNumberToVimMouseButton(button);
1621 flags = eventModifierFlagsToVimMouseModMask(flags);
1622 gui_send_mouse_event(button, col, row, count>1, flags);
1624 } else if (MouseUpMsgID == msgid) {
1626 const void *bytes = [data bytes];
1628 int row = *((int*)bytes); bytes += sizeof(int);
1629 int col = *((int*)bytes); bytes += sizeof(int);
1630 int flags = *((int*)bytes); bytes += sizeof(int);
1632 flags = eventModifierFlagsToVimMouseModMask(flags);
1634 gui_send_mouse_event(MOUSE_RELEASE, col, row, NO, flags);
1635 } else if (MouseDraggedMsgID == msgid) {
1637 const void *bytes = [data bytes];
1639 int row = *((int*)bytes); bytes += sizeof(int);
1640 int col = *((int*)bytes); bytes += sizeof(int);
1641 int flags = *((int*)bytes); bytes += sizeof(int);
1643 flags = eventModifierFlagsToVimMouseModMask(flags);
1645 gui_send_mouse_event(MOUSE_DRAG, col, row, NO, flags);
1646 } else if (MouseMovedMsgID == msgid) {
1647 const void *bytes = [data bytes];
1648 int row = *((int*)bytes); bytes += sizeof(int);
1649 int col = *((int*)bytes); bytes += sizeof(int);
1651 gui_mouse_moved(col, row);
1652 } else if (AddInputMsgID == msgid) {
1653 NSString *string = [[NSString alloc] initWithData:data
1654 encoding:NSUTF8StringEncoding];
1656 [self addInput:string];
1659 } else if (SelectTabMsgID == msgid) {
1661 const void *bytes = [data bytes];
1662 int idx = *((int*)bytes) + 1;
1663 //NSLog(@"Selecting tab %d", idx);
1664 send_tabline_event(idx);
1665 } else if (CloseTabMsgID == msgid) {
1667 const void *bytes = [data bytes];
1668 int idx = *((int*)bytes) + 1;
1669 //NSLog(@"Closing tab %d", idx);
1670 send_tabline_menu_event(idx, TABLINE_MENU_CLOSE);
1671 } else if (AddNewTabMsgID == msgid) {
1672 //NSLog(@"Adding new tab");
1673 send_tabline_menu_event(0, TABLINE_MENU_NEW);
1674 } else if (DraggedTabMsgID == msgid) {
1676 const void *bytes = [data bytes];
1677 // NOTE! The destination index is 0 based, so do not add 1 to make it 1
1679 int idx = *((int*)bytes);
1682 } else if (SetTextDimensionsMsgID == msgid || LiveResizeMsgID == msgid
1683 || SetTextRowsMsgID == msgid || SetTextColumnsMsgID == msgid) {
1685 const void *bytes = [data bytes];
1687 if (SetTextColumnsMsgID != msgid) {
1688 rows = *((int*)bytes); bytes += sizeof(int);
1691 if (SetTextRowsMsgID != msgid) {
1692 cols = *((int*)bytes); bytes += sizeof(int);
1696 if (SetTextRowsMsgID == msgid || SetTextColumnsMsgID == msgid) {
1697 int dim[2] = { rows, cols };
1698 d = [NSData dataWithBytes:dim length:2*sizeof(int)];
1699 msgid = SetTextDimensionsReplyMsgID;
1702 if (SetTextDimensionsMsgID == msgid)
1703 msgid = SetTextDimensionsReplyMsgID;
1705 // NOTE! Vim doesn't call gui_mch_set_shellsize() after
1706 // gui_resize_shell(), so we have to manually set the rows and columns
1707 // here since MacVim doesn't change the rows and columns to avoid
1708 // inconsistent states between Vim and MacVim. The message sent back
1709 // indicates that it is a reply to a message that originated in MacVim
1710 // since we need to be able to determine where a message originated.
1711 [self queueMessage:msgid data:d];
1713 //NSLog(@"[VimTask] Resizing shell to %dx%d.", cols, rows);
1714 gui_resize_shell(cols, rows);
1715 } else if (ExecuteMenuMsgID == msgid) {
1716 NSDictionary *attrs = [NSDictionary dictionaryWithData:data];
1718 NSArray *desc = [attrs objectForKey:@"descriptor"];
1719 vimmenu_T *menu = menu_for_descriptor(desc);
1723 } else if (ToggleToolbarMsgID == msgid) {
1724 [self handleToggleToolbar];
1725 } else if (ScrollbarEventMsgID == msgid) {
1726 [self handleScrollbarEvent:data];
1727 } else if (SetFontMsgID == msgid) {
1728 [self handleSetFont:data];
1729 } else if (VimShouldCloseMsgID == msgid) {
1731 } else if (DropFilesMsgID == msgid) {
1732 [self handleDropFiles:data];
1733 } else if (DropStringMsgID == msgid) {
1734 [self handleDropString:data];
1735 } else if (GotFocusMsgID == msgid) {
1737 [self focusChange:YES];
1738 } else if (LostFocusMsgID == msgid) {
1740 [self focusChange:NO];
1741 } else if (SetMouseShapeMsgID == msgid) {
1742 const void *bytes = [data bytes];
1743 int shape = *((int*)bytes); bytes += sizeof(int);
1744 update_mouseshape(shape);
1745 } else if (XcodeModMsgID == msgid) {
1746 [self handleXcodeMod:data];
1747 } else if (OpenWithArgumentsMsgID == msgid) {
1748 [self handleOpenWithArguments:[NSDictionary dictionaryWithData:data]];
1749 } else if (FindReplaceMsgID == msgid) {
1750 [self handleFindReplace:[NSDictionary dictionaryWithData:data]];
1752 NSLog(@"WARNING: Unknown message received (msgid=%d)", msgid);
1756 + (NSDictionary *)specialKeys
1758 static NSDictionary *specialKeys = nil;
1761 NSBundle *mainBundle = [NSBundle mainBundle];
1762 NSString *path = [mainBundle pathForResource:@"SpecialKeys"
1764 specialKeys = [[NSDictionary alloc] initWithContentsOfFile:path];
1770 - (void)handleInsertText:(NSString *)text
1774 char_u *str = (char_u*)[text UTF8String];
1775 int i, len = [text lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
1778 char_u *conv_str = NULL;
1779 if (input_conv.vc_type != CONV_NONE) {
1780 conv_str = string_convert(&input_conv, str, &len);
1786 for (i = 0; i < len; ++i) {
1787 add_to_input_buf(str+i, 1);
1788 if (CSI == str[i]) {
1789 // NOTE: If the converted string contains the byte CSI, then it
1790 // must be followed by the bytes KS_EXTRA, KE_CSI or things
1792 static char_u extra[2] = { KS_EXTRA, KE_CSI };
1793 add_to_input_buf(extra, 2);
1803 - (void)handleKeyDown:(NSString *)key modifiers:(int)mods
1805 // TODO: This code is a horrible mess -- clean up!
1808 char_u *chars = (char_u*)[key UTF8String];
1810 char_u *conv_str = NULL;
1812 int length = [key lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
1814 // Special keys (arrow keys, function keys, etc.) are stored in a plist so
1815 // that new keys can easily be added.
1816 NSString *specialString = [[MMBackend specialKeys]
1818 if (specialString && [specialString length] > 1) {
1819 //NSLog(@"special key: %@", specialString);
1820 int ikey = TO_SPECIAL([specialString characterAtIndex:0],
1821 [specialString characterAtIndex:1]);
1823 ikey = simplify_key(ikey, &mods);
1828 special[1] = K_SECOND(ikey);
1829 special[2] = K_THIRD(ikey);
1833 } else if (1 == length && TAB == chars[0]) {
1834 // Tab is a trouble child:
1835 // - <Tab> is added to the input buffer as is
1836 // - <S-Tab> is translated to, {CSI,'k','B'} (i.e. 'Back-tab')
1837 // - <M-Tab> should be 0x80|TAB but this is not valid utf-8 so it needs
1838 // to be converted to utf-8
1839 // - <S-M-Tab> is translated to <S-Tab> with ALT modifier
1840 // - <C-Tab> is reserved by Mac OS X
1841 // - <D-Tab> is reserved by Mac OS X
1846 if (mods & MOD_MASK_SHIFT) {
1847 mods &= ~MOD_MASK_SHIFT;
1849 special[1] = K_SECOND(K_S_TAB);
1850 special[2] = K_THIRD(K_S_TAB);
1852 } else if (mods & MOD_MASK_ALT) {
1853 int mtab = 0x80 | TAB;
1857 special[0] = (mtab >> 6) + 0xc0;
1858 special[1] = mtab & 0xbf;
1866 mods &= ~MOD_MASK_ALT;
1868 } else if (1 == length && chars[0] < 0x80 && (mods & MOD_MASK_ALT)) {
1869 // META key is treated separately. This code was taken from gui_w48.c
1870 // and gui_gtk_x11.c.
1872 int ch = simplify_key(chars[0], &mods);
1874 // Remove the SHIFT modifier for keys where it's already included,
1875 // e.g., '(' and '*'
1876 if (ch < 0x100 && !isalpha(ch) && isprint(ch))
1877 mods &= ~MOD_MASK_SHIFT;
1879 // Interpret the ALT key as making the key META, include SHIFT, etc.
1880 ch = extract_modifiers(ch, &mods);
1886 string[len++] = CSI;
1887 string[len++] = KS_MODIFIER;
1888 string[len++] = mods;
1891 if (IS_SPECIAL(ch)) {
1892 string[len++] = CSI;
1893 string[len++] = K_SECOND(ch);
1894 string[len++] = K_THIRD(ch);
1898 // TODO: What if 'enc' is not "utf-8"?
1899 if (enc_utf8 && (ch & 0x80)) { // convert to utf-8
1900 string[len++] = ch & 0xbf;
1901 string[len-2] = ((unsigned)ch >> 6) + 0xc0;
1902 if (string[len-1] == CSI) {
1903 string[len++] = KS_EXTRA;
1904 string[len++] = (int)KE_CSI;
1910 add_to_input_buf(string, len);
1912 } else if (length > 0) {
1913 unichar c = [key characterAtIndex:0];
1914 //NSLog(@"non-special: %@ (hex=%x, mods=%d)", key,
1915 // [key characterAtIndex:0], mods);
1917 // HACK! In most circumstances the Ctrl and Shift modifiers should be
1918 // cleared since they are already added to the key by the AppKit.
1919 // Unfortunately, the only way to deal with when to clear the modifiers
1920 // or not seems to be to have hard-wired rules like this.
1921 if ( !((' ' == c) || (0xa0 == c) || (mods & MOD_MASK_CMD)
1922 || 0x9 == c || 0xd == c || ESC == c) ) {
1923 mods &= ~MOD_MASK_SHIFT;
1924 mods &= ~MOD_MASK_CTRL;
1925 //NSLog(@"clear shift ctrl");
1929 if (input_conv.vc_type != CONV_NONE) {
1930 conv_str = string_convert(&input_conv, chars, &length);
1937 if (chars && length > 0) {
1939 //NSLog(@"adding mods: %d", mods);
1941 modChars[1] = KS_MODIFIER;
1943 add_to_input_buf(modChars, 3);
1946 //NSLog(@"add to input buf: 0x%x", chars[0]);
1947 // TODO: Check for CSI bytes?
1948 add_to_input_buf(chars, length);
1957 - (void)queueMessage:(int)msgid data:(NSData *)data
1959 //if (msgid != EnableMenuItemMsgID)
1960 // NSLog(@"queueMessage:%s", MessageStrings[msgid]);
1962 [outputQueue addObject:[NSData dataWithBytes:&msgid length:sizeof(int)]];
1964 [outputQueue addObject:data];
1966 [outputQueue addObject:[NSData data]];
1969 - (void)connectionDidDie:(NSNotification *)notification
1971 // If the main connection to MacVim is lost this means that either MacVim
1972 // has crashed or this process did not receive its termination message
1973 // properly (e.g. if the TerminateNowMsgID was dropped).
1975 // NOTE: This is not called if a Vim controller invalidates its connection.
1977 NSLog(@"WARNING[%s]: Main connection was lost before process had a chance "
1978 "to terminate; preserving swap files.", _cmd);
1979 getout_preserve_modified(1);
1982 - (void)blinkTimerFired:(NSTimer *)timer
1984 NSTimeInterval timeInterval = 0;
1986 [blinkTimer release];
1989 if (MMBlinkStateOn == blinkState) {
1990 gui_undraw_cursor();
1991 blinkState = MMBlinkStateOff;
1992 timeInterval = blinkOffInterval;
1993 } else if (MMBlinkStateOff == blinkState) {
1994 gui_update_cursor(TRUE, FALSE);
1995 blinkState = MMBlinkStateOn;
1996 timeInterval = blinkOnInterval;
1999 if (timeInterval > 0) {
2001 [[NSTimer scheduledTimerWithTimeInterval:timeInterval target:self
2002 selector:@selector(blinkTimerFired:)
2003 userInfo:nil repeats:NO] retain];
2004 [self flushQueue:YES];
2008 - (void)focusChange:(BOOL)on
2010 gui_focus_change(on);
2013 - (void)handleToggleToolbar
2015 // If 'go' contains 'T', then remove it, else add it.
2017 char_u go[sizeof(GO_ALL)+2];
2022 p = vim_strchr(go, GO_TOOLBAR);
2026 char_u *end = go + len;
2032 go[len] = GO_TOOLBAR;
2036 set_option_value((char_u*)"guioptions", 0, go, 0);
2038 [self redrawScreen];
2041 - (void)handleScrollbarEvent:(NSData *)data
2045 const void *bytes = [data bytes];
2046 long ident = *((long*)bytes); bytes += sizeof(long);
2047 int hitPart = *((int*)bytes); bytes += sizeof(int);
2048 float fval = *((float*)bytes); bytes += sizeof(float);
2049 scrollbar_T *sb = gui_find_scrollbar(ident);
2052 scrollbar_T *sb_info = sb->wp ? &sb->wp->w_scrollbars[0] : sb;
2053 long value = sb_info->value;
2054 long size = sb_info->size;
2055 long max = sb_info->max;
2056 BOOL isStillDragging = NO;
2057 BOOL updateKnob = YES;
2060 case NSScrollerDecrementPage:
2061 value -= (size > 2 ? size - 2 : 1);
2063 case NSScrollerIncrementPage:
2064 value += (size > 2 ? size - 2 : 1);
2066 case NSScrollerDecrementLine:
2069 case NSScrollerIncrementLine:
2072 case NSScrollerKnob:
2073 isStillDragging = YES;
2075 case NSScrollerKnobSlot:
2076 value = (long)(fval * (max - size + 1));
2083 //NSLog(@"value %d -> %d", sb_info->value, value);
2084 gui_drag_scrollbar(sb, value, isStillDragging);
2087 // Dragging the knob or option+clicking automatically updates
2088 // the knob position (on the actual NSScroller), so we only
2089 // need to set the knob position in the other cases.
2091 // Update both the left&right vertical scrollbars.
2092 long identLeft = sb->wp->w_scrollbars[SBAR_LEFT].ident;
2093 long identRight = sb->wp->w_scrollbars[SBAR_RIGHT].ident;
2094 [self setScrollbarThumbValue:value size:size max:max
2095 identifier:identLeft];
2096 [self setScrollbarThumbValue:value size:size max:max
2097 identifier:identRight];
2099 // Update the horizontal scrollbar.
2100 [self setScrollbarThumbValue:value size:size max:max
2107 - (void)handleSetFont:(NSData *)data
2111 const void *bytes = [data bytes];
2112 float pointSize = *((float*)bytes); bytes += sizeof(float);
2113 //unsigned len = *((unsigned*)bytes); bytes += sizeof(unsigned);
2114 bytes += sizeof(unsigned); // len not used
2116 NSMutableString *name = [NSMutableString stringWithUTF8String:bytes];
2117 [name appendString:[NSString stringWithFormat:@":h%d", (int)pointSize]];
2118 char_u *s = (char_u*)[name UTF8String];
2121 s = CONVERT_FROM_UTF8(s);
2124 set_option_value((char_u*)"guifont", 0, s, 0);
2127 CONVERT_FROM_UTF8_FREE(s);
2130 [self redrawScreen];
2133 - (void)handleDropFiles:(NSData *)data
2135 // TODO: Get rid of this method; instead use Vim script directly. At the
2136 // moment I know how to do this to open files in tabs, but I'm not sure how
2137 // to add the filenames to the command line when in command line mode.
2141 NSMutableDictionary *args = [NSMutableDictionary dictionaryWithData:data];
2144 id obj = [args objectForKey:@"forceOpen"];
2145 BOOL forceOpen = YES;
2147 forceOpen = [obj boolValue];
2149 NSArray *filenames = [args objectForKey:@"filenames"];
2150 if (!(filenames && [filenames count] > 0)) return;
2153 if (!forceOpen && (State & CMDLINE)) {
2154 // HACK! If Vim is in command line mode then the files names
2155 // should be added to the command line, instead of opening the
2156 // files in tabs (unless forceOpen is set). This is taken care of by
2157 // gui_handle_drop().
2158 int n = [filenames count];
2159 char_u **fnames = (char_u **)alloc(n * sizeof(char_u *));
2162 for (i = 0; i < n; ++i)
2163 fnames[i] = [[filenames objectAtIndex:i] vimStringSave];
2165 // NOTE! This function will free 'fnames'.
2166 // HACK! It is assumed that the 'x' and 'y' arguments are
2167 // unused when in command line mode.
2168 gui_handle_drop(0, 0, 0, fnames, n);
2173 [self handleOpenWithArguments:args];
2177 - (void)handleDropString:(NSData *)data
2182 char_u dropkey[3] = { CSI, KS_EXTRA, (char_u)KE_DROP };
2183 const void *bytes = [data bytes];
2184 int len = *((int*)bytes); bytes += sizeof(int);
2185 NSMutableString *string = [NSMutableString stringWithUTF8String:bytes];
2187 // Replace unrecognized end-of-line sequences with \x0a (line feed).
2188 NSRange range = { 0, [string length] };
2189 unsigned n = [string replaceOccurrencesOfString:@"\x0d\x0a"
2190 withString:@"\x0a" options:0
2193 n = [string replaceOccurrencesOfString:@"\x0d" withString:@"\x0a"
2194 options:0 range:range];
2197 len = [string lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
2198 char_u *s = (char_u*)[string UTF8String];
2200 if (input_conv.vc_type != CONV_NONE)
2201 s = string_convert(&input_conv, s, &len);
2203 dnd_yank_drag_data(s, len);
2205 if (input_conv.vc_type != CONV_NONE)
2208 add_to_input_buf(dropkey, sizeof(dropkey));
2212 - (void)startOdbEditWithArguments:(NSDictionary *)args
2214 #ifdef FEAT_ODB_EDITOR
2215 id obj = [args objectForKey:@"remoteID"];
2218 OSType serverID = [obj unsignedIntValue];
2219 NSString *remotePath = [args objectForKey:@"remotePath"];
2221 NSAppleEventDescriptor *token = nil;
2222 NSData *tokenData = [args objectForKey:@"remoteTokenData"];
2223 obj = [args objectForKey:@"remoteTokenDescType"];
2224 if (tokenData && obj) {
2225 DescType tokenType = [obj unsignedLongValue];
2226 token = [NSAppleEventDescriptor descriptorWithDescriptorType:tokenType
2230 NSArray *filenames = [args objectForKey:@"filenames"];
2231 unsigned i, numFiles = [filenames count];
2232 for (i = 0; i < numFiles; ++i) {
2233 NSString *filename = [filenames objectAtIndex:i];
2234 char_u *s = [filename vimStringSave];
2235 buf_T *buf = buflist_findname(s);
2239 if (buf->b_odb_token) {
2240 [(NSAppleEventDescriptor*)(buf->b_odb_token) release];
2241 buf->b_odb_token = NULL;
2244 if (buf->b_odb_fname) {
2245 vim_free(buf->b_odb_fname);
2246 buf->b_odb_fname = NULL;
2249 buf->b_odb_server_id = serverID;
2252 buf->b_odb_token = [token retain];
2254 buf->b_odb_fname = [remotePath vimStringSave];
2256 NSLog(@"WARNING: Could not find buffer '%@' for ODB editing.",
2260 #endif // FEAT_ODB_EDITOR
2263 - (void)handleXcodeMod:(NSData *)data
2266 const void *bytes = [data bytes];
2267 DescType type = *((DescType*)bytes); bytes += sizeof(DescType);
2268 unsigned len = *((unsigned*)bytes); bytes += sizeof(unsigned);
2272 NSAppleEventDescriptor *replyEvent = [NSAppleEventDescriptor
2273 descriptorWithDescriptorType:type
2279 - (void)handleOpenWithArguments:(NSDictionary *)args
2281 // ARGUMENT: DESCRIPTION:
2282 // -------------------------------------------------------------
2283 // filenames list of filenames
2284 // dontOpen don't open files specified in above argument
2285 // layout which layout to use to open files
2286 // selectionRange range of lines to select
2287 // searchText string to search for
2288 // cursorLine line to position the cursor on
2289 // cursorColumn column to position the cursor on
2290 // (only valid when "cursorLine" is set)
2291 // remoteID ODB parameter
2292 // remotePath ODB parameter
2293 // remoteTokenDescType ODB parameter
2294 // remoteTokenData ODB parameter
2296 //NSLog(@"%s%@ (starting=%d)", _cmd, args, starting);
2298 NSArray *filenames = [args objectForKey:@"filenames"];
2299 int i, numFiles = filenames ? [filenames count] : 0;
2300 BOOL openFiles = ![[args objectForKey:@"dontOpen"] boolValue];
2301 int layout = [[args objectForKey:@"layout"] intValue];
2303 // Change to directory of first file to open if this is an "unused" editor
2304 // (but do not do this if editing remotely).
2305 if (openFiles && numFiles > 0 && ![args objectForKey:@"remoteID"]
2306 && (starting || [self unusedEditor]) ) {
2307 char_u *s = [[filenames objectAtIndex:0] vimStringSave];
2313 // When Vim is starting we simply add the files to be opened to the
2314 // global arglist and Vim will take care of opening them for us.
2315 if (openFiles && numFiles > 0) {
2316 for (i = 0; i < numFiles; i++) {
2317 NSString *fname = [filenames objectAtIndex:i];
2320 if (ga_grow(&global_alist.al_ga, 1) == FAIL
2321 || (p = [fname vimStringSave]) == NULL)
2322 exit(2); // See comment in -[MMBackend exit]
2324 alist_add(&global_alist, p, 2);
2327 // Vim will take care of arranging the files added to the arglist
2328 // in windows or tabs; all we must do is to specify which layout to
2330 initialWindowLayout = layout;
2333 // When Vim is already open we resort to some trickery to open the
2334 // files with the specified layout.
2336 // TODO: Figure out a better way to handle this?
2337 if (openFiles && numFiles > 0) {
2338 BOOL oneWindowInTab = topframe ? YES
2339 : (topframe->fr_layout == FR_LEAF);
2340 BOOL bufChanged = NO;
2341 BOOL bufHasFilename = NO;
2343 bufChanged = curbufIsChanged();
2344 bufHasFilename = curbuf->b_ffname != NULL;
2347 // Temporarily disable flushing since the following code may
2348 // potentially cause multiple redraws.
2349 flushDisabled = YES;
2351 BOOL onlyOneTab = (first_tabpage->tp_next == NULL);
2352 if (WIN_TABS == layout && !onlyOneTab) {
2353 // By going to the last tabpage we ensure that the new tabs
2354 // will appear last (if this call is left out, the taborder
2359 // Make sure we're in normal mode first.
2360 [self addInput:@"<C-\\><C-N>"];
2363 // With "split layout" we open a new tab before opening
2364 // multiple files if the current tab has more than one window
2365 // or if there is exactly one window but whose buffer has a
2366 // filename. (The :drop command ensures modified buffers get
2367 // their own window.)
2368 if ((WIN_HOR == layout || WIN_VER == layout) &&
2369 (!oneWindowInTab || bufHasFilename))
2370 [self addInput:@":tabnew<CR>"];
2372 // The files are opened by constructing a ":drop ..." command
2373 // and executing it.
2374 NSMutableString *cmd = (WIN_TABS == layout)
2375 ? [NSMutableString stringWithString:@":tab drop"]
2376 : [NSMutableString stringWithString:@":drop"];
2378 for (i = 0; i < numFiles; ++i) {
2379 NSString *file = [filenames objectAtIndex:i];
2380 file = [file stringByEscapingSpecialFilenameCharacters];
2381 [cmd appendString:@" "];
2382 [cmd appendString:file];
2385 // Temporarily clear 'suffixes' so that the files are opened in
2386 // the same order as they appear in the "filenames" array.
2387 [self addInput:@":let mvim_oldsu=&su|set su=<CR>"];
2389 [self addInput:cmd];
2391 // Split the view into multiple windows if requested.
2392 if (WIN_HOR == layout)
2393 [self addInput:@"|sall"];
2394 else if (WIN_VER == layout)
2395 [self addInput:@"|vert sall"];
2397 // Restore the old value of 'suffixes'.
2398 [self addInput:@"|let &su=mvim_oldsu|unlet mvim_oldsu<CR>"];
2400 // When opening one file we try to reuse the current window,
2401 // but not if its buffer is modified or has a filename.
2402 // However, the 'arglist' layout always opens the file in the
2404 NSString *file = [[filenames lastObject]
2405 stringByEscapingSpecialFilenameCharacters];
2407 if (WIN_HOR == layout) {
2408 if (!(bufHasFilename || bufChanged))
2409 cmd = [NSString stringWithFormat:@":e %@", file];
2411 cmd = [NSString stringWithFormat:@":sp %@", file];
2412 } else if (WIN_VER == layout) {
2413 if (!(bufHasFilename || bufChanged))
2414 cmd = [NSString stringWithFormat:@":e %@", file];
2416 cmd = [NSString stringWithFormat:@":vsp %@", file];
2417 } else if (WIN_TABS == layout) {
2418 if (oneWindowInTab && !(bufHasFilename || bufChanged))
2419 cmd = [NSString stringWithFormat:@":e %@", file];
2421 cmd = [NSString stringWithFormat:@":tabe %@", file];
2423 // (The :drop command will split if there is a modified
2425 cmd = [NSString stringWithFormat:@":drop %@", file];
2428 [self addInput:cmd];
2429 [self addInput:@"<CR>"];
2432 // Force screen redraw (does it have to be this complicated?).
2433 // (This code was taken from the end of gui_handle_drop().)
2434 update_screen(NOT_VALID);
2437 gui_update_cursor(FALSE, FALSE);
2444 if ([args objectForKey:@"remoteID"]) {
2445 // NOTE: We have to delay processing any ODB related arguments since
2446 // the file(s) may not be opened until the input buffer is processed.
2447 [self performSelectorOnMainThread:@selector(startOdbEditWithArguments:)
2452 NSString *lineString = [args objectForKey:@"cursorLine"];
2453 if (lineString && [lineString intValue] > 0) {
2454 NSString *columnString = [args objectForKey:@"cursorColumn"];
2455 if (!(columnString && [columnString intValue] > 0))
2456 columnString = @"1";
2458 NSString *cmd = [NSString stringWithFormat:@"<C-\\><C-N>:cal "
2459 "cursor(%@,%@)|norm! zz<CR>:f<CR>", lineString, columnString];
2460 [self addInput:cmd];
2463 NSString *rangeString = [args objectForKey:@"selectionRange"];
2465 // Build a command line string that will select the given range of
2466 // lines. If range.length == 0, then position the cursor on the given
2467 // line but do not select.
2468 NSRange range = NSRangeFromString(rangeString);
2470 if (range.length > 0) {
2471 cmd = [NSString stringWithFormat:@"<C-\\><C-N>%dGV%dGz.0",
2472 NSMaxRange(range), range.location];
2474 cmd = [NSString stringWithFormat:@"<C-\\><C-N>%dGz.0",
2478 [self addInput:cmd];
2481 NSString *searchText = [args objectForKey:@"searchText"];
2483 // TODO: Searching is an exclusive motion, so if the pattern would
2484 // match on row 0 column 0 then this pattern will miss that match.
2485 [self addInput:[NSString stringWithFormat:@"<C-\\><C-N>gg/\\c%@<CR>",
2490 - (BOOL)checkForModifiedBuffers
2493 for (buf = firstbuf; buf != NULL; buf = buf->b_next) {
2494 if (bufIsChanged(buf)) {
2502 - (void)addInput:(NSString *)input
2504 // NOTE: This code is essentially identical to server_to_input_buf(),
2505 // except the 'silent' flag is TRUE in the call to ins_typebuf() below.
2506 char_u *string = [input vimStringSave];
2507 if (!string) return;
2509 /* Set 'cpoptions' the way we want it.
2510 * B set - backslashes are *not* treated specially
2511 * k set - keycodes are *not* reverse-engineered
2512 * < unset - <Key> sequences *are* interpreted
2513 * The last but one parameter of replace_termcodes() is TRUE so that the
2514 * <lt> sequence is recognised - needed for a real backslash.
2517 char_u *cpo_save = p_cpo;
2518 p_cpo = (char_u *)"Bk";
2519 char_u *str = replace_termcodes((char_u *)string, &ptr, FALSE, TRUE, FALSE);
2522 if (*ptr != NUL) /* trailing CTRL-V results in nothing */
2525 * Add the string to the input stream.
2526 * Can't use add_to_input_buf() here, we now have K_SPECIAL bytes.
2528 * First clear typed characters from the typeahead buffer, there could
2529 * be half a mapping there. Then append to the existing string, so
2530 * that multiple commands from a client are concatenated.
2532 if (typebuf.tb_maplen < typebuf.tb_len)
2533 del_typebuf(typebuf.tb_len - typebuf.tb_maplen, typebuf.tb_maplen);
2534 (void)ins_typebuf(str, REMAP_NONE, typebuf.tb_len, TRUE, TRUE);
2536 /* Let input_available() know we inserted text in the typeahead
2538 typebuf_was_filled = TRUE;
2544 - (BOOL)unusedEditor
2546 BOOL oneWindowInTab = topframe ? YES
2547 : (topframe->fr_layout == FR_LEAF);
2548 BOOL bufChanged = NO;
2549 BOOL bufHasFilename = NO;
2551 bufChanged = curbufIsChanged();
2552 bufHasFilename = curbuf->b_ffname != NULL;
2555 BOOL onlyOneTab = (first_tabpage->tp_next == NULL);
2557 return onlyOneTab && oneWindowInTab && !bufChanged && !bufHasFilename;
2560 - (void)redrawScreen
2562 // Force screen redraw (does it have to be this complicated?).
2563 redraw_all_later(CLEAR);
2564 update_screen(NOT_VALID);
2567 gui_update_cursor(FALSE, FALSE);
2569 // HACK! The cursor is not put back at the command line by the above
2570 // "redraw commands". The following test seems to do the trick though.
2571 if (State & CMDLINE)
2575 - (void)handleFindReplace:(NSDictionary *)args
2579 NSString *findString = [args objectForKey:@"find"];
2580 if (!findString) return;
2582 char_u *find = [findString vimStringSave];
2583 char_u *replace = [[args objectForKey:@"replace"] vimStringSave];
2584 int flags = [[args objectForKey:@"flags"] intValue];
2586 // NOTE: The flag 0x100 is used to indicate a backward search.
2587 gui_do_findrepl(flags, find, replace, (flags & 0x100) == 0);
2593 @end // MMBackend (Private)
2598 @implementation MMBackend (ClientServer)
2600 - (NSString *)connectionNameFromServerName:(NSString *)name
2602 NSString *bundlePath = [[NSBundle mainBundle] bundlePath];
2604 return [[NSString stringWithFormat:@"%@.%@", bundlePath, name]
2608 - (NSConnection *)connectionForServerName:(NSString *)name
2610 // TODO: Try 'name%d' if 'name' fails.
2611 NSString *connName = [self connectionNameFromServerName:name];
2612 NSConnection *svrConn = [connectionNameDict objectForKey:connName];
2615 svrConn = [NSConnection connectionWithRegisteredName:connName
2617 // Try alternate server...
2618 if (!svrConn && alternateServerName) {
2619 //NSLog(@" trying to connect to alternate server: %@",
2620 // alternateServerName);
2621 connName = [self connectionNameFromServerName:alternateServerName];
2622 svrConn = [NSConnection connectionWithRegisteredName:connName
2626 // Try looking for alternate servers...
2628 //NSLog(@" looking for alternate servers...");
2629 NSString *alt = [self alternateServerNameForName:name];
2630 if (alt != alternateServerName) {
2631 //NSLog(@" found alternate server: %@", string);
2632 [alternateServerName release];
2633 alternateServerName = [alt copy];
2637 // Try alternate server again...
2638 if (!svrConn && alternateServerName) {
2639 //NSLog(@" trying to connect to alternate server: %@",
2640 // alternateServerName);
2641 connName = [self connectionNameFromServerName:alternateServerName];
2642 svrConn = [NSConnection connectionWithRegisteredName:connName
2647 [connectionNameDict setObject:svrConn forKey:connName];
2649 //NSLog(@"Adding %@ as connection observer for %@", self, svrConn);
2650 [[NSNotificationCenter defaultCenter] addObserver:self
2651 selector:@selector(serverConnectionDidDie:)
2652 name:NSConnectionDidDieNotification object:svrConn];
2659 - (NSConnection *)connectionForServerPort:(int)port
2662 NSEnumerator *e = [connectionNameDict objectEnumerator];
2664 while ((conn = [e nextObject])) {
2665 // HACK! Assume connection uses mach ports.
2666 if (port == [(NSMachPort*)[conn sendPort] machPort])
2673 - (void)serverConnectionDidDie:(NSNotification *)notification
2675 //NSLog(@"%s%@", _cmd, notification);
2677 NSConnection *svrConn = [notification object];
2679 //NSLog(@"Removing %@ as connection observer from %@", self, svrConn);
2680 [[NSNotificationCenter defaultCenter]
2682 name:NSConnectionDidDieNotification
2685 [connectionNameDict removeObjectsForKeys:
2686 [connectionNameDict allKeysForObject:svrConn]];
2688 // HACK! Assume connection uses mach ports.
2689 int port = [(NSMachPort*)[svrConn sendPort] machPort];
2690 NSNumber *key = [NSNumber numberWithInt:port];
2692 [clientProxyDict removeObjectForKey:key];
2693 [serverReplyDict removeObjectForKey:key];
2696 - (void)addClient:(NSDistantObject *)client
2698 NSConnection *conn = [client connectionForProxy];
2699 // HACK! Assume connection uses mach ports.
2700 int port = [(NSMachPort*)[conn sendPort] machPort];
2701 NSNumber *key = [NSNumber numberWithInt:port];
2703 if (![clientProxyDict objectForKey:key]) {
2704 [client setProtocolForProxy:@protocol(MMVimClientProtocol)];
2705 [clientProxyDict setObject:client forKey:key];
2708 // NOTE: 'clientWindow' is a global variable which is used by <client>
2709 clientWindow = port;
2712 - (NSString *)alternateServerNameForName:(NSString *)name
2714 if (!(name && [name length] > 0))
2717 // Only look for alternates if 'name' doesn't end in a digit.
2718 unichar lastChar = [name characterAtIndex:[name length]-1];
2719 if (lastChar >= '0' && lastChar <= '9')
2722 // Look for alternates among all current servers.
2723 NSArray *list = [self serverList];
2724 if (!(list && [list count] > 0))
2727 // Filter out servers starting with 'name' and ending with a number. The
2728 // (?i) pattern ensures that the match is case insensitive.
2729 NSString *pat = [NSString stringWithFormat:@"(?i)%@[0-9]+\\z", name];
2730 NSPredicate *pred = [NSPredicate predicateWithFormat:
2731 @"SELF MATCHES %@", pat];
2732 list = [list filteredArrayUsingPredicate:pred];
2733 if ([list count] > 0) {
2734 list = [list sortedArrayUsingSelector:@selector(serverNameCompare:)];
2735 return [list objectAtIndex:0];
2741 @end // MMBackend (ClientServer)
2746 @implementation NSString (MMServerNameCompare)
2747 - (NSComparisonResult)serverNameCompare:(NSString *)string
2749 return [self compare:string
2750 options:NSCaseInsensitiveSearch|NSNumericSearch];
2757 static int eventModifierFlagsToVimModMask(int modifierFlags)
2761 if (modifierFlags & NSShiftKeyMask)
2762 modMask |= MOD_MASK_SHIFT;
2763 if (modifierFlags & NSControlKeyMask)
2764 modMask |= MOD_MASK_CTRL;
2765 if (modifierFlags & NSAlternateKeyMask)
2766 modMask |= MOD_MASK_ALT;
2767 if (modifierFlags & NSCommandKeyMask)
2768 modMask |= MOD_MASK_CMD;
2773 static int eventModifierFlagsToVimMouseModMask(int modifierFlags)
2777 if (modifierFlags & NSShiftKeyMask)
2778 modMask |= MOUSE_SHIFT;
2779 if (modifierFlags & NSControlKeyMask)
2780 modMask |= MOUSE_CTRL;
2781 if (modifierFlags & NSAlternateKeyMask)
2782 modMask |= MOUSE_ALT;
2787 static int eventButtonNumberToVimMouseButton(int buttonNumber)
2789 static int mouseButton[] = { MOUSE_LEFT, MOUSE_RIGHT, MOUSE_MIDDLE };
2791 return (buttonNumber >= 0 && buttonNumber < 3)
2792 ? mouseButton[buttonNumber] : -1;
2797 // This function is modeled after the VimToPython function found in if_python.c
2798 // NB This does a deep copy by value, it does not lookup references like the
2799 // VimToPython function does. This is because I didn't want to deal with the
2800 // retain cycles that this would create, and we can cover 99% of the use cases
2801 // by ignoring it. If we ever switch to using GC in MacVim then this
2802 // functionality can be implemented easily.
2803 static id vimToCocoa(typval_T * tv, int depth)
2809 // Avoid infinite recursion
2814 if (tv->v_type == VAR_STRING) {
2815 char_u * val = tv->vval.v_string;
2816 // val can be NULL if the string is empty
2818 result = [NSString string];
2821 val = CONVERT_TO_UTF8(val);
2823 result = [NSString stringWithUTF8String:(char*)val];
2825 CONVERT_TO_UTF8_FREE(val);
2828 } else if (tv->v_type == VAR_NUMBER) {
2829 // looks like sizeof(varnumber_T) is always <= sizeof(long)
2830 result = [NSNumber numberWithLong:(long)tv->vval.v_number];
2831 } else if (tv->v_type == VAR_LIST) {
2832 list_T * list = tv->vval.v_list;
2835 NSMutableArray * arr = result = [NSMutableArray array];
2838 for (curr = list->lv_first; curr != NULL; curr = curr->li_next) {
2839 newObj = vimToCocoa(&curr->li_tv, depth + 1);
2840 [arr addObject:newObj];
2843 } else if (tv->v_type == VAR_DICT) {
2844 NSMutableDictionary * dict = result = [NSMutableDictionary dictionary];
2846 if (tv->vval.v_dict != NULL) {
2847 hashtab_T * ht = &tv->vval.v_dict->dv_hashtab;
2848 int todo = ht->ht_used;
2852 for (hi = ht->ht_array; todo > 0; ++hi) {
2853 if (!HASHITEM_EMPTY(hi)) {
2856 di = dict_lookup(hi);
2857 newObj = vimToCocoa(&di->di_tv, depth + 1);
2859 char_u * keyval = hi->hi_key;
2861 keyval = CONVERT_TO_UTF8(keyval);
2863 NSString * key = [NSString stringWithUTF8String:(char*)keyval];
2865 CONVERT_TO_UTF8_FREE(keyval);
2867 [dict setObject:newObj forKey:key];
2871 } else { // only func refs should fall into this category?
2879 // This function is modeled after eval_client_expr_to_string found in main.c
2880 // Returns nil if there was an error evaluating the expression, and writes a
2881 // message to errorStr.
2882 // TODO Get the error that occurred while evaluating the expression in vim
2884 static id evalExprCocoa(NSString * expr, NSString ** errstr)
2887 char_u *s = (char_u*)[expr UTF8String];
2890 s = CONVERT_FROM_UTF8(s);
2893 int save_dbl = debug_break_level;
2894 int save_ro = redir_off;
2896 debug_break_level = -1;
2900 typval_T * tvres = eval_expr(s, NULL);
2902 debug_break_level = save_dbl;
2903 redir_off = save_ro;
2910 CONVERT_FROM_UTF8_FREE(s);
2915 gui_update_cursor(FALSE, FALSE);
2918 if (tvres == NULL) {
2920 *errstr = @"Expression evaluation failed.";
2923 id res = vimToCocoa(tvres, 1);
2928 *errstr = @"Conversion to cocoa values failed.";
2936 @implementation NSString (VimStrings)
2938 + (id)stringWithVimString:(char_u *)s
2940 // This method ensures a non-nil string is returned. If 's' cannot be
2941 // converted to a utf-8 string it is assumed to be latin-1. If conversion
2942 // still fails an empty NSString is returned.
2943 NSString *string = nil;
2946 s = CONVERT_TO_UTF8(s);
2948 string = [NSString stringWithUTF8String:(char*)s];
2950 // HACK! Apparently 's' is not a valid utf-8 string, maybe it is
2952 string = [NSString stringWithCString:(char*)s
2953 encoding:NSISOLatin1StringEncoding];
2956 CONVERT_TO_UTF8_FREE(s);
2960 return string != nil ? string : [NSString string];
2963 - (char_u *)vimStringSave
2965 char_u *s = (char_u*)[self UTF8String], *ret = NULL;
2968 s = CONVERT_FROM_UTF8(s);
2970 ret = vim_strsave(s);
2972 CONVERT_FROM_UTF8_FREE(s);
2978 @end // NSString (VimStrings)