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 // This constant controls how often the command queue may be flushed. If it is
48 // too small the app might feel unresponsive; if it is too large there might be
49 // long periods without the screen updating (e.g. when sourcing a large session
50 // file). (The unit is seconds.)
51 static float MMFlushTimeoutInterval = 0.1f;
52 static int MMFlushQueueLenHint = 80*40;
54 static unsigned MMServerMax = 1000;
56 // TODO: Move to separate file.
57 static int eventModifierFlagsToVimModMask(int modifierFlags);
58 static int eventModifierFlagsToVimMouseModMask(int modifierFlags);
59 static int eventButtonNumberToVimMouseButton(int buttonNumber);
63 vimmenu_T *menu_for_descriptor(NSArray *desc);
65 static id evalExprCocoa(NSString * expr, NSString ** errstr);
73 static NSString *MMSymlinkWarningString =
74 @"\n\n\tMost likely this is because you have symlinked directly to\n"
75 "\tthe Vim binary, which Cocoa does not allow. Please use an\n"
76 "\talias or the mvim shell script instead. If you have not used\n"
77 "\ta symlink, then your MacVim.app bundle is incomplete.\n\n";
81 @interface NSString (MMServerNameCompare)
82 - (NSComparisonResult)serverNameCompare:(NSString *)string;
88 @interface MMBackend (Private)
89 - (void)waitForDialogReturn;
90 - (void)queueVimStateMessage;
91 - (void)processInputQueue;
92 - (void)handleInputEvent:(int)msgid data:(NSData *)data;
93 + (NSDictionary *)specialKeys;
94 - (void)handleInsertText:(NSData *)data;
95 - (void)handleKeyDown:(NSString *)key modifiers:(int)mods;
96 - (void)queueMessage:(int)msgid data:(NSData *)data;
97 - (void)connectionDidDie:(NSNotification *)notification;
98 - (void)blinkTimerFired:(NSTimer *)timer;
99 - (void)focusChange:(BOOL)on;
100 - (void)handleToggleToolbar;
101 - (void)handleScrollbarEvent:(NSData *)data;
102 - (void)handleSetFont:(NSData *)data;
103 - (void)handleDropFiles:(NSData *)data;
104 - (void)handleDropString:(NSData *)data;
105 - (void)startOdbEditWithArguments:(NSDictionary *)args;
106 - (void)handleXcodeMod:(NSData *)data;
107 - (void)handleOpenWithArguments:(NSDictionary *)args;
108 - (BOOL)checkForModifiedBuffers;
109 - (void)addInput:(NSString *)input;
114 @interface MMBackend (ClientServer)
115 - (NSString *)connectionNameFromServerName:(NSString *)name;
116 - (NSConnection *)connectionForServerName:(NSString *)name;
117 - (NSConnection *)connectionForServerPort:(int)port;
118 - (void)serverConnectionDidDie:(NSNotification *)notification;
119 - (void)addClient:(NSDistantObject *)client;
120 - (NSString *)alternateServerNameForName:(NSString *)name;
125 @implementation MMBackend
127 + (MMBackend *)sharedInstance
129 static MMBackend *singleton = nil;
130 return singleton ? singleton : (singleton = [MMBackend new]);
136 if (!self) return nil;
138 fontContainerRef = loadFonts();
140 outputQueue = [[NSMutableArray alloc] init];
141 inputQueue = [[NSMutableArray alloc] init];
142 drawData = [[NSMutableData alloc] initWithCapacity:1024];
143 connectionNameDict = [[NSMutableDictionary alloc] init];
144 clientProxyDict = [[NSMutableDictionary alloc] init];
145 serverReplyDict = [[NSMutableDictionary alloc] init];
147 NSBundle *mainBundle = [NSBundle mainBundle];
148 NSString *path = [mainBundle pathForResource:@"Colors" ofType:@"plist"];
150 colorDict = [[NSDictionary dictionaryWithContentsOfFile:path] retain];
152 path = [mainBundle pathForResource:@"SystemColors" ofType:@"plist"];
154 sysColorDict = [[NSDictionary dictionaryWithContentsOfFile:path]
157 path = [mainBundle pathForResource:@"Actions" ofType:@"plist"];
159 actionDict = [[NSDictionary dictionaryWithContentsOfFile:path] retain];
161 if (!(colorDict && sysColorDict && actionDict))
162 NSLog(@"ERROR: Failed to load dictionaries.%@",
163 MMSymlinkWarningString);
170 //NSLog(@"%@ %s", [self className], _cmd);
171 [[NSNotificationCenter defaultCenter] removeObserver:self];
173 [oldWideFont release]; oldWideFont = nil;
174 [blinkTimer release]; blinkTimer = nil;
175 [alternateServerName release]; alternateServerName = nil;
176 [serverReplyDict release]; serverReplyDict = nil;
177 [clientProxyDict release]; clientProxyDict = nil;
178 [connectionNameDict release]; connectionNameDict = nil;
179 [inputQueue release]; inputQueue = nil;
180 [outputQueue release]; outputQueue = nil;
181 [drawData release]; drawData = nil;
182 [frontendProxy release]; frontendProxy = nil;
183 [connection release]; connection = nil;
184 [actionDict release]; actionDict = nil;
185 [sysColorDict release]; sysColorDict = nil;
186 [colorDict release]; colorDict = nil;
191 - (void)setBackgroundColor:(int)color
193 backgroundColor = MM_COLOR_WITH_TRANSP(color,p_transp);
196 - (void)setForegroundColor:(int)color
198 foregroundColor = MM_COLOR(color);
201 - (void)setSpecialColor:(int)color
203 specialColor = MM_COLOR(color);
206 - (void)setDefaultColorsBackground:(int)bg foreground:(int)fg
208 defaultBackgroundColor = MM_COLOR_WITH_TRANSP(bg,p_transp);
209 defaultForegroundColor = MM_COLOR(fg);
211 NSMutableData *data = [NSMutableData data];
213 [data appendBytes:&defaultBackgroundColor length:sizeof(unsigned)];
214 [data appendBytes:&defaultForegroundColor length:sizeof(unsigned)];
216 [self queueMessage:SetDefaultColorsMsgID data:data];
219 - (NSConnection *)connection
222 // NOTE! If the name of the connection changes here it must also be
223 // updated in MMAppController.m.
224 NSString *name = [NSString stringWithFormat:@"%@-connection",
225 [[NSBundle mainBundle] bundleIdentifier]];
227 connection = [NSConnection connectionWithRegisteredName:name host:nil];
231 // NOTE: 'connection' may be nil here.
235 - (NSDictionary *)actionDict
240 - (int)initialWindowLayout
242 return initialWindowLayout;
245 - (void)queueMessage:(int)msgid properties:(NSDictionary *)props
247 [self queueMessage:msgid data:[props dictionaryAsData]];
252 if (![self connection]) {
254 // This is a preloaded process and as such should not cause the
255 // MacVim to be opened. We probably got here as a result of the
256 // user quitting MacVim while the process was preloading, so exit
261 NSBundle *mainBundle = [NSBundle mainBundle];
266 // Launch MacVim using Launch Services (NSWorkspace would be nicer, but
267 // the API to pass Apple Event parameters is broken on 10.4).
268 NSString *path = [mainBundle bundlePath];
269 status = FSPathMakeRef((const UInt8 *)[path UTF8String], &ref, NULL);
270 if (noErr == status) {
271 // Pass parameter to the 'Open' Apple Event that tells MacVim not
272 // to open an untitled window.
273 NSAppleEventDescriptor *desc =
274 [NSAppleEventDescriptor recordDescriptor];
275 [desc setParamDescriptor:
276 [NSAppleEventDescriptor descriptorWithBoolean:NO]
277 forKeyword:keyMMUntitledWindow];
279 LSLaunchFSRefSpec spec = { &ref, 0, NULL, [desc aeDesc],
280 kLSLaunchDefaults, NULL };
281 status = LSOpenFromRefSpec(&spec, NULL);
284 if (noErr != status) {
285 NSLog(@"ERROR: Failed to launch MacVim (path=%@).%@",
286 path, MMSymlinkWarningString);
290 // Launch MacVim using NSTask. For some reason the above code using
291 // Launch Services sometimes fails on LSOpenFromRefSpec() (when it
292 // fails, the dock icon starts bouncing and never stops). It seems
293 // like rebuilding the Launch Services database takes care of this
294 // problem, but the NSTask way seems more stable so stick with it.
296 // NOTE! Using NSTask to launch the GUI has the negative side-effect
297 // that the GUI won't be activated (or raised) so there is a hack in
298 // MMAppController which raises the app when a new window is opened.
299 NSMutableArray *args = [NSMutableArray arrayWithObjects:
300 [NSString stringWithFormat:@"-%@", MMNoWindowKey], @"yes", nil];
301 NSString *exeName = [[mainBundle infoDictionary]
302 objectForKey:@"CFBundleExecutable"];
303 NSString *path = [mainBundle pathForAuxiliaryExecutable:exeName];
305 NSLog(@"ERROR: Could not find MacVim executable in bundle.%@",
306 MMSymlinkWarningString);
310 [NSTask launchedTaskWithLaunchPath:path arguments:args];
313 // HACK! Poll the mach bootstrap server until it returns a valid
314 // connection to detect that MacVim has finished launching. Also set a
315 // time-out date so that we don't get stuck doing this forever.
316 NSDate *timeOutDate = [NSDate dateWithTimeIntervalSinceNow:10];
317 while (![self connection] &&
318 NSOrderedDescending == [timeOutDate compare:[NSDate date]])
319 [[NSRunLoop currentRunLoop]
320 runMode:NSDefaultRunLoopMode
321 beforeDate:[NSDate dateWithTimeIntervalSinceNow:1]];
323 // NOTE: [self connection] will set 'connection' as a side-effect.
325 NSLog(@"WARNING: Timed-out waiting for GUI to launch.");
332 [[NSNotificationCenter defaultCenter] addObserver:self
333 selector:@selector(connectionDidDie:)
334 name:NSConnectionDidDieNotification object:connection];
336 id proxy = [connection rootProxy];
337 [proxy setProtocolForProxy:@protocol(MMAppProtocol)];
339 int pid = [[NSProcessInfo processInfo] processIdentifier];
341 frontendProxy = [proxy connectBackend:self pid:pid];
343 [frontendProxy retain];
344 [frontendProxy setProtocolForProxy:@protocol(MMAppProtocol)];
348 @catch (NSException *e) {
349 NSLog(@"Exception caught when trying to connect backend: \"%@\"", e);
355 - (BOOL)openVimWindow
357 [self queueMessage:OpenVimWindowMsgID data:nil];
363 int type = ClearAllDrawType;
365 // Any draw commands in queue are effectively obsolete since this clearAll
366 // will negate any effect they have, therefore we may as well clear the
368 [drawData setLength:0];
370 [drawData appendBytes:&type length:sizeof(int)];
373 - (void)clearBlockFromRow:(int)row1 column:(int)col1
374 toRow:(int)row2 column:(int)col2
376 int type = ClearBlockDrawType;
378 [drawData appendBytes:&type length:sizeof(int)];
380 [drawData appendBytes:&defaultBackgroundColor length:sizeof(unsigned)];
381 [drawData appendBytes:&row1 length:sizeof(int)];
382 [drawData appendBytes:&col1 length:sizeof(int)];
383 [drawData appendBytes:&row2 length:sizeof(int)];
384 [drawData appendBytes:&col2 length:sizeof(int)];
387 - (void)deleteLinesFromRow:(int)row count:(int)count
388 scrollBottom:(int)bottom left:(int)left right:(int)right
390 int type = DeleteLinesDrawType;
392 [drawData appendBytes:&type length:sizeof(int)];
394 [drawData appendBytes:&defaultBackgroundColor length:sizeof(unsigned)];
395 [drawData appendBytes:&row length:sizeof(int)];
396 [drawData appendBytes:&count length:sizeof(int)];
397 [drawData appendBytes:&bottom length:sizeof(int)];
398 [drawData appendBytes:&left length:sizeof(int)];
399 [drawData appendBytes:&right length:sizeof(int)];
402 - (void)drawString:(char*)s length:(int)len row:(int)row column:(int)col
403 cells:(int)cells flags:(int)flags
405 if (len <= 0 || cells <= 0) return;
407 int type = DrawStringDrawType;
409 [drawData appendBytes:&type length:sizeof(int)];
411 [drawData appendBytes:&backgroundColor length:sizeof(unsigned)];
412 [drawData appendBytes:&foregroundColor length:sizeof(unsigned)];
413 [drawData appendBytes:&specialColor length:sizeof(unsigned)];
414 [drawData appendBytes:&row length:sizeof(int)];
415 [drawData appendBytes:&col length:sizeof(int)];
416 [drawData appendBytes:&cells length:sizeof(int)];
417 [drawData appendBytes:&flags length:sizeof(int)];
418 [drawData appendBytes:&len length:sizeof(int)];
419 [drawData appendBytes:s length:len];
422 - (void)insertLinesFromRow:(int)row count:(int)count
423 scrollBottom:(int)bottom left:(int)left right:(int)right
425 int type = InsertLinesDrawType;
427 [drawData appendBytes:&type length:sizeof(int)];
429 [drawData appendBytes:&defaultBackgroundColor length:sizeof(unsigned)];
430 [drawData appendBytes:&row length:sizeof(int)];
431 [drawData appendBytes:&count length:sizeof(int)];
432 [drawData appendBytes:&bottom length:sizeof(int)];
433 [drawData appendBytes:&left length:sizeof(int)];
434 [drawData appendBytes:&right length:sizeof(int)];
437 - (void)drawCursorAtRow:(int)row column:(int)col shape:(int)shape
438 fraction:(int)percent color:(int)color
440 int type = DrawCursorDrawType;
441 unsigned uc = MM_COLOR(color);
443 [drawData appendBytes:&type length:sizeof(int)];
445 [drawData appendBytes:&uc length:sizeof(unsigned)];
446 [drawData appendBytes:&row length:sizeof(int)];
447 [drawData appendBytes:&col length:sizeof(int)];
448 [drawData appendBytes:&shape length:sizeof(int)];
449 [drawData appendBytes:&percent length:sizeof(int)];
452 - (void)drawInvertedRectAtRow:(int)row column:(int)col numRows:(int)nr
453 numColumns:(int)nc invert:(int)invert
455 int type = DrawInvertedRectDrawType;
456 [drawData appendBytes:&type length:sizeof(int)];
458 [drawData appendBytes:&row length:sizeof(int)];
459 [drawData appendBytes:&col length:sizeof(int)];
460 [drawData appendBytes:&nr length:sizeof(int)];
461 [drawData appendBytes:&nc length:sizeof(int)];
462 [drawData appendBytes:&invert length:sizeof(int)];
467 // Tend to the run loop, returning immediately if there are no events
469 [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
470 beforeDate:[NSDate distantPast]];
473 // Keyboard and mouse input is handled directly, other input is queued and
474 // processed here. This call may enter a blocking loop.
475 if ([inputQueue count] > 0)
476 [self processInputQueue];
480 - (void)flushQueue:(BOOL)force
482 // NOTE: This variable allows for better control over when the queue is
483 // flushed. It can be set to YES at the beginning of a sequence of calls
484 // that may potentially add items to the queue, and then restored back to
486 if (flushDisabled) return;
488 // NOTE! This method gets called a lot; if we were to flush every time it
489 // got called MacVim would feel unresponsive. So there is a time out which
490 // ensures that the queue isn't flushed too often.
491 if (!force && lastFlushDate
492 && -[lastFlushDate timeIntervalSinceNow] < MMFlushTimeoutInterval
493 && [drawData length] < MMFlushQueueLenHint)
496 if ([drawData length] > 0) {
497 // HACK! Detect changes to 'guifontwide'.
498 if (gui.wide_font != (GuiFont)oldWideFont) {
499 [oldWideFont release];
500 oldWideFont = [(NSFont*)gui.wide_font retain];
501 [self setWideFont:oldWideFont];
504 int type = SetCursorPosDrawType;
505 [drawData appendBytes:&type length:sizeof(type)];
506 [drawData appendBytes:&gui.row length:sizeof(gui.row)];
507 [drawData appendBytes:&gui.col length:sizeof(gui.col)];
509 [self queueMessage:BatchDrawMsgID data:[drawData copy]];
510 [drawData setLength:0];
513 if ([outputQueue count] > 0 || force) {
514 // When 'force' is set we always update the Vim state to ensure that
515 // MacVim has a copy of the latest state (since 'force' is typically
516 // set just before Vim takes a nap whilst waiting for input).
517 [self queueVimStateMessage];
520 [frontendProxy processCommandQueue:outputQueue];
522 @catch (NSException *e) {
523 NSLog(@"Exception caught when processing command queue: \"%@\"", e);
526 [outputQueue removeAllObjects];
528 [lastFlushDate release];
529 lastFlushDate = [[NSDate date] retain];
533 - (BOOL)waitForInput:(int)milliseconds
535 //NSLog(@"|ENTER| %s%d", _cmd, milliseconds);
537 // Only start the run loop if the input queue is empty, otherwise process
538 // the input first so that the input on queue isn't delayed.
539 if ([inputQueue count]) {
542 NSDate *date = milliseconds > 0 ?
543 [NSDate dateWithTimeIntervalSinceNow:.001*milliseconds] :
544 [NSDate distantFuture];
546 [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
550 // I know of no way to figure out if the run loop exited because input was
551 // found or because of a time out, so I need to manually indicate when
552 // input was received in processInput:data: and then reset it every time
554 BOOL yn = inputReceived;
557 // Keyboard and mouse input is handled directly, other input is queued and
558 // processed here. This call may enter a blocking loop.
559 if ([inputQueue count] > 0)
560 [self processInputQueue];
562 //NSLog(@"|LEAVE| %s input=%d", _cmd, yn);
568 // To notify MacVim that this Vim process is exiting we could simply
569 // invalidate the connection and it would automatically receive a
570 // connectionDidDie: notification. However, this notification seems to
571 // take up to 300 ms to arrive which is quite a noticeable delay. Instead
572 // we immediately send a message to MacVim asking it to close the window
573 // belonging to this process, and then we invalidate the connection (in
574 // case the message got lost).
576 // Make sure no connectionDidDie: notification is received now that we are
578 [[NSNotificationCenter defaultCenter] removeObserver:self];
580 if ([connection isValid]) {
582 int msgid = CloseWindowMsgID;
583 NSData *data = [NSData dataWithBytes:&msgid length:sizeof(int)];
584 NSArray *q = [NSArray arrayWithObjects:data, [NSData data], nil];
585 [frontendProxy processCommandQueue:q];
588 @catch (NSException *e) {
589 NSLog(@"Exception caught when sending CloseWindowMsgID: \"%@\"", e);
592 [connection invalidate];
595 #ifdef MAC_CLIENTSERVER
596 // The default connection is used for the client/server code.
597 [[NSConnection defaultConnection] setRootObject:nil];
598 [[NSConnection defaultConnection] invalidate];
601 if (fontContainerRef) {
602 ATSFontDeactivate(fontContainerRef, NULL, kATSOptionFlagsDefault);
603 fontContainerRef = 0;
608 - (void)selectTab:(int)index
610 //NSLog(@"%s%d", _cmd, index);
613 NSData *data = [NSData dataWithBytes:&index length:sizeof(int)];
614 [self queueMessage:SelectTabMsgID data:data];
619 //NSLog(@"%s", _cmd);
621 NSMutableData *data = [NSMutableData data];
623 int idx = tabpage_index(curtab) - 1;
624 [data appendBytes:&idx length:sizeof(int)];
627 for (tp = first_tabpage; tp != NULL; tp = tp->tp_next) {
628 // This function puts the label of the tab in the global 'NameBuff'.
629 get_tabline_label(tp, FALSE);
630 char_u *s = NameBuff;
632 if (len <= 0) continue;
635 s = CONVERT_TO_UTF8(s);
638 // Count the number of windows in the tabpage.
639 //win_T *wp = tp->tp_firstwin;
641 //for (wincount = 0; wp != NULL; wp = wp->w_next, ++wincount);
643 //[data appendBytes:&wincount length:sizeof(int)];
644 [data appendBytes:&len length:sizeof(int)];
645 [data appendBytes:s length:len];
648 CONVERT_TO_UTF8_FREE(s);
652 [self queueMessage:UpdateTabBarMsgID data:data];
655 - (BOOL)tabBarVisible
657 return tabBarVisible;
660 - (void)showTabBar:(BOOL)enable
662 tabBarVisible = enable;
664 int msgid = enable ? ShowTabBarMsgID : HideTabBarMsgID;
665 [self queueMessage:msgid data:nil];
668 - (void)setRows:(int)rows columns:(int)cols
670 //NSLog(@"[VimTask] setRows:%d columns:%d", rows, cols);
672 int dim[] = { rows, cols };
673 NSData *data = [NSData dataWithBytes:&dim length:2*sizeof(int)];
675 [self queueMessage:SetTextDimensionsMsgID data:data];
678 - (void)setWindowTitle:(char *)title
680 NSMutableData *data = [NSMutableData data];
681 int len = strlen(title);
682 if (len <= 0) return;
684 [data appendBytes:&len length:sizeof(int)];
685 [data appendBytes:title length:len];
687 [self queueMessage:SetWindowTitleMsgID data:data];
690 - (void)setDocumentFilename:(char *)filename
692 NSMutableData *data = [NSMutableData data];
693 int len = filename ? strlen(filename) : 0;
695 [data appendBytes:&len length:sizeof(int)];
697 [data appendBytes:filename length:len];
699 [self queueMessage:SetDocumentFilenameMsgID data:data];
702 - (char *)browseForFileWithAttributes:(NSDictionary *)attr
707 [frontendProxy showSavePanelWithAttributes:attr];
709 [self waitForDialogReturn];
711 if (dialogReturn && [dialogReturn isKindOfClass:[NSString class]]) {
712 char_u *ret = (char_u*)[dialogReturn UTF8String];
714 ret = CONVERT_FROM_UTF8(ret);
716 s = vim_strsave(ret);
718 CONVERT_FROM_UTF8_FREE(ret);
722 [dialogReturn release]; dialogReturn = nil;
724 @catch (NSException *e) {
725 NSLog(@"Exception caught when showing save panel: \"%@\"", e);
731 - (oneway void)setDialogReturn:(in bycopy id)obj
733 // NOTE: This is called by
734 // - [MMVimController panelDidEnd:::], and
735 // - [MMVimController alertDidEnd:::],
736 // to indicate that a save/open panel or alert has finished.
738 // We want to distinguish between "no dialog return yet" and "dialog
739 // returned nothing". The former can be tested with dialogReturn == nil,
740 // the latter with dialogReturn == [NSNull null].
741 if (!obj) obj = [NSNull null];
743 if (obj != dialogReturn) {
744 [dialogReturn release];
745 dialogReturn = [obj retain];
749 - (int)showDialogWithAttributes:(NSDictionary *)attr textField:(char *)txtfield
754 [frontendProxy presentDialogWithAttributes:attr];
756 [self waitForDialogReturn];
758 if (dialogReturn && [dialogReturn isKindOfClass:[NSArray class]]
759 && [dialogReturn count]) {
760 retval = [[dialogReturn objectAtIndex:0] intValue];
761 if (txtfield && [dialogReturn count] > 1) {
762 NSString *retString = [dialogReturn objectAtIndex:1];
763 char_u *ret = (char_u*)[retString UTF8String];
765 ret = CONVERT_FROM_UTF8(ret);
767 vim_strncpy((char_u*)txtfield, ret, IOSIZE - 1);
769 CONVERT_FROM_UTF8_FREE(ret);
774 [dialogReturn release]; dialogReturn = nil;
776 @catch (NSException *e) {
777 NSLog(@"Exception caught while showing alert dialog: \"%@\"", e);
783 - (void)showToolbar:(int)enable flags:(int)flags
785 NSMutableData *data = [NSMutableData data];
787 [data appendBytes:&enable length:sizeof(int)];
788 [data appendBytes:&flags length:sizeof(int)];
790 [self queueMessage:ShowToolbarMsgID data:data];
793 - (void)createScrollbarWithIdentifier:(long)ident type:(int)type
795 NSMutableData *data = [NSMutableData data];
797 [data appendBytes:&ident length:sizeof(long)];
798 [data appendBytes:&type length:sizeof(int)];
800 [self queueMessage:CreateScrollbarMsgID data:data];
803 - (void)destroyScrollbarWithIdentifier:(long)ident
805 NSMutableData *data = [NSMutableData data];
806 [data appendBytes:&ident length:sizeof(long)];
808 [self queueMessage:DestroyScrollbarMsgID data:data];
811 - (void)showScrollbarWithIdentifier:(long)ident state:(int)visible
813 NSMutableData *data = [NSMutableData data];
815 [data appendBytes:&ident length:sizeof(long)];
816 [data appendBytes:&visible length:sizeof(int)];
818 [self queueMessage:ShowScrollbarMsgID data:data];
821 - (void)setScrollbarPosition:(int)pos length:(int)len identifier:(long)ident
823 NSMutableData *data = [NSMutableData data];
825 [data appendBytes:&ident length:sizeof(long)];
826 [data appendBytes:&pos length:sizeof(int)];
827 [data appendBytes:&len length:sizeof(int)];
829 [self queueMessage:SetScrollbarPositionMsgID data:data];
832 - (void)setScrollbarThumbValue:(long)val size:(long)size max:(long)max
833 identifier:(long)ident
835 float fval = max-size+1 > 0 ? (float)val/(max-size+1) : 0;
836 float prop = (float)size/(max+1);
837 if (fval < 0) fval = 0;
838 else if (fval > 1.0f) fval = 1.0f;
839 if (prop < 0) prop = 0;
840 else if (prop > 1.0f) prop = 1.0f;
842 NSMutableData *data = [NSMutableData data];
844 [data appendBytes:&ident length:sizeof(long)];
845 [data appendBytes:&fval length:sizeof(float)];
846 [data appendBytes:&prop length:sizeof(float)];
848 [self queueMessage:SetScrollbarThumbMsgID data:data];
851 - (void)setFont:(NSFont *)font
853 NSString *fontName = [font displayName];
854 float size = [font pointSize];
855 int len = [fontName lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
857 NSMutableData *data = [NSMutableData data];
859 [data appendBytes:&size length:sizeof(float)];
860 [data appendBytes:&len length:sizeof(int)];
861 [data appendBytes:[fontName UTF8String] length:len];
863 [self queueMessage:SetFontMsgID data:data];
867 - (void)setWideFont:(NSFont *)font
869 NSString *fontName = [font displayName];
870 float size = [font pointSize];
871 int len = [fontName lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
872 NSMutableData *data = [NSMutableData data];
874 [data appendBytes:&size length:sizeof(float)];
875 [data appendBytes:&len length:sizeof(int)];
877 [data appendBytes:[fontName UTF8String] length:len];
879 [self queueMessage:SetWideFontMsgID data:data];
882 - (void)executeActionWithName:(NSString *)name
884 int len = [name lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
887 NSMutableData *data = [NSMutableData data];
889 [data appendBytes:&len length:sizeof(int)];
890 [data appendBytes:[name UTF8String] length:len];
892 [self queueMessage:ExecuteActionMsgID data:data];
896 - (void)setMouseShape:(int)shape
898 NSMutableData *data = [NSMutableData data];
899 [data appendBytes:&shape length:sizeof(int)];
900 [self queueMessage:SetMouseShapeMsgID data:data];
903 - (void)setBlinkWait:(int)wait on:(int)on off:(int)off
905 // Vim specifies times in milliseconds, whereas Cocoa wants them in
907 blinkWaitInterval = .001f*wait;
908 blinkOnInterval = .001f*on;
909 blinkOffInterval = .001f*off;
915 [blinkTimer invalidate];
916 [blinkTimer release];
920 if (blinkWaitInterval > 0 && blinkOnInterval > 0 && blinkOffInterval > 0
922 blinkState = MMBlinkStateOn;
924 [[NSTimer scheduledTimerWithTimeInterval:blinkWaitInterval
926 selector:@selector(blinkTimerFired:)
927 userInfo:nil repeats:NO] retain];
928 gui_update_cursor(TRUE, FALSE);
929 [self flushQueue:YES];
935 if (MMBlinkStateOff == blinkState) {
936 gui_update_cursor(TRUE, FALSE);
937 [self flushQueue:YES];
940 blinkState = MMBlinkStateNone;
943 - (void)adjustLinespace:(int)linespace
945 NSMutableData *data = [NSMutableData data];
946 [data appendBytes:&linespace length:sizeof(int)];
947 [self queueMessage:AdjustLinespaceMsgID data:data];
952 [self queueMessage:ActivateMsgID data:nil];
955 - (void)setPreEditRow:(int)row column:(int)col
957 NSMutableData *data = [NSMutableData data];
958 [data appendBytes:&row length:sizeof(int)];
959 [data appendBytes:&col length:sizeof(int)];
960 [self queueMessage:SetPreEditPositionMsgID data:data];
963 - (int)lookupColorWithKey:(NSString *)key
965 if (!(key && [key length] > 0))
968 NSString *stripKey = [[[[key lowercaseString]
969 stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]
970 componentsSeparatedByString:@" "]
971 componentsJoinedByString:@""];
973 if (stripKey && [stripKey length] > 0) {
974 // First of all try to lookup key in the color dictionary; note that
975 // all keys in this dictionary are lowercase with no whitespace.
976 id obj = [colorDict objectForKey:stripKey];
977 if (obj) return [obj intValue];
979 // The key was not in the dictionary; is it perhaps of the form
981 if ([stripKey length] > 1 && [stripKey characterAtIndex:0] == '#') {
982 NSScanner *scanner = [NSScanner scannerWithString:stripKey];
983 [scanner setScanLocation:1];
985 if ([scanner scanHexInt:&hex]) {
990 // As a last resort, check if it is one of the system defined colors.
991 // The keys in this dictionary are also lowercase with no whitespace.
992 obj = [sysColorDict objectForKey:stripKey];
994 NSColor *col = [NSColor performSelector:NSSelectorFromString(obj)];
997 col = [col colorUsingColorSpaceName:NSCalibratedRGBColorSpace];
998 [col getRed:&r green:&g blue:&b alpha:&a];
999 return (((int)(r*255+.5f) & 0xff) << 16)
1000 + (((int)(g*255+.5f) & 0xff) << 8)
1001 + ((int)(b*255+.5f) & 0xff);
1006 //NSLog(@"WARNING: No color with key %@ found.", stripKey);
1010 - (BOOL)hasSpecialKeyWithValue:(NSString *)value
1012 NSEnumerator *e = [[MMBackend specialKeys] objectEnumerator];
1015 while ((obj = [e nextObject])) {
1016 if ([value isEqual:obj])
1023 - (void)enterFullscreen:(int)fuoptions background:(int)bg
1025 NSMutableData *data = [NSMutableData data];
1026 [data appendBytes:&fuoptions length:sizeof(int)];
1028 [data appendBytes:&bg length:sizeof(int)];
1029 [self queueMessage:EnterFullscreenMsgID data:data];
1032 - (void)leaveFullscreen
1034 [self queueMessage:LeaveFullscreenMsgID data:nil];
1037 - (void)setAntialias:(BOOL)antialias
1039 int msgid = antialias ? EnableAntialiasMsgID : DisableAntialiasMsgID;
1041 [self queueMessage:msgid data:nil];
1044 - (void)updateModifiedFlag
1046 // Notify MacVim if _any_ buffer has changed from unmodified to modified or
1048 int msgid = [self checkForModifiedBuffers]
1049 ? BuffersModifiedMsgID : BuffersNotModifiedMsgID;
1051 [self queueMessage:msgid data:nil];
1054 - (oneway void)processInput:(int)msgid data:(in bycopy NSData *)data
1056 // NOTE: This method might get called whenever the run loop is tended to.
1057 // Normal keyboard and mouse input is added to input buffers, so there is
1058 // no risk in handling these events directly (they return immediately, and
1059 // do not call any other Vim functions). However, other events such
1060 // as 'VimShouldCloseMsgID' may enter blocking loops that wait for key
1061 // events which would cause this method to be called recursively. This
1062 // in turn leads to various difficulties that we do not want to have to
1063 // deal with. To avoid recursive calls here we add all events except
1064 // keyboard and mouse events to an input queue which is processed whenever
1065 // gui_mch_update() is called (see processInputQueue).
1067 //NSLog(@"%s%s", _cmd, MessageStrings[msgid]);
1069 // Don't flush too soon after receiving input or update speed will suffer.
1070 [lastFlushDate release];
1071 lastFlushDate = [[NSDate date] retain];
1073 // Handle keyboard and mouse input now. All other events are queued.
1074 if (InsertTextMsgID == msgid) {
1075 [self handleInsertText:data];
1076 } else if (KeyDownMsgID == msgid || CmdKeyMsgID == msgid) {
1078 const void *bytes = [data bytes];
1079 int mods = *((int*)bytes); bytes += sizeof(int);
1080 int len = *((int*)bytes); bytes += sizeof(int);
1081 NSString *key = [[NSString alloc] initWithBytes:bytes length:len
1082 encoding:NSUTF8StringEncoding];
1083 mods = eventModifierFlagsToVimModMask(mods);
1085 [self handleKeyDown:key modifiers:mods];
1088 } else if (ScrollWheelMsgID == msgid) {
1090 const void *bytes = [data bytes];
1092 int row = *((int*)bytes); bytes += sizeof(int);
1093 int col = *((int*)bytes); bytes += sizeof(int);
1094 int flags = *((int*)bytes); bytes += sizeof(int);
1095 float dy = *((float*)bytes); bytes += sizeof(float);
1097 int button = MOUSE_5;
1098 if (dy > 0) button = MOUSE_4;
1100 flags = eventModifierFlagsToVimMouseModMask(flags);
1102 int numLines = (int)round(dy);
1103 if (numLines < 0) numLines = -numLines;
1104 if (numLines == 0) numLines = 1;
1106 #ifdef FEAT_GUI_SCROLL_WHEEL_FORCE
1107 gui.scroll_wheel_force = numLines;
1110 gui_send_mouse_event(button, col, row, NO, flags);
1111 } else if (MouseDownMsgID == msgid) {
1113 const void *bytes = [data bytes];
1115 int row = *((int*)bytes); bytes += sizeof(int);
1116 int col = *((int*)bytes); bytes += sizeof(int);
1117 int button = *((int*)bytes); bytes += sizeof(int);
1118 int flags = *((int*)bytes); bytes += sizeof(int);
1119 int count = *((int*)bytes); bytes += sizeof(int);
1121 button = eventButtonNumberToVimMouseButton(button);
1123 flags = eventModifierFlagsToVimMouseModMask(flags);
1124 gui_send_mouse_event(button, col, row, count>1, flags);
1126 } else if (MouseUpMsgID == msgid) {
1128 const void *bytes = [data bytes];
1130 int row = *((int*)bytes); bytes += sizeof(int);
1131 int col = *((int*)bytes); bytes += sizeof(int);
1132 int flags = *((int*)bytes); bytes += sizeof(int);
1134 flags = eventModifierFlagsToVimMouseModMask(flags);
1136 gui_send_mouse_event(MOUSE_RELEASE, col, row, NO, flags);
1137 } else if (MouseDraggedMsgID == msgid) {
1139 const void *bytes = [data bytes];
1141 int row = *((int*)bytes); bytes += sizeof(int);
1142 int col = *((int*)bytes); bytes += sizeof(int);
1143 int flags = *((int*)bytes); bytes += sizeof(int);
1145 flags = eventModifierFlagsToVimMouseModMask(flags);
1147 gui_send_mouse_event(MOUSE_DRAG, col, row, NO, flags);
1148 } else if (MouseMovedMsgID == msgid) {
1149 const void *bytes = [data bytes];
1150 int row = *((int*)bytes); bytes += sizeof(int);
1151 int col = *((int*)bytes); bytes += sizeof(int);
1153 gui_mouse_moved(col, row);
1154 } else if (AddInputMsgID == msgid) {
1155 NSString *string = [[NSString alloc] initWithData:data
1156 encoding:NSUTF8StringEncoding];
1158 [self addInput:string];
1161 } else if (TerminateNowMsgID == msgid) {
1162 isTerminating = YES;
1164 // Not keyboard or mouse event, queue it and handle later.
1165 //NSLog(@"Add event %s to input event queue", MessageStrings[msgid]);
1166 [inputQueue addObject:[NSNumber numberWithInt:msgid]];
1167 [inputQueue addObject:(data ? (id)data : (id)[NSNull null])];
1170 // See waitForInput: for an explanation of this flag.
1171 inputReceived = YES;
1174 - (oneway void)processInputAndData:(in bycopy NSArray *)messages
1176 // TODO: Get rid of this method?
1177 //NSLog(@"%s%@", _cmd, messages);
1179 unsigned i, count = [messages count];
1180 for (i = 0; i < count; i += 2) {
1181 int msgid = [[messages objectAtIndex:i] intValue];
1182 id data = [messages objectAtIndex:i+1];
1183 if ([data isEqual:[NSNull null]])
1186 [self processInput:msgid data:data];
1190 - (id)evaluateExpressionCocoa:(in bycopy NSString *)expr
1191 errorString:(out bycopy NSString **)errstr
1193 return evalExprCocoa(expr, errstr);
1197 - (NSString *)evaluateExpression:(in bycopy NSString *)expr
1199 NSString *eval = nil;
1200 char_u *s = (char_u*)[expr UTF8String];
1203 s = CONVERT_FROM_UTF8(s);
1206 char_u *res = eval_client_expr_to_string(s);
1209 CONVERT_FROM_UTF8_FREE(s);
1215 s = CONVERT_TO_UTF8(s);
1217 eval = [NSString stringWithUTF8String:(char*)s];
1219 CONVERT_TO_UTF8_FREE(s);
1227 - (BOOL)starRegisterToPasteboard:(byref NSPasteboard *)pboard
1229 // TODO: This method should share code with clip_mch_request_selection().
1231 if (VIsual_active && (State & NORMAL) && clip_star.available) {
1232 // If there is no pasteboard, return YES to indicate that there is text
1237 clip_copy_selection();
1239 // Get the text to put on the pasteboard.
1240 long_u llen = 0; char_u *str = 0;
1241 int type = clip_convert_selection(&str, &llen, &clip_star);
1245 // TODO: Avoid overflow.
1246 int len = (int)llen;
1248 if (output_conv.vc_type != CONV_NONE) {
1249 char_u *conv_str = string_convert(&output_conv, str, &len);
1257 NSString *string = [[NSString alloc]
1258 initWithBytes:str length:len encoding:NSUTF8StringEncoding];
1260 NSArray *types = [NSArray arrayWithObject:NSStringPboardType];
1261 [pboard declareTypes:types owner:nil];
1262 BOOL ok = [pboard setString:string forType:NSStringPboardType];
1273 - (oneway void)addReply:(in bycopy NSString *)reply
1274 server:(in byref id <MMVimServerProtocol>)server
1276 //NSLog(@"addReply:%@ server:%@", reply, (id)server);
1278 // Replies might come at any time and in any order so we keep them in an
1279 // array inside a dictionary with the send port used as key.
1281 NSConnection *conn = [(NSDistantObject*)server connectionForProxy];
1282 // HACK! Assume connection uses mach ports.
1283 int port = [(NSMachPort*)[conn sendPort] machPort];
1284 NSNumber *key = [NSNumber numberWithInt:port];
1286 NSMutableArray *replies = [serverReplyDict objectForKey:key];
1288 replies = [NSMutableArray array];
1289 [serverReplyDict setObject:replies forKey:key];
1292 [replies addObject:reply];
1295 - (void)addInput:(in bycopy NSString *)input
1296 client:(in byref id <MMVimClientProtocol>)client
1298 //NSLog(@"addInput:%@ client:%@", input, (id)client);
1300 [self addInput:input];
1301 [self addClient:(id)client];
1303 inputReceived = YES;
1306 - (NSString *)evaluateExpression:(in bycopy NSString *)expr
1307 client:(in byref id <MMVimClientProtocol>)client
1309 [self addClient:(id)client];
1310 return [self evaluateExpression:expr];
1313 - (void)registerServerWithName:(NSString *)name
1315 NSString *svrName = name;
1316 NSConnection *svrConn = [NSConnection defaultConnection];
1319 for (i = 0; i < MMServerMax; ++i) {
1320 NSString *connName = [self connectionNameFromServerName:svrName];
1322 if ([svrConn registerName:connName]) {
1323 //NSLog(@"Registered server with name: %@", svrName);
1325 // TODO: Set request/reply time-outs to something else?
1327 // Don't wait for requests (time-out means that the message is
1329 [svrConn setRequestTimeout:0];
1330 //[svrConn setReplyTimeout:MMReplyTimeout];
1331 [svrConn setRootObject:self];
1333 char_u *s = (char_u*)[svrName UTF8String];
1335 s = CONVERT_FROM_UTF8(s);
1337 // NOTE: 'serverName' is a global variable
1338 serverName = vim_strsave(s);
1340 CONVERT_FROM_UTF8_FREE(s);
1343 set_vim_var_string(VV_SEND_SERVER, serverName, -1);
1346 need_maketitle = TRUE;
1348 [self queueMessage:SetServerNameMsgID data:
1349 [svrName dataUsingEncoding:NSUTF8StringEncoding]];
1353 svrName = [NSString stringWithFormat:@"%@%d", name, i+1];
1357 - (BOOL)sendToServer:(NSString *)name string:(NSString *)string
1358 reply:(char_u **)reply port:(int *)port expression:(BOOL)expr
1361 // NOTE: If 'name' equals 'serverName' then the request is local (client
1362 // and server are the same). This case is not handled separately, so a
1363 // connection will be set up anyway (this simplifies the code).
1365 NSConnection *conn = [self connectionForServerName:name];
1368 char_u *s = (char_u*)[name UTF8String];
1370 s = CONVERT_FROM_UTF8(s);
1372 EMSG2(_(e_noserver), s);
1374 CONVERT_FROM_UTF8_FREE(s);
1381 // HACK! Assume connection uses mach ports.
1382 *port = [(NSMachPort*)[conn sendPort] machPort];
1385 id proxy = [conn rootProxy];
1386 [proxy setProtocolForProxy:@protocol(MMVimServerProtocol)];
1390 NSString *eval = [proxy evaluateExpression:string client:self];
1393 char_u *r = (char_u*)[eval UTF8String];
1395 r = CONVERT_FROM_UTF8(r);
1397 *reply = vim_strsave(r);
1399 CONVERT_FROM_UTF8_FREE(r);
1402 *reply = vim_strsave((char_u*)_(e_invexprmsg));
1409 [proxy addInput:string client:self];
1412 @catch (NSException *e) {
1413 NSLog(@"WARNING: Caught exception in %s: \"%@\"", _cmd, e);
1420 - (NSArray *)serverList
1422 NSArray *list = nil;
1424 if ([self connection]) {
1425 id proxy = [connection rootProxy];
1426 [proxy setProtocolForProxy:@protocol(MMAppProtocol)];
1429 list = [proxy serverList];
1431 @catch (NSException *e) {
1432 NSLog(@"Exception caught when listing servers: \"%@\"", e);
1435 EMSG(_("E???: No connection to MacVim, server listing not possible."));
1441 - (NSString *)peekForReplyOnPort:(int)port
1443 //NSLog(@"%s%d", _cmd, port);
1445 NSNumber *key = [NSNumber numberWithInt:port];
1446 NSMutableArray *replies = [serverReplyDict objectForKey:key];
1447 if (replies && [replies count]) {
1448 //NSLog(@" %d replies, topmost is: %@", [replies count],
1449 // [replies objectAtIndex:0]);
1450 return [replies objectAtIndex:0];
1453 //NSLog(@" No replies");
1457 - (NSString *)waitForReplyOnPort:(int)port
1459 //NSLog(@"%s%d", _cmd, port);
1461 NSConnection *conn = [self connectionForServerPort:port];
1465 NSNumber *key = [NSNumber numberWithInt:port];
1466 NSMutableArray *replies = nil;
1467 NSString *reply = nil;
1469 // Wait for reply as long as the connection to the server is valid (unless
1470 // user interrupts wait with Ctrl-C).
1471 while (!got_int && [conn isValid] &&
1472 !(replies = [serverReplyDict objectForKey:key])) {
1473 [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
1474 beforeDate:[NSDate distantFuture]];
1478 if ([replies count] > 0) {
1479 reply = [[replies objectAtIndex:0] retain];
1480 //NSLog(@" Got reply: %@", reply);
1481 [replies removeObjectAtIndex:0];
1482 [reply autorelease];
1485 if ([replies count] == 0)
1486 [serverReplyDict removeObjectForKey:key];
1492 - (BOOL)sendReply:(NSString *)reply toPort:(int)port
1494 id client = [clientProxyDict objectForKey:[NSNumber numberWithInt:port]];
1497 //NSLog(@"sendReply:%@ toPort:%d", reply, port);
1498 [client addReply:reply server:self];
1501 @catch (NSException *e) {
1502 NSLog(@"WARNING: Exception caught in %s: \"%@\"", _cmd, e);
1505 EMSG2(_("E???: server2client failed; no client with id 0x%x"), port);
1516 - (void)setWaitForAck:(BOOL)yn
1521 - (void)waitForConnectionAcknowledgement
1523 if (!waitForAck) return;
1525 while (waitForAck && !got_int && [connection isValid] && !isTerminating) {
1526 [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
1527 beforeDate:[NSDate distantFuture]];
1528 //NSLog(@" waitForAck=%d got_int=%d isTerminating=%d isValid=%d",
1529 // waitForAck, got_int, isTerminating, [connection isValid]);
1533 // Never received a connection acknowledgement, so die.
1534 [[NSNotificationCenter defaultCenter] removeObserver:self];
1535 [frontendProxy release]; frontendProxy = nil;
1537 // NOTE: We intentionally do not call mch_exit() since this in turn
1538 // will lead to -[MMBackend exit] getting called which we want to
1543 [self processInputQueue];
1544 [self openVimWindow];
1547 - (oneway void)acknowledgeConnection
1549 //NSLog(@"%s", _cmd);
1557 @implementation MMBackend (Private)
1559 - (void)waitForDialogReturn
1561 // Keep processing the run loop until a dialog returns. To avoid getting
1562 // stuck in an endless loop (could happen if the setDialogReturn: message
1563 // was lost) we also do some paranoia checks.
1565 // Note that in Cocoa the user can still resize windows and select menu
1566 // items while a sheet is being displayed, so we can't just wait for the
1567 // first message to arrive and assume that is the setDialogReturn: call.
1569 while (nil == dialogReturn && !got_int && [connection isValid]
1571 [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
1572 beforeDate:[NSDate distantFuture]];
1574 // Search for any resize messages on the input queue. All other messages
1575 // on the input queue are dropped. The reason why we single out resize
1576 // messages is because the user may have resized the window while a sheet
1578 int i, count = [inputQueue count];
1580 id textDimData = nil;
1582 for (i = count-2; i >= 0; i -= 2) {
1583 int msgid = [[inputQueue objectAtIndex:i] intValue];
1584 if (SetTextDimensionsMsgID == msgid) {
1585 textDimData = [[inputQueue objectAtIndex:i+1] retain];
1591 [inputQueue removeAllObjects];
1594 [inputQueue addObject:
1595 [NSNumber numberWithInt:SetTextDimensionsMsgID]];
1596 [inputQueue addObject:textDimData];
1597 [textDimData release];
1602 - (void)queueVimStateMessage
1604 // NOTE: This is the place to add Vim state that needs to be accessed from
1605 // MacVim. Do not add state that could potentially require lots of memory
1606 // since this message gets sent each time the output queue is forcibly
1607 // flushed (e.g. storing the currently selected text would be a bad idea).
1608 // We take this approach of "pushing" the state to MacVim to avoid having
1609 // to make synchronous calls from MacVim to Vim in order to get state.
1611 NSDictionary *vimState = [NSDictionary dictionaryWithObjectsAndKeys:
1612 [[NSFileManager defaultManager] currentDirectoryPath], @"pwd",
1613 [NSNumber numberWithInt:p_mh], @"p_mh",
1616 [self queueMessage:SetVimStateMsgID data:[vimState dictionaryAsData]];
1619 - (void)processInputQueue
1621 if ([inputQueue count] == 0) return;
1623 // NOTE: One of the input events may cause this method to be called
1624 // recursively, so copy the input queue to a local variable and clear it
1625 // before starting to process input events (otherwise we could get stuck in
1626 // an endless loop).
1627 NSArray *q = [inputQueue copy];
1628 unsigned i, count = [q count];
1630 [inputQueue removeAllObjects];
1632 for (i = 0; i < count-1; i += 2) {
1633 int msgid = [[q objectAtIndex:i] intValue];
1634 id data = [q objectAtIndex:i+1];
1635 if ([data isEqual:[NSNull null]])
1638 //NSLog(@"(%d) %s:%s", i, _cmd, MessageStrings[msgid]);
1639 [self handleInputEvent:msgid data:data];
1643 //NSLog(@"Clear input event queue");
1646 - (void)handleInputEvent:(int)msgid data:(NSData *)data
1648 //NSLog(@"%s%s", _cmd, MessageStrings[msgid]);
1650 if (SelectTabMsgID == msgid) {
1652 const void *bytes = [data bytes];
1653 int idx = *((int*)bytes) + 1;
1654 //NSLog(@"Selecting tab %d", idx);
1655 send_tabline_event(idx);
1656 } else if (CloseTabMsgID == msgid) {
1658 const void *bytes = [data bytes];
1659 int idx = *((int*)bytes) + 1;
1660 //NSLog(@"Closing tab %d", idx);
1661 send_tabline_menu_event(idx, TABLINE_MENU_CLOSE);
1662 } else if (AddNewTabMsgID == msgid) {
1663 //NSLog(@"Adding new tab");
1664 send_tabline_menu_event(0, TABLINE_MENU_NEW);
1665 } else if (DraggedTabMsgID == msgid) {
1667 const void *bytes = [data bytes];
1668 // NOTE! The destination index is 0 based, so do not add 1 to make it 1
1670 int idx = *((int*)bytes);
1673 } else if (SetTextDimensionsMsgID == msgid || LiveResizeMsgID == msgid) {
1675 const void *bytes = [data bytes];
1676 int rows = *((int*)bytes); bytes += sizeof(int);
1677 int cols = *((int*)bytes); bytes += sizeof(int);
1679 // NOTE! Vim doesn't call gui_mch_set_shellsize() after
1680 // gui_resize_shell(), so we have to manually set the rows and columns
1681 // here. (MacVim doesn't change the rows and columns to avoid
1682 // inconsistent states between Vim and MacVim.)
1683 [self queueMessage:msgid data:data];
1685 //NSLog(@"[VimTask] Resizing shell to %dx%d.", cols, rows);
1686 gui_resize_shell(cols, rows);
1687 } else if (ExecuteMenuMsgID == msgid) {
1688 NSDictionary *attrs = [NSDictionary dictionaryWithData:data];
1690 NSArray *desc = [attrs objectForKey:@"descriptor"];
1691 vimmenu_T *menu = menu_for_descriptor(desc);
1695 } else if (ToggleToolbarMsgID == msgid) {
1696 [self handleToggleToolbar];
1697 } else if (ScrollbarEventMsgID == msgid) {
1698 [self handleScrollbarEvent:data];
1699 } else if (SetFontMsgID == msgid) {
1700 [self handleSetFont:data];
1701 } else if (VimShouldCloseMsgID == msgid) {
1703 } else if (DropFilesMsgID == msgid) {
1704 [self handleDropFiles:data];
1705 } else if (DropStringMsgID == msgid) {
1706 [self handleDropString:data];
1707 } else if (GotFocusMsgID == msgid) {
1709 [self focusChange:YES];
1710 } else if (LostFocusMsgID == msgid) {
1712 [self focusChange:NO];
1713 } else if (SetMouseShapeMsgID == msgid) {
1714 const void *bytes = [data bytes];
1715 int shape = *((int*)bytes); bytes += sizeof(int);
1716 update_mouseshape(shape);
1717 } else if (XcodeModMsgID == msgid) {
1718 [self handleXcodeMod:data];
1719 } else if (OpenWithArgumentsMsgID == msgid) {
1720 [self handleOpenWithArguments:[NSDictionary dictionaryWithData:data]];
1722 NSLog(@"WARNING: Unknown message received (msgid=%d)", msgid);
1726 + (NSDictionary *)specialKeys
1728 static NSDictionary *specialKeys = nil;
1731 NSBundle *mainBundle = [NSBundle mainBundle];
1732 NSString *path = [mainBundle pathForResource:@"SpecialKeys"
1734 specialKeys = [[NSDictionary alloc] initWithContentsOfFile:path];
1740 - (void)handleInsertText:(NSData *)data
1744 NSString *key = [[NSString alloc] initWithData:data
1745 encoding:NSUTF8StringEncoding];
1746 char_u *str = (char_u*)[key UTF8String];
1747 int i, len = [key lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
1750 char_u *conv_str = NULL;
1751 if (input_conv.vc_type != CONV_NONE) {
1752 conv_str = string_convert(&input_conv, str, &len);
1758 if (len == 1 && ((str[0] == Ctrl_C && ctrl_c_interrupts)
1759 || (str[0] == intr_char && intr_char != Ctrl_C))) {
1764 for (i = 0; i < len; ++i) {
1765 add_to_input_buf(str+i, 1);
1766 if (CSI == str[i]) {
1767 // NOTE: If the converted string contains the byte CSI, then it
1768 // must be followed by the bytes KS_EXTRA, KE_CSI or things
1770 static char_u extra[2] = { KS_EXTRA, KE_CSI };
1771 add_to_input_buf(extra, 2);
1782 - (void)handleKeyDown:(NSString *)key modifiers:(int)mods
1786 char_u *chars = (char_u*)[key UTF8String];
1788 char_u *conv_str = NULL;
1790 int length = [key lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
1792 // Special keys (arrow keys, function keys, etc.) are stored in a plist so
1793 // that new keys can easily be added.
1794 NSString *specialString = [[MMBackend specialKeys]
1796 if (specialString && [specialString length] > 1) {
1797 //NSLog(@"special key: %@", specialString);
1798 int ikey = TO_SPECIAL([specialString characterAtIndex:0],
1799 [specialString characterAtIndex:1]);
1801 ikey = simplify_key(ikey, &mods);
1806 special[1] = K_SECOND(ikey);
1807 special[2] = K_THIRD(ikey);
1811 } else if (1 == length && TAB == chars[0]) {
1812 // Tab is a trouble child:
1813 // - <Tab> is added to the input buffer as is
1814 // - <S-Tab> is translated to, {CSI,'k','B'} (i.e. 'Back-tab')
1815 // - <M-Tab> should be 0x80|TAB but this is not valid utf-8 so it needs
1816 // to be converted to utf-8
1817 // - <S-M-Tab> is translated to <S-Tab> with ALT modifier
1818 // - <C-Tab> is reserved by Mac OS X
1819 // - <D-Tab> is reserved by Mac OS X
1824 if (mods & MOD_MASK_SHIFT) {
1825 mods &= ~MOD_MASK_SHIFT;
1827 special[1] = K_SECOND(K_S_TAB);
1828 special[2] = K_THIRD(K_S_TAB);
1830 } else if (mods & MOD_MASK_ALT) {
1831 int mtab = 0x80 | TAB;
1835 special[0] = (mtab >> 6) + 0xc0;
1836 special[1] = mtab & 0xbf;
1844 mods &= ~MOD_MASK_ALT;
1846 } else if (length > 0) {
1847 unichar c = [key characterAtIndex:0];
1849 //NSLog(@"non-special: %@ (hex=%x, mods=%d)", key,
1850 // [key characterAtIndex:0], mods);
1852 if (length == 1 && ((c == Ctrl_C && ctrl_c_interrupts)
1853 || (c == intr_char && intr_char != Ctrl_C))) {
1858 // HACK! In most circumstances the Ctrl and Shift modifiers should be
1859 // cleared since they are already added to the key by the AppKit.
1860 // Unfortunately, the only way to deal with when to clear the modifiers
1861 // or not seems to be to have hard-wired rules like this.
1862 if ( !((' ' == c) || (0xa0 == c) || (mods & MOD_MASK_CMD)
1863 || 0x9 == c || 0xd == c || ESC == c) ) {
1864 mods &= ~MOD_MASK_SHIFT;
1865 mods &= ~MOD_MASK_CTRL;
1866 //NSLog(@"clear shift ctrl");
1869 // HACK! All Option+key presses go via 'insert text' messages, except
1870 // for <M-Space>. If the Alt flag is not cleared for <M-Space> it does
1871 // not work to map to it.
1872 if (0xa0 == c && !(mods & MOD_MASK_CMD)) {
1873 //NSLog(@"clear alt");
1874 mods &= ~MOD_MASK_ALT;
1878 if (input_conv.vc_type != CONV_NONE) {
1879 conv_str = string_convert(&input_conv, chars, &length);
1886 if (chars && length > 0) {
1888 //NSLog(@"adding mods: %d", mods);
1890 modChars[1] = KS_MODIFIER;
1892 add_to_input_buf(modChars, 3);
1895 //NSLog(@"add to input buf: 0x%x", chars[0]);
1896 // TODO: Check for CSI bytes?
1897 add_to_input_buf(chars, length);
1906 - (void)queueMessage:(int)msgid data:(NSData *)data
1908 //if (msgid != EnableMenuItemMsgID)
1909 // NSLog(@"queueMessage:%s", MessageStrings[msgid]);
1911 [outputQueue addObject:[NSData dataWithBytes:&msgid length:sizeof(int)]];
1913 [outputQueue addObject:data];
1915 [outputQueue addObject:[NSData data]];
1918 - (void)connectionDidDie:(NSNotification *)notification
1920 // If the main connection to MacVim is lost this means that MacVim was
1921 // either quit (by the user chosing Quit on the MacVim menu), or it has
1922 // crashed. In the former case the flag 'isTerminating' is set and we then
1923 // quit cleanly; in the latter case we make sure the swap files are left
1926 // NOTE: This is not called if a Vim controller invalidates its connection.
1928 //NSLog(@"%s isTerminating=%d", _cmd, isTerminating);
1932 getout_preserve_modified(1);
1935 - (void)blinkTimerFired:(NSTimer *)timer
1937 NSTimeInterval timeInterval = 0;
1939 [blinkTimer release];
1942 if (MMBlinkStateOn == blinkState) {
1943 gui_undraw_cursor();
1944 blinkState = MMBlinkStateOff;
1945 timeInterval = blinkOffInterval;
1946 } else if (MMBlinkStateOff == blinkState) {
1947 gui_update_cursor(TRUE, FALSE);
1948 blinkState = MMBlinkStateOn;
1949 timeInterval = blinkOnInterval;
1952 if (timeInterval > 0) {
1954 [[NSTimer scheduledTimerWithTimeInterval:timeInterval target:self
1955 selector:@selector(blinkTimerFired:)
1956 userInfo:nil repeats:NO] retain];
1957 [self flushQueue:YES];
1961 - (void)focusChange:(BOOL)on
1963 gui_focus_change(on);
1966 - (void)handleToggleToolbar
1968 // If 'go' contains 'T', then remove it, else add it.
1970 char_u go[sizeof(GO_ALL)+2];
1975 p = vim_strchr(go, GO_TOOLBAR);
1979 char_u *end = go + len;
1985 go[len] = GO_TOOLBAR;
1989 set_option_value((char_u*)"guioptions", 0, go, 0);
1991 // Force screen redraw (does it have to be this complicated?).
1992 redraw_all_later(CLEAR);
1993 update_screen(NOT_VALID);
1996 gui_update_cursor(FALSE, FALSE);
2000 - (void)handleScrollbarEvent:(NSData *)data
2004 const void *bytes = [data bytes];
2005 long ident = *((long*)bytes); bytes += sizeof(long);
2006 int hitPart = *((int*)bytes); bytes += sizeof(int);
2007 float fval = *((float*)bytes); bytes += sizeof(float);
2008 scrollbar_T *sb = gui_find_scrollbar(ident);
2011 scrollbar_T *sb_info = sb->wp ? &sb->wp->w_scrollbars[0] : sb;
2012 long value = sb_info->value;
2013 long size = sb_info->size;
2014 long max = sb_info->max;
2015 BOOL isStillDragging = NO;
2016 BOOL updateKnob = YES;
2019 case NSScrollerDecrementPage:
2020 value -= (size > 2 ? size - 2 : 1);
2022 case NSScrollerIncrementPage:
2023 value += (size > 2 ? size - 2 : 1);
2025 case NSScrollerDecrementLine:
2028 case NSScrollerIncrementLine:
2031 case NSScrollerKnob:
2032 isStillDragging = YES;
2034 case NSScrollerKnobSlot:
2035 value = (long)(fval * (max - size + 1));
2042 //NSLog(@"value %d -> %d", sb_info->value, value);
2043 gui_drag_scrollbar(sb, value, isStillDragging);
2046 // Dragging the knob or option+clicking automatically updates
2047 // the knob position (on the actual NSScroller), so we only
2048 // need to set the knob position in the other cases.
2050 // Update both the left&right vertical scrollbars.
2051 long identLeft = sb->wp->w_scrollbars[SBAR_LEFT].ident;
2052 long identRight = sb->wp->w_scrollbars[SBAR_RIGHT].ident;
2053 [self setScrollbarThumbValue:value size:size max:max
2054 identifier:identLeft];
2055 [self setScrollbarThumbValue:value size:size max:max
2056 identifier:identRight];
2058 // Update the horizontal scrollbar.
2059 [self setScrollbarThumbValue:value size:size max:max
2066 - (void)handleSetFont:(NSData *)data
2070 const void *bytes = [data bytes];
2071 float pointSize = *((float*)bytes); bytes += sizeof(float);
2072 //unsigned len = *((unsigned*)bytes); bytes += sizeof(unsigned);
2073 bytes += sizeof(unsigned); // len not used
2075 NSMutableString *name = [NSMutableString stringWithUTF8String:bytes];
2076 [name appendString:[NSString stringWithFormat:@":h%.2f", pointSize]];
2077 char_u *s = (char_u*)[name UTF8String];
2080 s = CONVERT_FROM_UTF8(s);
2083 set_option_value((char_u*)"guifont", 0, s, 0);
2086 CONVERT_FROM_UTF8_FREE(s);
2089 // Force screen redraw (does it have to be this complicated?).
2090 redraw_all_later(CLEAR);
2091 update_screen(NOT_VALID);
2094 gui_update_cursor(FALSE, FALSE);
2098 - (void)handleDropFiles:(NSData *)data
2100 // TODO: Get rid of this method; instead use Vim script directly. At the
2101 // moment I know how to do this to open files in tabs, but I'm not sure how
2102 // to add the filenames to the command line when in command line mode.
2106 NSMutableDictionary *args = [NSMutableDictionary dictionaryWithData:data];
2109 id obj = [args objectForKey:@"forceOpen"];
2110 BOOL forceOpen = YES;
2112 forceOpen = [obj boolValue];
2114 NSArray *filenames = [args objectForKey:@"filenames"];
2115 if (!(filenames && [filenames count] > 0)) return;
2118 if (!forceOpen && (State & CMDLINE)) {
2119 // HACK! If Vim is in command line mode then the files names
2120 // should be added to the command line, instead of opening the
2121 // files in tabs (unless forceOpen is set). This is taken care of by
2122 // gui_handle_drop().
2123 int n = [filenames count];
2124 char_u **fnames = (char_u **)alloc(n * sizeof(char_u *));
2127 for (i = 0; i < n; ++i)
2128 fnames[i] = [[filenames objectAtIndex:i] vimStringSave];
2130 // NOTE! This function will free 'fnames'.
2131 // HACK! It is assumed that the 'x' and 'y' arguments are
2132 // unused when in command line mode.
2133 gui_handle_drop(0, 0, 0, fnames, n);
2138 [self handleOpenWithArguments:args];
2142 - (void)handleDropString:(NSData *)data
2147 char_u dropkey[3] = { CSI, KS_EXTRA, (char_u)KE_DROP };
2148 const void *bytes = [data bytes];
2149 int len = *((int*)bytes); bytes += sizeof(int);
2150 NSMutableString *string = [NSMutableString stringWithUTF8String:bytes];
2152 // Replace unrecognized end-of-line sequences with \x0a (line feed).
2153 NSRange range = { 0, [string length] };
2154 unsigned n = [string replaceOccurrencesOfString:@"\x0d\x0a"
2155 withString:@"\x0a" options:0
2158 n = [string replaceOccurrencesOfString:@"\x0d" withString:@"\x0a"
2159 options:0 range:range];
2162 len = [string lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
2163 char_u *s = (char_u*)[string UTF8String];
2165 if (input_conv.vc_type != CONV_NONE)
2166 s = string_convert(&input_conv, s, &len);
2168 dnd_yank_drag_data(s, len);
2170 if (input_conv.vc_type != CONV_NONE)
2173 add_to_input_buf(dropkey, sizeof(dropkey));
2177 - (void)startOdbEditWithArguments:(NSDictionary *)args
2179 #ifdef FEAT_ODB_EDITOR
2180 id obj = [args objectForKey:@"remoteID"];
2183 OSType serverID = [obj unsignedIntValue];
2184 NSString *remotePath = [args objectForKey:@"remotePath"];
2186 NSAppleEventDescriptor *token = nil;
2187 NSData *tokenData = [args objectForKey:@"remoteTokenData"];
2188 obj = [args objectForKey:@"remoteTokenDescType"];
2189 if (tokenData && obj) {
2190 DescType tokenType = [obj unsignedLongValue];
2191 token = [NSAppleEventDescriptor descriptorWithDescriptorType:tokenType
2195 NSArray *filenames = [args objectForKey:@"filenames"];
2196 unsigned i, numFiles = [filenames count];
2197 for (i = 0; i < numFiles; ++i) {
2198 NSString *filename = [filenames objectAtIndex:i];
2199 char_u *s = [filename vimStringSave];
2200 buf_T *buf = buflist_findname(s);
2204 if (buf->b_odb_token) {
2205 [(NSAppleEventDescriptor*)(buf->b_odb_token) release];
2206 buf->b_odb_token = NULL;
2209 if (buf->b_odb_fname) {
2210 vim_free(buf->b_odb_fname);
2211 buf->b_odb_fname = NULL;
2214 buf->b_odb_server_id = serverID;
2217 buf->b_odb_token = [token retain];
2219 buf->b_odb_fname = [remotePath vimStringSave];
2221 NSLog(@"WARNING: Could not find buffer '%@' for ODB editing.",
2225 #endif // FEAT_ODB_EDITOR
2228 - (void)handleXcodeMod:(NSData *)data
2231 const void *bytes = [data bytes];
2232 DescType type = *((DescType*)bytes); bytes += sizeof(DescType);
2233 unsigned len = *((unsigned*)bytes); bytes += sizeof(unsigned);
2237 NSAppleEventDescriptor *replyEvent = [NSAppleEventDescriptor
2238 descriptorWithDescriptorType:type
2244 - (void)handleOpenWithArguments:(NSDictionary *)args
2246 // ARGUMENT: DESCRIPTION:
2247 // -------------------------------------------------------------
2248 // filenames list of filenames
2249 // dontOpen don't open files specified in above argument
2250 // layout which layout to use to open files
2251 // selectionRange range to select
2252 // searchText string to search for
2253 // remoteID ODB parameter
2254 // remotePath ODB parameter
2255 // remoteTokenDescType ODB parameter
2256 // remoteTokenData ODB parameter
2258 //NSLog(@"%s%@ (starting=%d)", _cmd, args, starting);
2260 NSArray *filenames = [args objectForKey:@"filenames"];
2261 int i, numFiles = filenames ? [filenames count] : 0;
2262 BOOL openFiles = ![[args objectForKey:@"dontOpen"] boolValue];
2263 int layout = [[args objectForKey:@"layout"] intValue];
2266 // When Vim is starting we simply add the files to be opened to the
2267 // global arglist and Vim will take care of opening them for us.
2268 if (openFiles && numFiles > 0) {
2269 for (i = 0; i < numFiles; i++) {
2270 NSString *fname = [filenames objectAtIndex:i];
2273 if (ga_grow(&global_alist.al_ga, 1) == FAIL
2274 || (p = [fname vimStringSave]) == NULL)
2277 alist_add(&global_alist, p, 2);
2280 // Vim will take care of arranging the files added to the arglist
2281 // in windows or tabs; all we must do is to specify which layout to
2283 initialWindowLayout = layout;
2286 // When Vim is already open we resort to some trickery to open the
2287 // files with the specified layout.
2289 // TODO: Figure out a better way to handle this?
2290 if (openFiles && numFiles > 0) {
2291 BOOL oneWindowInTab = topframe ? YES
2292 : (topframe->fr_layout == FR_LEAF);
2293 BOOL bufChanged = NO;
2294 BOOL bufHasFilename = NO;
2296 bufChanged = check_changed(curbuf, TRUE, FALSE, FALSE, FALSE);
2297 bufHasFilename = curbuf->b_ffname != NULL;
2300 // Temporarily disable flushing since the following code may
2301 // potentially cause multiple redraws.
2302 flushDisabled = YES;
2304 BOOL onlyOneTab = (first_tabpage->tp_next == NULL);
2305 if (WIN_TABS == layout && !onlyOneTab) {
2306 // By going to the last tabpage we ensure that the new tabs
2307 // will appear last (if this call is left out, the taborder
2312 // Make sure we're in normal mode first.
2313 [self addInput:@"<C-\\><C-N>"];
2316 // With "split layout" we open a new tab before opening
2317 // multiple files if the current tab has more than one window
2318 // or if there is exactly one window but whose buffer has a
2319 // filename. (The :drop command ensures modified buffers get
2320 // their own window.)
2321 if ((WIN_HOR == layout || WIN_VER == layout) &&
2322 (!oneWindowInTab || bufHasFilename))
2323 [self addInput:@":tabnew<CR>"];
2325 // The files are opened by constructing a ":drop ..." command
2326 // and executing it.
2327 NSMutableString *cmd = (WIN_TABS == layout)
2328 ? [NSMutableString stringWithString:@":tab drop"]
2329 : [NSMutableString stringWithString:@":drop"];
2331 for (i = 0; i < numFiles; ++i) {
2332 NSString *file = [filenames objectAtIndex:i];
2333 file = [file stringByEscapingSpecialFilenameCharacters];
2334 [cmd appendString:@" "];
2335 [cmd appendString:file];
2338 [self addInput:cmd];
2340 // Split the view into multiple windows if requested.
2341 if (WIN_HOR == layout)
2342 [self addInput:@"|sall"];
2343 else if (WIN_VER == layout)
2344 [self addInput:@"|vert sall"];
2346 // Adding "|redr|f" ensures a "Hit ENTER" prompt is not shown.
2347 [self addInput:@"|redr|f<CR>"];
2349 // When opening one file we try to reuse the current window,
2350 // but not if its buffer is modified or has a filename.
2351 // However, the 'arglist' layout always opens the file in the
2353 NSString *file = [[filenames lastObject]
2354 stringByEscapingSpecialFilenameCharacters];
2356 if (WIN_HOR == layout) {
2357 if (!(bufHasFilename || bufChanged))
2358 cmd = [NSString stringWithFormat:@":e %@", file];
2360 cmd = [NSString stringWithFormat:@":sp %@", file];
2361 } else if (WIN_VER == layout) {
2362 if (!(bufHasFilename || bufChanged))
2363 cmd = [NSString stringWithFormat:@":e %@", file];
2365 cmd = [NSString stringWithFormat:@":vsp %@", file];
2366 } else if (WIN_TABS == layout) {
2367 if (oneWindowInTab && !(bufHasFilename || bufChanged))
2368 cmd = [NSString stringWithFormat:@":e %@", file];
2370 cmd = [NSString stringWithFormat:@":tabe %@", file];
2372 // (The :drop command will split if there is a modified
2374 cmd = [NSString stringWithFormat:@":drop %@", file];
2377 [self addInput:cmd];
2379 // Adding "|redr|f" ensures a "Hit ENTER" prompt is not shown.
2380 [self addInput:@"|redr|f<CR>"];
2383 // Force screen redraw (does it have to be this complicated?).
2384 // (This code was taken from the end of gui_handle_drop().)
2385 update_screen(NOT_VALID);
2388 gui_update_cursor(FALSE, FALSE);
2396 if ([args objectForKey:@"remoteID"]) {
2397 // NOTE: We have to delay processing any ODB related arguments since
2398 // the file(s) may not be opened until the input buffer is processed.
2399 [self performSelectorOnMainThread:@selector(startOdbEditWithArguments:)
2404 NSString *rangeString = [args objectForKey:@"selectionRange"];
2406 // Build a command line string that will select the given range of
2407 // lines. If range.length == 0, then position the cursor on the given
2408 // line but do not select.
2409 NSRange range = NSRangeFromString(rangeString);
2411 if (range.length > 0) {
2412 cmd = [NSString stringWithFormat:@"<C-\\><C-N>%dGV%dGz.0",
2413 NSMaxRange(range), range.location];
2415 cmd = [NSString stringWithFormat:@"<C-\\><C-N>%dGz.0",
2419 [self addInput:cmd];
2422 NSString *searchText = [args objectForKey:@"searchText"];
2424 // TODO: Searching is an exclusive motion, so if the pattern would
2425 // match on row 0 column 0 then this pattern will miss that match.
2426 [self addInput:[NSString stringWithFormat:@"<C-\\><C-N>gg/\\c%@<CR>",
2431 - (BOOL)checkForModifiedBuffers
2434 for (buf = firstbuf; buf != NULL; buf = buf->b_next) {
2435 if (bufIsChanged(buf)) {
2443 - (void)addInput:(NSString *)input
2445 char_u *s = (char_u*)[input UTF8String];
2448 s = CONVERT_FROM_UTF8(s);
2451 server_to_input_buf(s);
2454 CONVERT_FROM_UTF8_FREE(s);
2458 @end // MMBackend (Private)
2463 @implementation MMBackend (ClientServer)
2465 - (NSString *)connectionNameFromServerName:(NSString *)name
2467 NSString *bundleIdentifier = [[NSBundle mainBundle] bundleIdentifier];
2469 return [[NSString stringWithFormat:@"%@.%@", bundleIdentifier, name]
2473 - (NSConnection *)connectionForServerName:(NSString *)name
2475 // TODO: Try 'name%d' if 'name' fails.
2476 NSString *connName = [self connectionNameFromServerName:name];
2477 NSConnection *svrConn = [connectionNameDict objectForKey:connName];
2480 svrConn = [NSConnection connectionWithRegisteredName:connName
2482 // Try alternate server...
2483 if (!svrConn && alternateServerName) {
2484 //NSLog(@" trying to connect to alternate server: %@",
2485 // alternateServerName);
2486 connName = [self connectionNameFromServerName:alternateServerName];
2487 svrConn = [NSConnection connectionWithRegisteredName:connName
2491 // Try looking for alternate servers...
2493 //NSLog(@" looking for alternate servers...");
2494 NSString *alt = [self alternateServerNameForName:name];
2495 if (alt != alternateServerName) {
2496 //NSLog(@" found alternate server: %@", string);
2497 [alternateServerName release];
2498 alternateServerName = [alt copy];
2502 // Try alternate server again...
2503 if (!svrConn && alternateServerName) {
2504 //NSLog(@" trying to connect to alternate server: %@",
2505 // alternateServerName);
2506 connName = [self connectionNameFromServerName:alternateServerName];
2507 svrConn = [NSConnection connectionWithRegisteredName:connName
2512 [connectionNameDict setObject:svrConn forKey:connName];
2514 //NSLog(@"Adding %@ as connection observer for %@", self, svrConn);
2515 [[NSNotificationCenter defaultCenter] addObserver:self
2516 selector:@selector(serverConnectionDidDie:)
2517 name:NSConnectionDidDieNotification object:svrConn];
2524 - (NSConnection *)connectionForServerPort:(int)port
2527 NSEnumerator *e = [connectionNameDict objectEnumerator];
2529 while ((conn = [e nextObject])) {
2530 // HACK! Assume connection uses mach ports.
2531 if (port == [(NSMachPort*)[conn sendPort] machPort])
2538 - (void)serverConnectionDidDie:(NSNotification *)notification
2540 //NSLog(@"%s%@", _cmd, notification);
2542 NSConnection *svrConn = [notification object];
2544 //NSLog(@"Removing %@ as connection observer from %@", self, svrConn);
2545 [[NSNotificationCenter defaultCenter]
2547 name:NSConnectionDidDieNotification
2550 [connectionNameDict removeObjectsForKeys:
2551 [connectionNameDict allKeysForObject:svrConn]];
2553 // HACK! Assume connection uses mach ports.
2554 int port = [(NSMachPort*)[svrConn sendPort] machPort];
2555 NSNumber *key = [NSNumber numberWithInt:port];
2557 [clientProxyDict removeObjectForKey:key];
2558 [serverReplyDict removeObjectForKey:key];
2561 - (void)addClient:(NSDistantObject *)client
2563 NSConnection *conn = [client connectionForProxy];
2564 // HACK! Assume connection uses mach ports.
2565 int port = [(NSMachPort*)[conn sendPort] machPort];
2566 NSNumber *key = [NSNumber numberWithInt:port];
2568 if (![clientProxyDict objectForKey:key]) {
2569 [client setProtocolForProxy:@protocol(MMVimClientProtocol)];
2570 [clientProxyDict setObject:client forKey:key];
2573 // NOTE: 'clientWindow' is a global variable which is used by <client>
2574 clientWindow = port;
2577 - (NSString *)alternateServerNameForName:(NSString *)name
2579 if (!(name && [name length] > 0))
2582 // Only look for alternates if 'name' doesn't end in a digit.
2583 unichar lastChar = [name characterAtIndex:[name length]-1];
2584 if (lastChar >= '0' && lastChar <= '9')
2587 // Look for alternates among all current servers.
2588 NSArray *list = [self serverList];
2589 if (!(list && [list count] > 0))
2592 // Filter out servers starting with 'name' and ending with a number. The
2593 // (?i) pattern ensures that the match is case insensitive.
2594 NSString *pat = [NSString stringWithFormat:@"(?i)%@[0-9]+\\z", name];
2595 NSPredicate *pred = [NSPredicate predicateWithFormat:
2596 @"SELF MATCHES %@", pat];
2597 list = [list filteredArrayUsingPredicate:pred];
2598 if ([list count] > 0) {
2599 list = [list sortedArrayUsingSelector:@selector(serverNameCompare:)];
2600 return [list objectAtIndex:0];
2606 @end // MMBackend (ClientServer)
2611 @implementation NSString (MMServerNameCompare)
2612 - (NSComparisonResult)serverNameCompare:(NSString *)string
2614 return [self compare:string
2615 options:NSCaseInsensitiveSearch|NSNumericSearch];
2622 static int eventModifierFlagsToVimModMask(int modifierFlags)
2626 if (modifierFlags & NSShiftKeyMask)
2627 modMask |= MOD_MASK_SHIFT;
2628 if (modifierFlags & NSControlKeyMask)
2629 modMask |= MOD_MASK_CTRL;
2630 if (modifierFlags & NSAlternateKeyMask)
2631 modMask |= MOD_MASK_ALT;
2632 if (modifierFlags & NSCommandKeyMask)
2633 modMask |= MOD_MASK_CMD;
2638 static int eventModifierFlagsToVimMouseModMask(int modifierFlags)
2642 if (modifierFlags & NSShiftKeyMask)
2643 modMask |= MOUSE_SHIFT;
2644 if (modifierFlags & NSControlKeyMask)
2645 modMask |= MOUSE_CTRL;
2646 if (modifierFlags & NSAlternateKeyMask)
2647 modMask |= MOUSE_ALT;
2652 static int eventButtonNumberToVimMouseButton(int buttonNumber)
2654 static int mouseButton[] = { MOUSE_LEFT, MOUSE_RIGHT, MOUSE_MIDDLE };
2656 return (buttonNumber >= 0 && buttonNumber < 3)
2657 ? mouseButton[buttonNumber] : -1;
2662 // This function is modeled after the VimToPython function found in if_python.c
2663 // NB This does a deep copy by value, it does not lookup references like the
2664 // VimToPython function does. This is because I didn't want to deal with the
2665 // retain cycles that this would create, and we can cover 99% of the use cases
2666 // by ignoring it. If we ever switch to using GC in MacVim then this
2667 // functionality can be implemented easily.
2668 static id vimToCocoa(typval_T * tv, int depth)
2674 // Avoid infinite recursion
2679 if (tv->v_type == VAR_STRING) {
2680 char_u * val = tv->vval.v_string;
2681 // val can be NULL if the string is empty
2683 result = [NSString string];
2686 val = CONVERT_TO_UTF8(val);
2688 result = [NSString stringWithUTF8String:(char*)val];
2690 CONVERT_TO_UTF8_FREE(val);
2693 } else if (tv->v_type == VAR_NUMBER) {
2694 // looks like sizeof(varnumber_T) is always <= sizeof(long)
2695 result = [NSNumber numberWithLong:(long)tv->vval.v_number];
2696 } else if (tv->v_type == VAR_LIST) {
2697 list_T * list = tv->vval.v_list;
2700 NSMutableArray * arr = result = [NSMutableArray array];
2703 for (curr = list->lv_first; curr != NULL; curr = curr->li_next) {
2704 newObj = vimToCocoa(&curr->li_tv, depth + 1);
2705 [arr addObject:newObj];
2708 } else if (tv->v_type == VAR_DICT) {
2709 NSMutableDictionary * dict = result = [NSMutableDictionary dictionary];
2711 if (tv->vval.v_dict != NULL) {
2712 hashtab_T * ht = &tv->vval.v_dict->dv_hashtab;
2713 int todo = ht->ht_used;
2717 for (hi = ht->ht_array; todo > 0; ++hi) {
2718 if (!HASHITEM_EMPTY(hi)) {
2721 di = dict_lookup(hi);
2722 newObj = vimToCocoa(&di->di_tv, depth + 1);
2724 char_u * keyval = hi->hi_key;
2726 keyval = CONVERT_TO_UTF8(keyval);
2728 NSString * key = [NSString stringWithUTF8String:(char*)keyval];
2730 CONVERT_TO_UTF8_FREE(keyval);
2732 [dict setObject:newObj forKey:key];
2736 } else { // only func refs should fall into this category?
2744 // This function is modeled after eval_client_expr_to_string found in main.c
2745 // Returns nil if there was an error evaluating the expression, and writes a
2746 // message to errorStr.
2747 // TODO Get the error that occurred while evaluating the expression in vim
2749 static id evalExprCocoa(NSString * expr, NSString ** errstr)
2752 char_u *s = (char_u*)[expr UTF8String];
2755 s = CONVERT_FROM_UTF8(s);
2758 int save_dbl = debug_break_level;
2759 int save_ro = redir_off;
2761 debug_break_level = -1;
2765 typval_T * tvres = eval_expr(s, NULL);
2767 debug_break_level = save_dbl;
2768 redir_off = save_ro;
2775 CONVERT_FROM_UTF8_FREE(s);
2780 gui_update_cursor(FALSE, FALSE);
2783 if (tvres == NULL) {
2785 *errstr = @"Expression evaluation failed.";
2788 id res = vimToCocoa(tvres, 1);
2793 *errstr = @"Conversion to cocoa values failed.";
2802 @implementation NSString (VimStrings)
2804 + (id)stringWithVimString:(char_u *)s
2806 // This method ensures a non-nil string is returned. If 's' cannot be
2807 // converted to a utf-8 string it is assumed to be latin-1. If conversion
2808 // still fails an empty NSString is returned.
2809 NSString *string = nil;
2812 s = CONVERT_TO_UTF8(s);
2814 string = [NSString stringWithUTF8String:(char*)s];
2816 // HACK! Apparently 's' is not a valid utf-8 string, maybe it is
2818 string = [NSString stringWithCString:(char*)s
2819 encoding:NSISOLatin1StringEncoding];
2822 CONVERT_TO_UTF8_FREE(s);
2826 return string != nil ? string : [NSString string];
2829 - (char_u *)vimStringSave
2831 char_u *s = (char_u*)[self UTF8String], *ret = NULL;
2834 s = CONVERT_FROM_UTF8(s);
2836 ret = vim_strsave(s);
2838 CONVERT_FROM_UTF8_FREE(s);
2844 @end // NSString (VimStrings)