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);
61 // Before exiting process, sleep for this many microseconds. This is to allow
62 // any distributed object messages in transit to be received by MacVim before
63 // the process dies (otherwise an error message is logged by Cocoa). Note that
64 // this delay is only necessary if an NSConnection to MacVim has been
66 static useconds_t MMExitProcessDelay = 300000;
69 vimmenu_T *menu_for_descriptor(NSArray *desc);
71 static id evalExprCocoa(NSString * expr, NSString ** errstr);
79 static NSString *MMSymlinkWarningString =
80 @"\n\n\tMost likely this is because you have symlinked directly to\n"
81 "\tthe Vim binary, which Cocoa does not allow. Please use an\n"
82 "\talias or the mvim shell script instead. If you have not used\n"
83 "\ta symlink, then your MacVim.app bundle is incomplete.\n\n";
87 @interface NSString (MMServerNameCompare)
88 - (NSComparisonResult)serverNameCompare:(NSString *)string;
94 @interface MMBackend (Private)
95 - (void)waitForDialogReturn;
96 - (void)insertVimStateMessage;
97 - (void)processInputQueue;
98 - (void)handleInputEvent:(int)msgid data:(NSData *)data;
99 + (NSDictionary *)specialKeys;
100 - (void)handleInsertText:(NSData *)data;
101 - (void)handleKeyDown:(NSString *)key modifiers:(int)mods;
102 - (void)queueMessage:(int)msgid data:(NSData *)data;
103 - (void)connectionDidDie:(NSNotification *)notification;
104 - (void)blinkTimerFired:(NSTimer *)timer;
105 - (void)focusChange:(BOOL)on;
106 - (void)handleToggleToolbar;
107 - (void)handleScrollbarEvent:(NSData *)data;
108 - (void)handleSetFont:(NSData *)data;
109 - (void)handleDropFiles:(NSData *)data;
110 - (void)handleDropString:(NSData *)data;
111 - (void)startOdbEditWithArguments:(NSDictionary *)args;
112 - (void)handleXcodeMod:(NSData *)data;
113 - (void)handleOpenWithArguments:(NSDictionary *)args;
114 - (BOOL)checkForModifiedBuffers;
115 - (void)addInput:(NSString *)input;
116 - (BOOL)unusedEditor;
121 @interface MMBackend (ClientServer)
122 - (NSString *)connectionNameFromServerName:(NSString *)name;
123 - (NSConnection *)connectionForServerName:(NSString *)name;
124 - (NSConnection *)connectionForServerPort:(int)port;
125 - (void)serverConnectionDidDie:(NSNotification *)notification;
126 - (void)addClient:(NSDistantObject *)client;
127 - (NSString *)alternateServerNameForName:(NSString *)name;
132 @implementation MMBackend
134 + (MMBackend *)sharedInstance
136 static MMBackend *singleton = nil;
137 return singleton ? singleton : (singleton = [MMBackend new]);
143 if (!self) return nil;
145 fontContainerRef = loadFonts();
147 outputQueue = [[NSMutableArray alloc] init];
148 inputQueue = [[NSMutableArray alloc] init];
149 drawData = [[NSMutableData alloc] initWithCapacity:1024];
150 connectionNameDict = [[NSMutableDictionary alloc] init];
151 clientProxyDict = [[NSMutableDictionary alloc] init];
152 serverReplyDict = [[NSMutableDictionary alloc] init];
154 NSBundle *mainBundle = [NSBundle mainBundle];
155 NSString *path = [mainBundle pathForResource:@"Colors" ofType:@"plist"];
157 colorDict = [[NSDictionary dictionaryWithContentsOfFile:path] retain];
159 path = [mainBundle pathForResource:@"SystemColors" ofType:@"plist"];
161 sysColorDict = [[NSDictionary dictionaryWithContentsOfFile:path]
164 path = [mainBundle pathForResource:@"Actions" ofType:@"plist"];
166 actionDict = [[NSDictionary dictionaryWithContentsOfFile:path] retain];
168 if (!(colorDict && sysColorDict && actionDict))
169 NSLog(@"ERROR: Failed to load dictionaries.%@",
170 MMSymlinkWarningString);
177 //NSLog(@"%@ %s", [self className], _cmd);
178 [[NSNotificationCenter defaultCenter] removeObserver:self];
180 [oldWideFont release]; oldWideFont = nil;
181 [blinkTimer release]; blinkTimer = nil;
182 [alternateServerName release]; alternateServerName = nil;
183 [serverReplyDict release]; serverReplyDict = nil;
184 [clientProxyDict release]; clientProxyDict = nil;
185 [connectionNameDict release]; connectionNameDict = nil;
186 [inputQueue release]; inputQueue = nil;
187 [outputQueue release]; outputQueue = nil;
188 [drawData release]; drawData = nil;
189 [frontendProxy release]; frontendProxy = nil;
190 [connection release]; connection = nil;
191 [actionDict release]; actionDict = nil;
192 [sysColorDict release]; sysColorDict = nil;
193 [colorDict release]; colorDict = nil;
198 - (void)setBackgroundColor:(int)color
200 backgroundColor = MM_COLOR_WITH_TRANSP(color,p_transp);
203 - (void)setForegroundColor:(int)color
205 foregroundColor = MM_COLOR(color);
208 - (void)setSpecialColor:(int)color
210 specialColor = MM_COLOR(color);
213 - (void)setDefaultColorsBackground:(int)bg foreground:(int)fg
215 defaultBackgroundColor = MM_COLOR_WITH_TRANSP(bg,p_transp);
216 defaultForegroundColor = MM_COLOR(fg);
218 NSMutableData *data = [NSMutableData data];
220 [data appendBytes:&defaultBackgroundColor length:sizeof(unsigned)];
221 [data appendBytes:&defaultForegroundColor length:sizeof(unsigned)];
223 [self queueMessage:SetDefaultColorsMsgID data:data];
226 - (NSConnection *)connection
229 // NOTE! If the name of the connection changes here it must also be
230 // updated in MMAppController.m.
231 NSString *name = [NSString stringWithFormat:@"%@-connection",
232 [[NSBundle mainBundle] bundlePath]];
234 connection = [NSConnection connectionWithRegisteredName:name host:nil];
238 // NOTE: 'connection' may be nil here.
242 - (NSDictionary *)actionDict
247 - (int)initialWindowLayout
249 return initialWindowLayout;
252 - (void)queueMessage:(int)msgid properties:(NSDictionary *)props
254 [self queueMessage:msgid data:[props dictionaryAsData]];
259 if (![self connection]) {
261 // This is a preloaded process and as such should not cause the
262 // MacVim to be opened. We probably got here as a result of the
263 // user quitting MacVim while the process was preloading, so exit
265 // (Don't use mch_exit() since it assumes the process has properly
270 NSBundle *mainBundle = [NSBundle mainBundle];
275 // Launch MacVim using Launch Services (NSWorkspace would be nicer, but
276 // the API to pass Apple Event parameters is broken on 10.4).
277 NSString *path = [mainBundle bundlePath];
278 status = FSPathMakeRef((const UInt8 *)[path UTF8String], &ref, NULL);
279 if (noErr == status) {
280 // Pass parameter to the 'Open' Apple Event that tells MacVim not
281 // to open an untitled window.
282 NSAppleEventDescriptor *desc =
283 [NSAppleEventDescriptor recordDescriptor];
284 [desc setParamDescriptor:
285 [NSAppleEventDescriptor descriptorWithBoolean:NO]
286 forKeyword:keyMMUntitledWindow];
288 LSLaunchFSRefSpec spec = { &ref, 0, NULL, [desc aeDesc],
289 kLSLaunchDefaults, NULL };
290 status = LSOpenFromRefSpec(&spec, NULL);
293 if (noErr != status) {
294 NSLog(@"ERROR: Failed to launch MacVim (path=%@).%@",
295 path, MMSymlinkWarningString);
299 // Launch MacVim using NSTask. For some reason the above code using
300 // Launch Services sometimes fails on LSOpenFromRefSpec() (when it
301 // fails, the dock icon starts bouncing and never stops). It seems
302 // like rebuilding the Launch Services database takes care of this
303 // problem, but the NSTask way seems more stable so stick with it.
305 // NOTE! Using NSTask to launch the GUI has the negative side-effect
306 // that the GUI won't be activated (or raised) so there is a hack in
307 // MMAppController which raises the app when a new window is opened.
308 NSMutableArray *args = [NSMutableArray arrayWithObjects:
309 [NSString stringWithFormat:@"-%@", MMNoWindowKey], @"yes", nil];
310 NSString *exeName = [[mainBundle infoDictionary]
311 objectForKey:@"CFBundleExecutable"];
312 NSString *path = [mainBundle pathForAuxiliaryExecutable:exeName];
314 NSLog(@"ERROR: Could not find MacVim executable in bundle.%@",
315 MMSymlinkWarningString);
319 [NSTask launchedTaskWithLaunchPath:path arguments:args];
322 // HACK! Poll the mach bootstrap server until it returns a valid
323 // connection to detect that MacVim has finished launching. Also set a
324 // time-out date so that we don't get stuck doing this forever.
325 NSDate *timeOutDate = [NSDate dateWithTimeIntervalSinceNow:10];
326 while (![self connection] &&
327 NSOrderedDescending == [timeOutDate compare:[NSDate date]])
328 [[NSRunLoop currentRunLoop]
329 runMode:NSDefaultRunLoopMode
330 beforeDate:[NSDate dateWithTimeIntervalSinceNow:1]];
332 // NOTE: [self connection] will set 'connection' as a side-effect.
334 NSLog(@"WARNING: Timed-out waiting for GUI to launch.");
341 [[NSNotificationCenter defaultCenter] addObserver:self
342 selector:@selector(connectionDidDie:)
343 name:NSConnectionDidDieNotification object:connection];
345 id proxy = [connection rootProxy];
346 [proxy setProtocolForProxy:@protocol(MMAppProtocol)];
348 int pid = [[NSProcessInfo processInfo] processIdentifier];
350 frontendProxy = [proxy connectBackend:self pid:pid];
352 [frontendProxy retain];
353 [frontendProxy setProtocolForProxy:@protocol(MMAppProtocol)];
357 @catch (NSException *e) {
358 NSLog(@"Exception caught when trying to connect backend: \"%@\"", e);
364 - (BOOL)openGUIWindow
366 [self queueMessage:OpenWindowMsgID data:nil];
372 int type = ClearAllDrawType;
374 // Any draw commands in queue are effectively obsolete since this clearAll
375 // will negate any effect they have, therefore we may as well clear the
377 [drawData setLength:0];
379 [drawData appendBytes:&type length:sizeof(int)];
382 - (void)clearBlockFromRow:(int)row1 column:(int)col1
383 toRow:(int)row2 column:(int)col2
385 int type = ClearBlockDrawType;
387 [drawData appendBytes:&type length:sizeof(int)];
389 [drawData appendBytes:&defaultBackgroundColor length:sizeof(unsigned)];
390 [drawData appendBytes:&row1 length:sizeof(int)];
391 [drawData appendBytes:&col1 length:sizeof(int)];
392 [drawData appendBytes:&row2 length:sizeof(int)];
393 [drawData appendBytes:&col2 length:sizeof(int)];
396 - (void)deleteLinesFromRow:(int)row count:(int)count
397 scrollBottom:(int)bottom left:(int)left right:(int)right
399 int type = DeleteLinesDrawType;
401 [drawData appendBytes:&type length:sizeof(int)];
403 [drawData appendBytes:&defaultBackgroundColor length:sizeof(unsigned)];
404 [drawData appendBytes:&row length:sizeof(int)];
405 [drawData appendBytes:&count length:sizeof(int)];
406 [drawData appendBytes:&bottom length:sizeof(int)];
407 [drawData appendBytes:&left length:sizeof(int)];
408 [drawData appendBytes:&right length:sizeof(int)];
411 - (void)drawString:(char*)s length:(int)len row:(int)row column:(int)col
412 cells:(int)cells flags:(int)flags
414 if (len <= 0 || cells <= 0) return;
416 int type = DrawStringDrawType;
418 [drawData appendBytes:&type length:sizeof(int)];
420 [drawData appendBytes:&backgroundColor length:sizeof(unsigned)];
421 [drawData appendBytes:&foregroundColor length:sizeof(unsigned)];
422 [drawData appendBytes:&specialColor length:sizeof(unsigned)];
423 [drawData appendBytes:&row length:sizeof(int)];
424 [drawData appendBytes:&col length:sizeof(int)];
425 [drawData appendBytes:&cells length:sizeof(int)];
426 [drawData appendBytes:&flags length:sizeof(int)];
427 [drawData appendBytes:&len length:sizeof(int)];
428 [drawData appendBytes:s length:len];
431 - (void)insertLinesFromRow:(int)row count:(int)count
432 scrollBottom:(int)bottom left:(int)left right:(int)right
434 int type = InsertLinesDrawType;
436 [drawData appendBytes:&type length:sizeof(int)];
438 [drawData appendBytes:&defaultBackgroundColor length:sizeof(unsigned)];
439 [drawData appendBytes:&row length:sizeof(int)];
440 [drawData appendBytes:&count length:sizeof(int)];
441 [drawData appendBytes:&bottom length:sizeof(int)];
442 [drawData appendBytes:&left length:sizeof(int)];
443 [drawData appendBytes:&right length:sizeof(int)];
446 - (void)drawCursorAtRow:(int)row column:(int)col shape:(int)shape
447 fraction:(int)percent color:(int)color
449 int type = DrawCursorDrawType;
450 unsigned uc = MM_COLOR(color);
452 [drawData appendBytes:&type length:sizeof(int)];
454 [drawData appendBytes:&uc length:sizeof(unsigned)];
455 [drawData appendBytes:&row length:sizeof(int)];
456 [drawData appendBytes:&col length:sizeof(int)];
457 [drawData appendBytes:&shape length:sizeof(int)];
458 [drawData appendBytes:&percent length:sizeof(int)];
461 - (void)drawInvertedRectAtRow:(int)row column:(int)col numRows:(int)nr
462 numColumns:(int)nc invert:(int)invert
464 int type = DrawInvertedRectDrawType;
465 [drawData appendBytes:&type length:sizeof(int)];
467 [drawData appendBytes:&row length:sizeof(int)];
468 [drawData appendBytes:&col length:sizeof(int)];
469 [drawData appendBytes:&nr length:sizeof(int)];
470 [drawData appendBytes:&nc length:sizeof(int)];
471 [drawData appendBytes:&invert length:sizeof(int)];
476 // Tend to the run loop, returning immediately if there are no events
478 [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
479 beforeDate:[NSDate distantPast]];
482 // Keyboard and mouse input is handled directly, other input is queued and
483 // processed here. This call may enter a blocking loop.
484 if ([inputQueue count] > 0)
485 [self processInputQueue];
489 - (void)flushQueue:(BOOL)force
491 // NOTE: This variable allows for better control over when the queue is
492 // flushed. It can be set to YES at the beginning of a sequence of calls
493 // that may potentially add items to the queue, and then restored back to
495 if (flushDisabled) return;
497 // NOTE! This method gets called a lot; if we were to flush every time it
498 // got called MacVim would feel unresponsive. So there is a time out which
499 // ensures that the queue isn't flushed too often.
500 if (!force && lastFlushDate
501 && -[lastFlushDate timeIntervalSinceNow] < MMFlushTimeoutInterval
502 && [drawData length] < MMFlushQueueLenHint)
505 if ([drawData length] > 0) {
506 // HACK! Detect changes to 'guifontwide'.
507 if (gui.wide_font != (GuiFont)oldWideFont) {
508 [oldWideFont release];
509 oldWideFont = [(NSFont*)gui.wide_font retain];
510 [self setWideFont:oldWideFont];
513 int type = SetCursorPosDrawType;
514 [drawData appendBytes:&type length:sizeof(type)];
515 [drawData appendBytes:&gui.row length:sizeof(gui.row)];
516 [drawData appendBytes:&gui.col length:sizeof(gui.col)];
518 [self queueMessage:BatchDrawMsgID data:[drawData copy]];
519 [drawData setLength:0];
522 if ([outputQueue count] > 0) {
523 [self insertVimStateMessage];
526 [frontendProxy processCommandQueue:outputQueue];
528 @catch (NSException *e) {
529 NSLog(@"Exception caught when processing command queue: \"%@\"", e);
532 [outputQueue removeAllObjects];
534 [lastFlushDate release];
535 lastFlushDate = [[NSDate date] retain];
539 - (BOOL)waitForInput:(int)milliseconds
541 //NSLog(@"|ENTER| %s%d", _cmd, milliseconds);
543 // Only start the run loop if the input queue is empty, otherwise process
544 // the input first so that the input on queue isn't delayed.
545 if ([inputQueue count]) {
548 NSDate *date = milliseconds > 0 ?
549 [NSDate dateWithTimeIntervalSinceNow:.001*milliseconds] :
550 [NSDate distantFuture];
552 [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
556 // I know of no way to figure out if the run loop exited because input was
557 // found or because of a time out, so I need to manually indicate when
558 // input was received in processInput:data: and then reset it every time
560 BOOL yn = inputReceived;
563 // Keyboard and mouse input is handled directly, other input is queued and
564 // processed here. This call may enter a blocking loop.
565 if ([inputQueue count] > 0)
566 [self processInputQueue];
568 //NSLog(@"|LEAVE| %s input=%d", _cmd, yn);
574 // NOTE: This is called if mch_exit() is called. Since we assume here that
575 // the process has started properly, be sure to use exit() instead of
576 // mch_exit() to prematurely terminate a process.
578 // To notify MacVim that this Vim process is exiting we could simply
579 // invalidate the connection and it would automatically receive a
580 // connectionDidDie: notification. However, this notification seems to
581 // take up to 300 ms to arrive which is quite a noticeable delay. Instead
582 // we immediately send a message to MacVim asking it to close the window
583 // belonging to this process, and then we invalidate the connection (in
584 // case the message got lost).
586 // Make sure no connectionDidDie: notification is received now that we are
588 [[NSNotificationCenter defaultCenter] removeObserver:self];
590 if ([connection isValid]) {
592 // Flush the entire queue in case a VimLeave autocommand added
593 // something to the queue.
594 [self queueMessage:CloseWindowMsgID data:nil];
595 [frontendProxy processCommandQueue:outputQueue];
597 @catch (NSException *e) {
598 NSLog(@"Exception caught when sending CloseWindowMsgID: \"%@\"", e);
601 [connection invalidate];
604 #ifdef MAC_CLIENTSERVER
605 // The default connection is used for the client/server code.
606 [[NSConnection defaultConnection] setRootObject:nil];
607 [[NSConnection defaultConnection] invalidate];
610 if (fontContainerRef) {
611 ATSFontDeactivate(fontContainerRef, NULL, kATSOptionFlagsDefault);
612 fontContainerRef = 0;
615 usleep(MMExitProcessDelay);
618 - (void)selectTab:(int)index
620 //NSLog(@"%s%d", _cmd, index);
623 NSData *data = [NSData dataWithBytes:&index length:sizeof(int)];
624 [self queueMessage:SelectTabMsgID data:data];
629 //NSLog(@"%s", _cmd);
631 NSMutableData *data = [NSMutableData data];
633 int idx = tabpage_index(curtab) - 1;
634 [data appendBytes:&idx length:sizeof(int)];
637 for (tp = first_tabpage; tp != NULL; tp = tp->tp_next) {
638 // This function puts the label of the tab in the global 'NameBuff'.
639 get_tabline_label(tp, FALSE);
640 char_u *s = NameBuff;
642 if (len <= 0) continue;
645 s = CONVERT_TO_UTF8(s);
648 // Count the number of windows in the tabpage.
649 //win_T *wp = tp->tp_firstwin;
651 //for (wincount = 0; wp != NULL; wp = wp->w_next, ++wincount);
653 //[data appendBytes:&wincount length:sizeof(int)];
654 [data appendBytes:&len length:sizeof(int)];
655 [data appendBytes:s length:len];
658 CONVERT_TO_UTF8_FREE(s);
662 [self queueMessage:UpdateTabBarMsgID data:data];
665 - (BOOL)tabBarVisible
667 return tabBarVisible;
670 - (void)showTabBar:(BOOL)enable
672 tabBarVisible = enable;
674 int msgid = enable ? ShowTabBarMsgID : HideTabBarMsgID;
675 [self queueMessage:msgid data:nil];
678 - (void)setRows:(int)rows columns:(int)cols
680 //NSLog(@"[VimTask] setRows:%d columns:%d", rows, cols);
682 int dim[] = { rows, cols };
683 NSData *data = [NSData dataWithBytes:&dim length:2*sizeof(int)];
685 [self queueMessage:SetTextDimensionsMsgID data:data];
688 - (void)setWindowTitle:(char *)title
690 NSMutableData *data = [NSMutableData data];
691 int len = strlen(title);
692 if (len <= 0) return;
694 [data appendBytes:&len length:sizeof(int)];
695 [data appendBytes:title length:len];
697 [self queueMessage:SetWindowTitleMsgID data:data];
700 - (void)setDocumentFilename:(char *)filename
702 NSMutableData *data = [NSMutableData data];
703 int len = filename ? strlen(filename) : 0;
705 [data appendBytes:&len length:sizeof(int)];
707 [data appendBytes:filename length:len];
709 [self queueMessage:SetDocumentFilenameMsgID data:data];
712 - (char *)browseForFileWithAttributes:(NSDictionary *)attr
717 [frontendProxy showSavePanelWithAttributes:attr];
719 [self waitForDialogReturn];
721 if (dialogReturn && [dialogReturn isKindOfClass:[NSString class]]) {
722 char_u *ret = (char_u*)[dialogReturn UTF8String];
724 ret = CONVERT_FROM_UTF8(ret);
726 s = vim_strsave(ret);
728 CONVERT_FROM_UTF8_FREE(ret);
732 [dialogReturn release]; dialogReturn = nil;
734 @catch (NSException *e) {
735 NSLog(@"Exception caught when showing save panel: \"%@\"", e);
741 - (oneway void)setDialogReturn:(in bycopy id)obj
743 // NOTE: This is called by
744 // - [MMVimController panelDidEnd:::], and
745 // - [MMVimController alertDidEnd:::],
746 // to indicate that a save/open panel or alert has finished.
748 // We want to distinguish between "no dialog return yet" and "dialog
749 // returned nothing". The former can be tested with dialogReturn == nil,
750 // the latter with dialogReturn == [NSNull null].
751 if (!obj) obj = [NSNull null];
753 if (obj != dialogReturn) {
754 [dialogReturn release];
755 dialogReturn = [obj retain];
759 - (int)showDialogWithAttributes:(NSDictionary *)attr textField:(char *)txtfield
764 [frontendProxy presentDialogWithAttributes:attr];
766 [self waitForDialogReturn];
768 if (dialogReturn && [dialogReturn isKindOfClass:[NSArray class]]
769 && [dialogReturn count]) {
770 retval = [[dialogReturn objectAtIndex:0] intValue];
771 if (txtfield && [dialogReturn count] > 1) {
772 NSString *retString = [dialogReturn objectAtIndex:1];
773 char_u *ret = (char_u*)[retString UTF8String];
775 ret = CONVERT_FROM_UTF8(ret);
777 vim_strncpy((char_u*)txtfield, ret, IOSIZE - 1);
779 CONVERT_FROM_UTF8_FREE(ret);
784 [dialogReturn release]; dialogReturn = nil;
786 @catch (NSException *e) {
787 NSLog(@"Exception caught while showing alert dialog: \"%@\"", e);
793 - (void)showToolbar:(int)enable flags:(int)flags
795 NSMutableData *data = [NSMutableData data];
797 [data appendBytes:&enable length:sizeof(int)];
798 [data appendBytes:&flags length:sizeof(int)];
800 [self queueMessage:ShowToolbarMsgID data:data];
803 - (void)createScrollbarWithIdentifier:(long)ident type:(int)type
805 NSMutableData *data = [NSMutableData data];
807 [data appendBytes:&ident length:sizeof(long)];
808 [data appendBytes:&type length:sizeof(int)];
810 [self queueMessage:CreateScrollbarMsgID data:data];
813 - (void)destroyScrollbarWithIdentifier:(long)ident
815 NSMutableData *data = [NSMutableData data];
816 [data appendBytes:&ident length:sizeof(long)];
818 [self queueMessage:DestroyScrollbarMsgID data:data];
821 - (void)showScrollbarWithIdentifier:(long)ident state:(int)visible
823 NSMutableData *data = [NSMutableData data];
825 [data appendBytes:&ident length:sizeof(long)];
826 [data appendBytes:&visible length:sizeof(int)];
828 [self queueMessage:ShowScrollbarMsgID data:data];
831 - (void)setScrollbarPosition:(int)pos length:(int)len identifier:(long)ident
833 NSMutableData *data = [NSMutableData data];
835 [data appendBytes:&ident length:sizeof(long)];
836 [data appendBytes:&pos length:sizeof(int)];
837 [data appendBytes:&len length:sizeof(int)];
839 [self queueMessage:SetScrollbarPositionMsgID data:data];
842 - (void)setScrollbarThumbValue:(long)val size:(long)size max:(long)max
843 identifier:(long)ident
845 float fval = max-size+1 > 0 ? (float)val/(max-size+1) : 0;
846 float prop = (float)size/(max+1);
847 if (fval < 0) fval = 0;
848 else if (fval > 1.0f) fval = 1.0f;
849 if (prop < 0) prop = 0;
850 else if (prop > 1.0f) prop = 1.0f;
852 NSMutableData *data = [NSMutableData data];
854 [data appendBytes:&ident length:sizeof(long)];
855 [data appendBytes:&fval length:sizeof(float)];
856 [data appendBytes:&prop length:sizeof(float)];
858 [self queueMessage:SetScrollbarThumbMsgID data:data];
861 - (void)setFont:(NSFont *)font
863 NSString *fontName = [font displayName];
864 float size = [font pointSize];
865 int len = [fontName lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
867 NSMutableData *data = [NSMutableData data];
869 [data appendBytes:&size length:sizeof(float)];
870 [data appendBytes:&len length:sizeof(int)];
871 [data appendBytes:[fontName UTF8String] length:len];
873 [self queueMessage:SetFontMsgID data:data];
877 - (void)setWideFont:(NSFont *)font
879 NSString *fontName = [font displayName];
880 float size = [font pointSize];
881 int len = [fontName lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
882 NSMutableData *data = [NSMutableData data];
884 [data appendBytes:&size length:sizeof(float)];
885 [data appendBytes:&len length:sizeof(int)];
887 [data appendBytes:[fontName UTF8String] length:len];
889 [self queueMessage:SetWideFontMsgID data:data];
892 - (void)executeActionWithName:(NSString *)name
894 int len = [name lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
897 NSMutableData *data = [NSMutableData data];
899 [data appendBytes:&len length:sizeof(int)];
900 [data appendBytes:[name UTF8String] length:len];
902 [self queueMessage:ExecuteActionMsgID data:data];
906 - (void)setMouseShape:(int)shape
908 NSMutableData *data = [NSMutableData data];
909 [data appendBytes:&shape length:sizeof(int)];
910 [self queueMessage:SetMouseShapeMsgID data:data];
913 - (void)setBlinkWait:(int)wait on:(int)on off:(int)off
915 // Vim specifies times in milliseconds, whereas Cocoa wants them in
917 blinkWaitInterval = .001f*wait;
918 blinkOnInterval = .001f*on;
919 blinkOffInterval = .001f*off;
925 [blinkTimer invalidate];
926 [blinkTimer release];
930 if (blinkWaitInterval > 0 && blinkOnInterval > 0 && blinkOffInterval > 0
932 blinkState = MMBlinkStateOn;
934 [[NSTimer scheduledTimerWithTimeInterval:blinkWaitInterval
936 selector:@selector(blinkTimerFired:)
937 userInfo:nil repeats:NO] retain];
938 gui_update_cursor(TRUE, FALSE);
939 [self flushQueue:YES];
945 if (MMBlinkStateOff == blinkState) {
946 gui_update_cursor(TRUE, FALSE);
947 [self flushQueue:YES];
950 blinkState = MMBlinkStateNone;
953 - (void)adjustLinespace:(int)linespace
955 NSMutableData *data = [NSMutableData data];
956 [data appendBytes:&linespace length:sizeof(int)];
957 [self queueMessage:AdjustLinespaceMsgID data:data];
962 [self queueMessage:ActivateMsgID data:nil];
965 - (void)setPreEditRow:(int)row column:(int)col
967 NSMutableData *data = [NSMutableData data];
968 [data appendBytes:&row length:sizeof(int)];
969 [data appendBytes:&col length:sizeof(int)];
970 [self queueMessage:SetPreEditPositionMsgID data:data];
973 - (int)lookupColorWithKey:(NSString *)key
975 if (!(key && [key length] > 0))
978 NSString *stripKey = [[[[key lowercaseString]
979 stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]
980 componentsSeparatedByString:@" "]
981 componentsJoinedByString:@""];
983 if (stripKey && [stripKey length] > 0) {
984 // First of all try to lookup key in the color dictionary; note that
985 // all keys in this dictionary are lowercase with no whitespace.
986 id obj = [colorDict objectForKey:stripKey];
987 if (obj) return [obj intValue];
989 // The key was not in the dictionary; is it perhaps of the form
991 if ([stripKey length] > 1 && [stripKey characterAtIndex:0] == '#') {
992 NSScanner *scanner = [NSScanner scannerWithString:stripKey];
993 [scanner setScanLocation:1];
995 if ([scanner scanHexInt:&hex]) {
1000 // As a last resort, check if it is one of the system defined colors.
1001 // The keys in this dictionary are also lowercase with no whitespace.
1002 obj = [sysColorDict objectForKey:stripKey];
1004 NSColor *col = [NSColor performSelector:NSSelectorFromString(obj)];
1007 col = [col colorUsingColorSpaceName:NSCalibratedRGBColorSpace];
1008 [col getRed:&r green:&g blue:&b alpha:&a];
1009 return (((int)(r*255+.5f) & 0xff) << 16)
1010 + (((int)(g*255+.5f) & 0xff) << 8)
1011 + ((int)(b*255+.5f) & 0xff);
1016 //NSLog(@"WARNING: No color with key %@ found.", stripKey);
1020 - (BOOL)hasSpecialKeyWithValue:(NSString *)value
1022 NSEnumerator *e = [[MMBackend specialKeys] objectEnumerator];
1025 while ((obj = [e nextObject])) {
1026 if ([value isEqual:obj])
1033 - (void)enterFullscreen:(int)fuoptions background:(int)bg
1035 NSMutableData *data = [NSMutableData data];
1036 [data appendBytes:&fuoptions length:sizeof(int)];
1038 [data appendBytes:&bg length:sizeof(int)];
1039 [self queueMessage:EnterFullscreenMsgID data:data];
1042 - (void)leaveFullscreen
1044 [self queueMessage:LeaveFullscreenMsgID data:nil];
1047 - (void)setAntialias:(BOOL)antialias
1049 int msgid = antialias ? EnableAntialiasMsgID : DisableAntialiasMsgID;
1051 [self queueMessage:msgid data:nil];
1054 - (void)updateModifiedFlag
1056 // Notify MacVim if _any_ buffer has changed from unmodified to modified or
1058 int msgid = [self checkForModifiedBuffers]
1059 ? BuffersModifiedMsgID : BuffersNotModifiedMsgID;
1061 [self queueMessage:msgid data:nil];
1064 - (oneway void)processInput:(int)msgid data:(in bycopy NSData *)data
1066 // NOTE: This method might get called whenever the run loop is tended to.
1067 // Normal keyboard and mouse input is added to input buffers, so there is
1068 // no risk in handling these events directly (they return immediately, and
1069 // do not call any other Vim functions). However, other events such
1070 // as 'VimShouldCloseMsgID' may enter blocking loops that wait for key
1071 // events which would cause this method to be called recursively. This
1072 // in turn leads to various difficulties that we do not want to have to
1073 // deal with. To avoid recursive calls here we add all events except
1074 // keyboard and mouse events to an input queue which is processed whenever
1075 // gui_mch_update() is called (see processInputQueue).
1077 //NSLog(@"%s%s", _cmd, MessageStrings[msgid]);
1079 // Don't flush too soon after receiving input or update speed will suffer.
1080 [lastFlushDate release];
1081 lastFlushDate = [[NSDate date] retain];
1083 // Handle keyboard and mouse input now. All other events are queued.
1084 if (InsertTextMsgID == msgid) {
1085 [self handleInsertText:data];
1086 } else if (KeyDownMsgID == msgid || CmdKeyMsgID == msgid) {
1088 const void *bytes = [data bytes];
1089 int mods = *((int*)bytes); bytes += sizeof(int);
1090 int len = *((int*)bytes); bytes += sizeof(int);
1091 NSString *key = [[NSString alloc] initWithBytes:bytes length:len
1092 encoding:NSUTF8StringEncoding];
1093 mods = eventModifierFlagsToVimModMask(mods);
1095 [self handleKeyDown:key modifiers:mods];
1098 } else if (ScrollWheelMsgID == msgid) {
1100 const void *bytes = [data bytes];
1102 int row = *((int*)bytes); bytes += sizeof(int);
1103 int col = *((int*)bytes); bytes += sizeof(int);
1104 int flags = *((int*)bytes); bytes += sizeof(int);
1105 float dy = *((float*)bytes); bytes += sizeof(float);
1107 int button = MOUSE_5;
1108 if (dy > 0) button = MOUSE_4;
1110 flags = eventModifierFlagsToVimMouseModMask(flags);
1112 int numLines = (int)round(dy);
1113 if (numLines < 0) numLines = -numLines;
1114 if (numLines == 0) numLines = 1;
1116 #ifdef FEAT_GUI_SCROLL_WHEEL_FORCE
1117 gui.scroll_wheel_force = numLines;
1120 gui_send_mouse_event(button, col, row, NO, flags);
1121 } else if (MouseDownMsgID == msgid) {
1123 const void *bytes = [data bytes];
1125 int row = *((int*)bytes); bytes += sizeof(int);
1126 int col = *((int*)bytes); bytes += sizeof(int);
1127 int button = *((int*)bytes); bytes += sizeof(int);
1128 int flags = *((int*)bytes); bytes += sizeof(int);
1129 int count = *((int*)bytes); bytes += sizeof(int);
1131 button = eventButtonNumberToVimMouseButton(button);
1133 flags = eventModifierFlagsToVimMouseModMask(flags);
1134 gui_send_mouse_event(button, col, row, count>1, flags);
1136 } else if (MouseUpMsgID == msgid) {
1138 const void *bytes = [data bytes];
1140 int row = *((int*)bytes); bytes += sizeof(int);
1141 int col = *((int*)bytes); bytes += sizeof(int);
1142 int flags = *((int*)bytes); bytes += sizeof(int);
1144 flags = eventModifierFlagsToVimMouseModMask(flags);
1146 gui_send_mouse_event(MOUSE_RELEASE, col, row, NO, flags);
1147 } else if (MouseDraggedMsgID == msgid) {
1149 const void *bytes = [data bytes];
1151 int row = *((int*)bytes); bytes += sizeof(int);
1152 int col = *((int*)bytes); bytes += sizeof(int);
1153 int flags = *((int*)bytes); bytes += sizeof(int);
1155 flags = eventModifierFlagsToVimMouseModMask(flags);
1157 gui_send_mouse_event(MOUSE_DRAG, col, row, NO, flags);
1158 } else if (MouseMovedMsgID == msgid) {
1159 const void *bytes = [data bytes];
1160 int row = *((int*)bytes); bytes += sizeof(int);
1161 int col = *((int*)bytes); bytes += sizeof(int);
1163 gui_mouse_moved(col, row);
1164 } else if (AddInputMsgID == msgid) {
1165 NSString *string = [[NSString alloc] initWithData:data
1166 encoding:NSUTF8StringEncoding];
1168 [self addInput:string];
1171 } else if (TerminateNowMsgID == msgid) {
1172 isTerminating = YES;
1174 // Not keyboard or mouse event, queue it and handle later.
1175 //NSLog(@"Add event %s to input event queue", MessageStrings[msgid]);
1176 [inputQueue addObject:[NSNumber numberWithInt:msgid]];
1177 [inputQueue addObject:(data ? (id)data : (id)[NSNull null])];
1180 // See waitForInput: for an explanation of this flag.
1181 inputReceived = YES;
1184 - (oneway void)processInputAndData:(in bycopy NSArray *)messages
1186 // TODO: Get rid of this method?
1187 //NSLog(@"%s%@", _cmd, messages);
1189 unsigned i, count = [messages count];
1190 for (i = 0; i < count; i += 2) {
1191 int msgid = [[messages objectAtIndex:i] intValue];
1192 id data = [messages objectAtIndex:i+1];
1193 if ([data isEqual:[NSNull null]])
1196 [self processInput:msgid data:data];
1200 - (id)evaluateExpressionCocoa:(in bycopy NSString *)expr
1201 errorString:(out bycopy NSString **)errstr
1203 return evalExprCocoa(expr, errstr);
1207 - (NSString *)evaluateExpression:(in bycopy NSString *)expr
1209 NSString *eval = nil;
1210 char_u *s = (char_u*)[expr UTF8String];
1213 s = CONVERT_FROM_UTF8(s);
1216 char_u *res = eval_client_expr_to_string(s);
1219 CONVERT_FROM_UTF8_FREE(s);
1225 s = CONVERT_TO_UTF8(s);
1227 eval = [NSString stringWithUTF8String:(char*)s];
1229 CONVERT_TO_UTF8_FREE(s);
1237 - (BOOL)starRegisterToPasteboard:(byref NSPasteboard *)pboard
1239 // TODO: This method should share code with clip_mch_request_selection().
1241 if (VIsual_active && (State & NORMAL) && clip_star.available) {
1242 // If there is no pasteboard, return YES to indicate that there is text
1247 clip_copy_selection();
1249 // Get the text to put on the pasteboard.
1250 long_u llen = 0; char_u *str = 0;
1251 int type = clip_convert_selection(&str, &llen, &clip_star);
1255 // TODO: Avoid overflow.
1256 int len = (int)llen;
1258 if (output_conv.vc_type != CONV_NONE) {
1259 char_u *conv_str = string_convert(&output_conv, str, &len);
1267 NSString *string = [[NSString alloc]
1268 initWithBytes:str length:len encoding:NSUTF8StringEncoding];
1270 NSArray *types = [NSArray arrayWithObject:NSStringPboardType];
1271 [pboard declareTypes:types owner:nil];
1272 BOOL ok = [pboard setString:string forType:NSStringPboardType];
1283 - (oneway void)addReply:(in bycopy NSString *)reply
1284 server:(in byref id <MMVimServerProtocol>)server
1286 //NSLog(@"addReply:%@ server:%@", reply, (id)server);
1288 // Replies might come at any time and in any order so we keep them in an
1289 // array inside a dictionary with the send port used as key.
1291 NSConnection *conn = [(NSDistantObject*)server connectionForProxy];
1292 // HACK! Assume connection uses mach ports.
1293 int port = [(NSMachPort*)[conn sendPort] machPort];
1294 NSNumber *key = [NSNumber numberWithInt:port];
1296 NSMutableArray *replies = [serverReplyDict objectForKey:key];
1298 replies = [NSMutableArray array];
1299 [serverReplyDict setObject:replies forKey:key];
1302 [replies addObject:reply];
1305 - (void)addInput:(in bycopy NSString *)input
1306 client:(in byref id <MMVimClientProtocol>)client
1308 //NSLog(@"addInput:%@ client:%@", input, (id)client);
1310 [self addInput:input];
1311 [self addClient:(id)client];
1313 inputReceived = YES;
1316 - (NSString *)evaluateExpression:(in bycopy NSString *)expr
1317 client:(in byref id <MMVimClientProtocol>)client
1319 [self addClient:(id)client];
1320 return [self evaluateExpression:expr];
1323 - (void)registerServerWithName:(NSString *)name
1325 NSString *svrName = name;
1326 NSConnection *svrConn = [NSConnection defaultConnection];
1329 for (i = 0; i < MMServerMax; ++i) {
1330 NSString *connName = [self connectionNameFromServerName:svrName];
1332 if ([svrConn registerName:connName]) {
1333 //NSLog(@"Registered server with name: %@", svrName);
1335 // TODO: Set request/reply time-outs to something else?
1337 // Don't wait for requests (time-out means that the message is
1339 [svrConn setRequestTimeout:0];
1340 //[svrConn setReplyTimeout:MMReplyTimeout];
1341 [svrConn setRootObject:self];
1343 char_u *s = (char_u*)[svrName UTF8String];
1345 s = CONVERT_FROM_UTF8(s);
1347 // NOTE: 'serverName' is a global variable
1348 serverName = vim_strsave(s);
1350 CONVERT_FROM_UTF8_FREE(s);
1353 set_vim_var_string(VV_SEND_SERVER, serverName, -1);
1356 need_maketitle = TRUE;
1358 [self queueMessage:SetServerNameMsgID data:
1359 [svrName dataUsingEncoding:NSUTF8StringEncoding]];
1363 svrName = [NSString stringWithFormat:@"%@%d", name, i+1];
1367 - (BOOL)sendToServer:(NSString *)name string:(NSString *)string
1368 reply:(char_u **)reply port:(int *)port expression:(BOOL)expr
1371 // NOTE: If 'name' equals 'serverName' then the request is local (client
1372 // and server are the same). This case is not handled separately, so a
1373 // connection will be set up anyway (this simplifies the code).
1375 NSConnection *conn = [self connectionForServerName:name];
1378 char_u *s = (char_u*)[name UTF8String];
1380 s = CONVERT_FROM_UTF8(s);
1382 EMSG2(_(e_noserver), s);
1384 CONVERT_FROM_UTF8_FREE(s);
1391 // HACK! Assume connection uses mach ports.
1392 *port = [(NSMachPort*)[conn sendPort] machPort];
1395 id proxy = [conn rootProxy];
1396 [proxy setProtocolForProxy:@protocol(MMVimServerProtocol)];
1400 NSString *eval = [proxy evaluateExpression:string client:self];
1403 char_u *r = (char_u*)[eval UTF8String];
1405 r = CONVERT_FROM_UTF8(r);
1407 *reply = vim_strsave(r);
1409 CONVERT_FROM_UTF8_FREE(r);
1412 *reply = vim_strsave((char_u*)_(e_invexprmsg));
1419 [proxy addInput:string client:self];
1422 @catch (NSException *e) {
1423 NSLog(@"WARNING: Caught exception in %s: \"%@\"", _cmd, e);
1430 - (NSArray *)serverList
1432 NSArray *list = nil;
1434 if ([self connection]) {
1435 id proxy = [connection rootProxy];
1436 [proxy setProtocolForProxy:@protocol(MMAppProtocol)];
1439 list = [proxy serverList];
1441 @catch (NSException *e) {
1442 NSLog(@"Exception caught when listing servers: \"%@\"", e);
1445 EMSG(_("E???: No connection to MacVim, server listing not possible."));
1451 - (NSString *)peekForReplyOnPort:(int)port
1453 //NSLog(@"%s%d", _cmd, port);
1455 NSNumber *key = [NSNumber numberWithInt:port];
1456 NSMutableArray *replies = [serverReplyDict objectForKey:key];
1457 if (replies && [replies count]) {
1458 //NSLog(@" %d replies, topmost is: %@", [replies count],
1459 // [replies objectAtIndex:0]);
1460 return [replies objectAtIndex:0];
1463 //NSLog(@" No replies");
1467 - (NSString *)waitForReplyOnPort:(int)port
1469 //NSLog(@"%s%d", _cmd, port);
1471 NSConnection *conn = [self connectionForServerPort:port];
1475 NSNumber *key = [NSNumber numberWithInt:port];
1476 NSMutableArray *replies = nil;
1477 NSString *reply = nil;
1479 // Wait for reply as long as the connection to the server is valid (unless
1480 // user interrupts wait with Ctrl-C).
1481 while (!got_int && [conn isValid] &&
1482 !(replies = [serverReplyDict objectForKey:key])) {
1483 [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
1484 beforeDate:[NSDate distantFuture]];
1488 if ([replies count] > 0) {
1489 reply = [[replies objectAtIndex:0] retain];
1490 //NSLog(@" Got reply: %@", reply);
1491 [replies removeObjectAtIndex:0];
1492 [reply autorelease];
1495 if ([replies count] == 0)
1496 [serverReplyDict removeObjectForKey:key];
1502 - (BOOL)sendReply:(NSString *)reply toPort:(int)port
1504 id client = [clientProxyDict objectForKey:[NSNumber numberWithInt:port]];
1507 //NSLog(@"sendReply:%@ toPort:%d", reply, port);
1508 [client addReply:reply server:self];
1511 @catch (NSException *e) {
1512 NSLog(@"WARNING: Exception caught in %s: \"%@\"", _cmd, e);
1515 EMSG2(_("E???: server2client failed; no client with id 0x%x"), port);
1526 - (void)setWaitForAck:(BOOL)yn
1531 - (void)waitForConnectionAcknowledgement
1533 if (!waitForAck) return;
1535 while (waitForAck && !got_int && [connection isValid] && !isTerminating) {
1536 [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
1537 beforeDate:[NSDate distantFuture]];
1538 //NSLog(@" waitForAck=%d got_int=%d isTerminating=%d isValid=%d",
1539 // waitForAck, got_int, isTerminating, [connection isValid]);
1543 // Never received a connection acknowledgement, so die.
1544 [[NSNotificationCenter defaultCenter] removeObserver:self];
1545 [frontendProxy release]; frontendProxy = nil;
1547 // NOTE: We intentionally do not call mch_exit() since this in turn
1548 // will lead to -[MMBackend exit] getting called which we want to
1550 usleep(MMExitProcessDelay);
1554 [self processInputQueue];
1557 - (oneway void)acknowledgeConnection
1559 //NSLog(@"%s", _cmd);
1567 @implementation MMBackend (Private)
1569 - (void)waitForDialogReturn
1571 // Keep processing the run loop until a dialog returns. To avoid getting
1572 // stuck in an endless loop (could happen if the setDialogReturn: message
1573 // was lost) we also do some paranoia checks.
1575 // Note that in Cocoa the user can still resize windows and select menu
1576 // items while a sheet is being displayed, so we can't just wait for the
1577 // first message to arrive and assume that is the setDialogReturn: call.
1579 while (nil == dialogReturn && !got_int && [connection isValid]
1581 [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
1582 beforeDate:[NSDate distantFuture]];
1584 // Search for any resize messages on the input queue. All other messages
1585 // on the input queue are dropped. The reason why we single out resize
1586 // messages is because the user may have resized the window while a sheet
1588 int i, count = [inputQueue count];
1590 id textDimData = nil;
1592 for (i = count-2; i >= 0; i -= 2) {
1593 int msgid = [[inputQueue objectAtIndex:i] intValue];
1594 if (SetTextDimensionsMsgID == msgid) {
1595 textDimData = [[inputQueue objectAtIndex:i+1] retain];
1601 [inputQueue removeAllObjects];
1604 [inputQueue addObject:
1605 [NSNumber numberWithInt:SetTextDimensionsMsgID]];
1606 [inputQueue addObject:textDimData];
1607 [textDimData release];
1612 - (void)insertVimStateMessage
1614 // NOTE: This is the place to add Vim state that needs to be accessed from
1615 // MacVim. Do not add state that could potentially require lots of memory
1616 // since this message gets sent each time the output queue is forcibly
1617 // flushed (e.g. storing the currently selected text would be a bad idea).
1618 // We take this approach of "pushing" the state to MacVim to avoid having
1619 // to make synchronous calls from MacVim to Vim in order to get state.
1621 NSDictionary *vimState = [NSDictionary dictionaryWithObjectsAndKeys:
1622 [[NSFileManager defaultManager] currentDirectoryPath], @"pwd",
1623 [NSNumber numberWithInt:p_mh], @"p_mh",
1624 [NSNumber numberWithBool:[self unusedEditor]], @"unusedEditor",
1627 // Put the state before all other messages.
1628 int msgid = SetVimStateMsgID;
1629 [outputQueue insertObject:[vimState dictionaryAsData] atIndex:0];
1630 [outputQueue insertObject:[NSData dataWithBytes:&msgid length:sizeof(int)]
1634 - (void)processInputQueue
1636 if ([inputQueue count] == 0) return;
1638 // NOTE: One of the input events may cause this method to be called
1639 // recursively, so copy the input queue to a local variable and clear it
1640 // before starting to process input events (otherwise we could get stuck in
1641 // an endless loop).
1642 NSArray *q = [inputQueue copy];
1643 unsigned i, count = [q count];
1645 [inputQueue removeAllObjects];
1647 for (i = 0; i < count-1; i += 2) {
1648 int msgid = [[q objectAtIndex:i] intValue];
1649 id data = [q objectAtIndex:i+1];
1650 if ([data isEqual:[NSNull null]])
1653 //NSLog(@"(%d) %s:%s", i, _cmd, MessageStrings[msgid]);
1654 [self handleInputEvent:msgid data:data];
1658 //NSLog(@"Clear input event queue");
1661 - (void)handleInputEvent:(int)msgid data:(NSData *)data
1663 //NSLog(@"%s%s", _cmd, MessageStrings[msgid]);
1665 if (SelectTabMsgID == msgid) {
1667 const void *bytes = [data bytes];
1668 int idx = *((int*)bytes) + 1;
1669 //NSLog(@"Selecting tab %d", idx);
1670 send_tabline_event(idx);
1671 } else if (CloseTabMsgID == msgid) {
1673 const void *bytes = [data bytes];
1674 int idx = *((int*)bytes) + 1;
1675 //NSLog(@"Closing tab %d", idx);
1676 send_tabline_menu_event(idx, TABLINE_MENU_CLOSE);
1677 } else if (AddNewTabMsgID == msgid) {
1678 //NSLog(@"Adding new tab");
1679 send_tabline_menu_event(0, TABLINE_MENU_NEW);
1680 } else if (DraggedTabMsgID == msgid) {
1682 const void *bytes = [data bytes];
1683 // NOTE! The destination index is 0 based, so do not add 1 to make it 1
1685 int idx = *((int*)bytes);
1688 } else if (SetTextDimensionsMsgID == msgid || LiveResizeMsgID == msgid
1689 || SetTextRowsMsgID == msgid || SetTextColumnsMsgID == msgid) {
1691 const void *bytes = [data bytes];
1693 if (SetTextColumnsMsgID != msgid) {
1694 rows = *((int*)bytes); bytes += sizeof(int);
1697 if (SetTextRowsMsgID != msgid) {
1698 cols = *((int*)bytes); bytes += sizeof(int);
1702 if (SetTextRowsMsgID == msgid || SetTextColumnsMsgID == msgid) {
1703 int dim[2] = { rows, cols };
1704 d = [NSData dataWithBytes:dim length:2*sizeof(int)];
1705 msgid = SetTextDimensionsMsgID;
1708 // NOTE! Vim doesn't call gui_mch_set_shellsize() after
1709 // gui_resize_shell(), so we have to manually set the rows and columns
1710 // here. (MacVim doesn't change the rows and columns to avoid
1711 // inconsistent states between Vim and MacVim.)
1712 [self queueMessage:msgid data:d];
1714 //NSLog(@"[VimTask] Resizing shell to %dx%d.", cols, rows);
1715 gui_resize_shell(cols, rows);
1716 } else if (ExecuteMenuMsgID == msgid) {
1717 NSDictionary *attrs = [NSDictionary dictionaryWithData:data];
1719 NSArray *desc = [attrs objectForKey:@"descriptor"];
1720 vimmenu_T *menu = menu_for_descriptor(desc);
1724 } else if (ToggleToolbarMsgID == msgid) {
1725 [self handleToggleToolbar];
1726 } else if (ScrollbarEventMsgID == msgid) {
1727 [self handleScrollbarEvent:data];
1728 } else if (SetFontMsgID == msgid) {
1729 [self handleSetFont:data];
1730 } else if (VimShouldCloseMsgID == msgid) {
1732 } else if (DropFilesMsgID == msgid) {
1733 [self handleDropFiles:data];
1734 } else if (DropStringMsgID == msgid) {
1735 [self handleDropString:data];
1736 } else if (GotFocusMsgID == msgid) {
1738 [self focusChange:YES];
1739 } else if (LostFocusMsgID == msgid) {
1741 [self focusChange:NO];
1742 } else if (SetMouseShapeMsgID == msgid) {
1743 const void *bytes = [data bytes];
1744 int shape = *((int*)bytes); bytes += sizeof(int);
1745 update_mouseshape(shape);
1746 } else if (XcodeModMsgID == msgid) {
1747 [self handleXcodeMod:data];
1748 } else if (OpenWithArgumentsMsgID == msgid) {
1749 [self handleOpenWithArguments:[NSDictionary dictionaryWithData:data]];
1751 NSLog(@"WARNING: Unknown message received (msgid=%d)", msgid);
1755 + (NSDictionary *)specialKeys
1757 static NSDictionary *specialKeys = nil;
1760 NSBundle *mainBundle = [NSBundle mainBundle];
1761 NSString *path = [mainBundle pathForResource:@"SpecialKeys"
1763 specialKeys = [[NSDictionary alloc] initWithContentsOfFile:path];
1769 - (void)handleInsertText:(NSData *)data
1773 NSString *key = [[NSString alloc] initWithData:data
1774 encoding:NSUTF8StringEncoding];
1775 char_u *str = (char_u*)[key UTF8String];
1776 int i, len = [key lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
1779 char_u *conv_str = NULL;
1780 if (input_conv.vc_type != CONV_NONE) {
1781 conv_str = string_convert(&input_conv, str, &len);
1787 if (len == 1 && ((str[0] == Ctrl_C && ctrl_c_interrupts)
1788 || (str[0] == intr_char && intr_char != Ctrl_C))) {
1793 for (i = 0; i < len; ++i) {
1794 add_to_input_buf(str+i, 1);
1795 if (CSI == str[i]) {
1796 // NOTE: If the converted string contains the byte CSI, then it
1797 // must be followed by the bytes KS_EXTRA, KE_CSI or things
1799 static char_u extra[2] = { KS_EXTRA, KE_CSI };
1800 add_to_input_buf(extra, 2);
1811 - (void)handleKeyDown:(NSString *)key modifiers:(int)mods
1815 char_u *chars = (char_u*)[key UTF8String];
1817 char_u *conv_str = NULL;
1819 int length = [key lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
1821 // Special keys (arrow keys, function keys, etc.) are stored in a plist so
1822 // that new keys can easily be added.
1823 NSString *specialString = [[MMBackend specialKeys]
1825 if (specialString && [specialString length] > 1) {
1826 //NSLog(@"special key: %@", specialString);
1827 int ikey = TO_SPECIAL([specialString characterAtIndex:0],
1828 [specialString characterAtIndex:1]);
1830 ikey = simplify_key(ikey, &mods);
1835 special[1] = K_SECOND(ikey);
1836 special[2] = K_THIRD(ikey);
1840 } else if (1 == length && TAB == chars[0]) {
1841 // Tab is a trouble child:
1842 // - <Tab> is added to the input buffer as is
1843 // - <S-Tab> is translated to, {CSI,'k','B'} (i.e. 'Back-tab')
1844 // - <M-Tab> should be 0x80|TAB but this is not valid utf-8 so it needs
1845 // to be converted to utf-8
1846 // - <S-M-Tab> is translated to <S-Tab> with ALT modifier
1847 // - <C-Tab> is reserved by Mac OS X
1848 // - <D-Tab> is reserved by Mac OS X
1853 if (mods & MOD_MASK_SHIFT) {
1854 mods &= ~MOD_MASK_SHIFT;
1856 special[1] = K_SECOND(K_S_TAB);
1857 special[2] = K_THIRD(K_S_TAB);
1859 } else if (mods & MOD_MASK_ALT) {
1860 int mtab = 0x80 | TAB;
1864 special[0] = (mtab >> 6) + 0xc0;
1865 special[1] = mtab & 0xbf;
1873 mods &= ~MOD_MASK_ALT;
1875 } else if (length > 0) {
1876 unichar c = [key characterAtIndex:0];
1878 //NSLog(@"non-special: %@ (hex=%x, mods=%d)", key,
1879 // [key characterAtIndex:0], mods);
1881 if (length == 1 && ((c == Ctrl_C && ctrl_c_interrupts)
1882 || (c == intr_char && intr_char != Ctrl_C))) {
1887 // HACK! In most circumstances the Ctrl and Shift modifiers should be
1888 // cleared since they are already added to the key by the AppKit.
1889 // Unfortunately, the only way to deal with when to clear the modifiers
1890 // or not seems to be to have hard-wired rules like this.
1891 if ( !((' ' == c) || (0xa0 == c) || (mods & MOD_MASK_CMD)
1892 || 0x9 == c || 0xd == c || ESC == c) ) {
1893 mods &= ~MOD_MASK_SHIFT;
1894 mods &= ~MOD_MASK_CTRL;
1895 //NSLog(@"clear shift ctrl");
1898 // HACK! All Option+key presses go via 'insert text' messages, except
1899 // for <M-Space>. If the Alt flag is not cleared for <M-Space> it does
1900 // not work to map to it.
1901 if (0xa0 == c && !(mods & MOD_MASK_CMD)) {
1902 //NSLog(@"clear alt");
1903 mods &= ~MOD_MASK_ALT;
1907 if (input_conv.vc_type != CONV_NONE) {
1908 conv_str = string_convert(&input_conv, chars, &length);
1915 if (chars && length > 0) {
1917 //NSLog(@"adding mods: %d", mods);
1919 modChars[1] = KS_MODIFIER;
1921 add_to_input_buf(modChars, 3);
1924 //NSLog(@"add to input buf: 0x%x", chars[0]);
1925 // TODO: Check for CSI bytes?
1926 add_to_input_buf(chars, length);
1935 - (void)queueMessage:(int)msgid data:(NSData *)data
1937 //if (msgid != EnableMenuItemMsgID)
1938 // NSLog(@"queueMessage:%s", MessageStrings[msgid]);
1940 [outputQueue addObject:[NSData dataWithBytes:&msgid length:sizeof(int)]];
1942 [outputQueue addObject:data];
1944 [outputQueue addObject:[NSData data]];
1947 - (void)connectionDidDie:(NSNotification *)notification
1949 // If the main connection to MacVim is lost this means that MacVim was
1950 // either quit (by the user chosing Quit on the MacVim menu), or it has
1951 // crashed. In the former case the flag 'isTerminating' is set and we then
1952 // quit cleanly; in the latter case we make sure the swap files are left
1955 // NOTE: This is not called if a Vim controller invalidates its connection.
1957 //NSLog(@"%s isTerminating=%d", _cmd, isTerminating);
1961 getout_preserve_modified(1);
1964 - (void)blinkTimerFired:(NSTimer *)timer
1966 NSTimeInterval timeInterval = 0;
1968 [blinkTimer release];
1971 if (MMBlinkStateOn == blinkState) {
1972 gui_undraw_cursor();
1973 blinkState = MMBlinkStateOff;
1974 timeInterval = blinkOffInterval;
1975 } else if (MMBlinkStateOff == blinkState) {
1976 gui_update_cursor(TRUE, FALSE);
1977 blinkState = MMBlinkStateOn;
1978 timeInterval = blinkOnInterval;
1981 if (timeInterval > 0) {
1983 [[NSTimer scheduledTimerWithTimeInterval:timeInterval target:self
1984 selector:@selector(blinkTimerFired:)
1985 userInfo:nil repeats:NO] retain];
1986 [self flushQueue:YES];
1990 - (void)focusChange:(BOOL)on
1992 gui_focus_change(on);
1995 - (void)handleToggleToolbar
1997 // If 'go' contains 'T', then remove it, else add it.
1999 char_u go[sizeof(GO_ALL)+2];
2004 p = vim_strchr(go, GO_TOOLBAR);
2008 char_u *end = go + len;
2014 go[len] = GO_TOOLBAR;
2018 set_option_value((char_u*)"guioptions", 0, go, 0);
2020 // Force screen redraw (does it have to be this complicated?).
2021 redraw_all_later(CLEAR);
2022 update_screen(NOT_VALID);
2025 gui_update_cursor(FALSE, FALSE);
2029 - (void)handleScrollbarEvent:(NSData *)data
2033 const void *bytes = [data bytes];
2034 long ident = *((long*)bytes); bytes += sizeof(long);
2035 int hitPart = *((int*)bytes); bytes += sizeof(int);
2036 float fval = *((float*)bytes); bytes += sizeof(float);
2037 scrollbar_T *sb = gui_find_scrollbar(ident);
2040 scrollbar_T *sb_info = sb->wp ? &sb->wp->w_scrollbars[0] : sb;
2041 long value = sb_info->value;
2042 long size = sb_info->size;
2043 long max = sb_info->max;
2044 BOOL isStillDragging = NO;
2045 BOOL updateKnob = YES;
2048 case NSScrollerDecrementPage:
2049 value -= (size > 2 ? size - 2 : 1);
2051 case NSScrollerIncrementPage:
2052 value += (size > 2 ? size - 2 : 1);
2054 case NSScrollerDecrementLine:
2057 case NSScrollerIncrementLine:
2060 case NSScrollerKnob:
2061 isStillDragging = YES;
2063 case NSScrollerKnobSlot:
2064 value = (long)(fval * (max - size + 1));
2071 //NSLog(@"value %d -> %d", sb_info->value, value);
2072 gui_drag_scrollbar(sb, value, isStillDragging);
2075 // Dragging the knob or option+clicking automatically updates
2076 // the knob position (on the actual NSScroller), so we only
2077 // need to set the knob position in the other cases.
2079 // Update both the left&right vertical scrollbars.
2080 long identLeft = sb->wp->w_scrollbars[SBAR_LEFT].ident;
2081 long identRight = sb->wp->w_scrollbars[SBAR_RIGHT].ident;
2082 [self setScrollbarThumbValue:value size:size max:max
2083 identifier:identLeft];
2084 [self setScrollbarThumbValue:value size:size max:max
2085 identifier:identRight];
2087 // Update the horizontal scrollbar.
2088 [self setScrollbarThumbValue:value size:size max:max
2095 - (void)handleSetFont:(NSData *)data
2099 const void *bytes = [data bytes];
2100 float pointSize = *((float*)bytes); bytes += sizeof(float);
2101 //unsigned len = *((unsigned*)bytes); bytes += sizeof(unsigned);
2102 bytes += sizeof(unsigned); // len not used
2104 NSMutableString *name = [NSMutableString stringWithUTF8String:bytes];
2105 [name appendString:[NSString stringWithFormat:@":h%.2f", pointSize]];
2106 char_u *s = (char_u*)[name UTF8String];
2109 s = CONVERT_FROM_UTF8(s);
2112 set_option_value((char_u*)"guifont", 0, s, 0);
2115 CONVERT_FROM_UTF8_FREE(s);
2118 // Force screen redraw (does it have to be this complicated?).
2119 redraw_all_later(CLEAR);
2120 update_screen(NOT_VALID);
2123 gui_update_cursor(FALSE, FALSE);
2127 - (void)handleDropFiles:(NSData *)data
2129 // TODO: Get rid of this method; instead use Vim script directly. At the
2130 // moment I know how to do this to open files in tabs, but I'm not sure how
2131 // to add the filenames to the command line when in command line mode.
2135 NSMutableDictionary *args = [NSMutableDictionary dictionaryWithData:data];
2138 id obj = [args objectForKey:@"forceOpen"];
2139 BOOL forceOpen = YES;
2141 forceOpen = [obj boolValue];
2143 NSArray *filenames = [args objectForKey:@"filenames"];
2144 if (!(filenames && [filenames count] > 0)) return;
2147 if (!forceOpen && (State & CMDLINE)) {
2148 // HACK! If Vim is in command line mode then the files names
2149 // should be added to the command line, instead of opening the
2150 // files in tabs (unless forceOpen is set). This is taken care of by
2151 // gui_handle_drop().
2152 int n = [filenames count];
2153 char_u **fnames = (char_u **)alloc(n * sizeof(char_u *));
2156 for (i = 0; i < n; ++i)
2157 fnames[i] = [[filenames objectAtIndex:i] vimStringSave];
2159 // NOTE! This function will free 'fnames'.
2160 // HACK! It is assumed that the 'x' and 'y' arguments are
2161 // unused when in command line mode.
2162 gui_handle_drop(0, 0, 0, fnames, n);
2167 [self handleOpenWithArguments:args];
2171 - (void)handleDropString:(NSData *)data
2176 char_u dropkey[3] = { CSI, KS_EXTRA, (char_u)KE_DROP };
2177 const void *bytes = [data bytes];
2178 int len = *((int*)bytes); bytes += sizeof(int);
2179 NSMutableString *string = [NSMutableString stringWithUTF8String:bytes];
2181 // Replace unrecognized end-of-line sequences with \x0a (line feed).
2182 NSRange range = { 0, [string length] };
2183 unsigned n = [string replaceOccurrencesOfString:@"\x0d\x0a"
2184 withString:@"\x0a" options:0
2187 n = [string replaceOccurrencesOfString:@"\x0d" withString:@"\x0a"
2188 options:0 range:range];
2191 len = [string lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
2192 char_u *s = (char_u*)[string UTF8String];
2194 if (input_conv.vc_type != CONV_NONE)
2195 s = string_convert(&input_conv, s, &len);
2197 dnd_yank_drag_data(s, len);
2199 if (input_conv.vc_type != CONV_NONE)
2202 add_to_input_buf(dropkey, sizeof(dropkey));
2206 - (void)startOdbEditWithArguments:(NSDictionary *)args
2208 #ifdef FEAT_ODB_EDITOR
2209 id obj = [args objectForKey:@"remoteID"];
2212 OSType serverID = [obj unsignedIntValue];
2213 NSString *remotePath = [args objectForKey:@"remotePath"];
2215 NSAppleEventDescriptor *token = nil;
2216 NSData *tokenData = [args objectForKey:@"remoteTokenData"];
2217 obj = [args objectForKey:@"remoteTokenDescType"];
2218 if (tokenData && obj) {
2219 DescType tokenType = [obj unsignedLongValue];
2220 token = [NSAppleEventDescriptor descriptorWithDescriptorType:tokenType
2224 NSArray *filenames = [args objectForKey:@"filenames"];
2225 unsigned i, numFiles = [filenames count];
2226 for (i = 0; i < numFiles; ++i) {
2227 NSString *filename = [filenames objectAtIndex:i];
2228 char_u *s = [filename vimStringSave];
2229 buf_T *buf = buflist_findname(s);
2233 if (buf->b_odb_token) {
2234 [(NSAppleEventDescriptor*)(buf->b_odb_token) release];
2235 buf->b_odb_token = NULL;
2238 if (buf->b_odb_fname) {
2239 vim_free(buf->b_odb_fname);
2240 buf->b_odb_fname = NULL;
2243 buf->b_odb_server_id = serverID;
2246 buf->b_odb_token = [token retain];
2248 buf->b_odb_fname = [remotePath vimStringSave];
2250 NSLog(@"WARNING: Could not find buffer '%@' for ODB editing.",
2254 #endif // FEAT_ODB_EDITOR
2257 - (void)handleXcodeMod:(NSData *)data
2260 const void *bytes = [data bytes];
2261 DescType type = *((DescType*)bytes); bytes += sizeof(DescType);
2262 unsigned len = *((unsigned*)bytes); bytes += sizeof(unsigned);
2266 NSAppleEventDescriptor *replyEvent = [NSAppleEventDescriptor
2267 descriptorWithDescriptorType:type
2273 - (void)handleOpenWithArguments:(NSDictionary *)args
2275 // ARGUMENT: DESCRIPTION:
2276 // -------------------------------------------------------------
2277 // filenames list of filenames
2278 // dontOpen don't open files specified in above argument
2279 // layout which layout to use to open files
2280 // selectionRange range of lines to select
2281 // searchText string to search for
2282 // cursorLine line to position the cursor on
2283 // cursorColumn column to position the cursor on
2284 // (only valid when "cursorLine" is set)
2285 // remoteID ODB parameter
2286 // remotePath ODB parameter
2287 // remoteTokenDescType ODB parameter
2288 // remoteTokenData ODB parameter
2290 //NSLog(@"%s%@ (starting=%d)", _cmd, args, starting);
2292 NSArray *filenames = [args objectForKey:@"filenames"];
2293 int i, numFiles = filenames ? [filenames count] : 0;
2294 BOOL openFiles = ![[args objectForKey:@"dontOpen"] boolValue];
2295 int layout = [[args objectForKey:@"layout"] intValue];
2297 // Change to directory of first file to open if this is an "unused" editor
2298 // (but do not do this if editing remotely).
2299 if (openFiles && numFiles > 0 && ![args objectForKey:@"remoteID"]
2300 && (starting || [self unusedEditor]) ) {
2301 char_u *s = [[filenames objectAtIndex:0] vimStringSave];
2307 // When Vim is starting we simply add the files to be opened to the
2308 // global arglist and Vim will take care of opening them for us.
2309 if (openFiles && numFiles > 0) {
2310 for (i = 0; i < numFiles; i++) {
2311 NSString *fname = [filenames objectAtIndex:i];
2314 if (ga_grow(&global_alist.al_ga, 1) == FAIL
2315 || (p = [fname vimStringSave]) == NULL)
2316 exit(2); // See comment in -[MMBackend exit]
2318 alist_add(&global_alist, p, 2);
2321 // Vim will take care of arranging the files added to the arglist
2322 // in windows or tabs; all we must do is to specify which layout to
2324 initialWindowLayout = layout;
2327 // When Vim is already open we resort to some trickery to open the
2328 // files with the specified layout.
2330 // TODO: Figure out a better way to handle this?
2331 if (openFiles && numFiles > 0) {
2332 BOOL oneWindowInTab = topframe ? YES
2333 : (topframe->fr_layout == FR_LEAF);
2334 BOOL bufChanged = NO;
2335 BOOL bufHasFilename = NO;
2337 bufChanged = curbufIsChanged();
2338 bufHasFilename = curbuf->b_ffname != NULL;
2341 // Temporarily disable flushing since the following code may
2342 // potentially cause multiple redraws.
2343 flushDisabled = YES;
2345 BOOL onlyOneTab = (first_tabpage->tp_next == NULL);
2346 if (WIN_TABS == layout && !onlyOneTab) {
2347 // By going to the last tabpage we ensure that the new tabs
2348 // will appear last (if this call is left out, the taborder
2353 // Make sure we're in normal mode first.
2354 [self addInput:@"<C-\\><C-N>"];
2357 // With "split layout" we open a new tab before opening
2358 // multiple files if the current tab has more than one window
2359 // or if there is exactly one window but whose buffer has a
2360 // filename. (The :drop command ensures modified buffers get
2361 // their own window.)
2362 if ((WIN_HOR == layout || WIN_VER == layout) &&
2363 (!oneWindowInTab || bufHasFilename))
2364 [self addInput:@":tabnew<CR>"];
2366 // The files are opened by constructing a ":drop ..." command
2367 // and executing it.
2368 NSMutableString *cmd = (WIN_TABS == layout)
2369 ? [NSMutableString stringWithString:@":tab drop"]
2370 : [NSMutableString stringWithString:@":drop"];
2372 for (i = 0; i < numFiles; ++i) {
2373 NSString *file = [filenames objectAtIndex:i];
2374 file = [file stringByEscapingSpecialFilenameCharacters];
2375 [cmd appendString:@" "];
2376 [cmd appendString:file];
2379 // Temporarily clear 'suffixes' so that the files are opened in
2380 // the same order as they appear in the "filenames" array.
2381 [self addInput:@":let t:mvim_oldsu=&su|set su=<CR>"];
2383 [self addInput:cmd];
2385 // Split the view into multiple windows if requested.
2386 if (WIN_HOR == layout)
2387 [self addInput:@"|sall"];
2388 else if (WIN_VER == layout)
2389 [self addInput:@"|vert sall"];
2391 // Restore the old value of 'suffixes'.
2392 [self addInput:@"|let &su=t:mvim_oldsu|unlet t:mvim_oldsu"];
2394 // Adding "|redr|f" ensures a "Hit ENTER" prompt is not shown.
2395 [self addInput:@"|redr|f<CR>"];
2397 // When opening one file we try to reuse the current window,
2398 // but not if its buffer is modified or has a filename.
2399 // However, the 'arglist' layout always opens the file in the
2401 NSString *file = [[filenames lastObject]
2402 stringByEscapingSpecialFilenameCharacters];
2404 if (WIN_HOR == layout) {
2405 if (!(bufHasFilename || bufChanged))
2406 cmd = [NSString stringWithFormat:@":e %@", file];
2408 cmd = [NSString stringWithFormat:@":sp %@", file];
2409 } else if (WIN_VER == layout) {
2410 if (!(bufHasFilename || bufChanged))
2411 cmd = [NSString stringWithFormat:@":e %@", file];
2413 cmd = [NSString stringWithFormat:@":vsp %@", file];
2414 } else if (WIN_TABS == layout) {
2415 if (oneWindowInTab && !(bufHasFilename || bufChanged))
2416 cmd = [NSString stringWithFormat:@":e %@", file];
2418 cmd = [NSString stringWithFormat:@":tabe %@", file];
2420 // (The :drop command will split if there is a modified
2422 cmd = [NSString stringWithFormat:@":drop %@", file];
2425 [self addInput:cmd];
2427 // Adding "|redr|f" ensures a "Hit ENTER" prompt is not shown.
2428 [self addInput:@"|redr|f<CR>"];
2431 // Force screen redraw (does it have to be this complicated?).
2432 // (This code was taken from the end of gui_handle_drop().)
2433 update_screen(NOT_VALID);
2436 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 char_u *s = (char_u*)[input UTF8String];
2507 s = CONVERT_FROM_UTF8(s);
2510 server_to_input_buf(s);
2513 CONVERT_FROM_UTF8_FREE(s);
2517 - (BOOL)unusedEditor
2519 BOOL oneWindowInTab = topframe ? YES
2520 : (topframe->fr_layout == FR_LEAF);
2521 BOOL bufChanged = NO;
2522 BOOL bufHasFilename = NO;
2524 bufChanged = curbufIsChanged();
2525 bufHasFilename = curbuf->b_ffname != NULL;
2528 BOOL onlyOneTab = (first_tabpage->tp_next == NULL);
2530 return onlyOneTab && oneWindowInTab && !bufChanged && !bufHasFilename;
2533 @end // MMBackend (Private)
2538 @implementation MMBackend (ClientServer)
2540 - (NSString *)connectionNameFromServerName:(NSString *)name
2542 NSString *bundlePath = [[NSBundle mainBundle] bundlePath];
2544 return [[NSString stringWithFormat:@"%@.%@", bundlePath, name]
2548 - (NSConnection *)connectionForServerName:(NSString *)name
2550 // TODO: Try 'name%d' if 'name' fails.
2551 NSString *connName = [self connectionNameFromServerName:name];
2552 NSConnection *svrConn = [connectionNameDict objectForKey:connName];
2555 svrConn = [NSConnection connectionWithRegisteredName:connName
2557 // Try alternate server...
2558 if (!svrConn && alternateServerName) {
2559 //NSLog(@" trying to connect to alternate server: %@",
2560 // alternateServerName);
2561 connName = [self connectionNameFromServerName:alternateServerName];
2562 svrConn = [NSConnection connectionWithRegisteredName:connName
2566 // Try looking for alternate servers...
2568 //NSLog(@" looking for alternate servers...");
2569 NSString *alt = [self alternateServerNameForName:name];
2570 if (alt != alternateServerName) {
2571 //NSLog(@" found alternate server: %@", string);
2572 [alternateServerName release];
2573 alternateServerName = [alt copy];
2577 // Try alternate server again...
2578 if (!svrConn && alternateServerName) {
2579 //NSLog(@" trying to connect to alternate server: %@",
2580 // alternateServerName);
2581 connName = [self connectionNameFromServerName:alternateServerName];
2582 svrConn = [NSConnection connectionWithRegisteredName:connName
2587 [connectionNameDict setObject:svrConn forKey:connName];
2589 //NSLog(@"Adding %@ as connection observer for %@", self, svrConn);
2590 [[NSNotificationCenter defaultCenter] addObserver:self
2591 selector:@selector(serverConnectionDidDie:)
2592 name:NSConnectionDidDieNotification object:svrConn];
2599 - (NSConnection *)connectionForServerPort:(int)port
2602 NSEnumerator *e = [connectionNameDict objectEnumerator];
2604 while ((conn = [e nextObject])) {
2605 // HACK! Assume connection uses mach ports.
2606 if (port == [(NSMachPort*)[conn sendPort] machPort])
2613 - (void)serverConnectionDidDie:(NSNotification *)notification
2615 //NSLog(@"%s%@", _cmd, notification);
2617 NSConnection *svrConn = [notification object];
2619 //NSLog(@"Removing %@ as connection observer from %@", self, svrConn);
2620 [[NSNotificationCenter defaultCenter]
2622 name:NSConnectionDidDieNotification
2625 [connectionNameDict removeObjectsForKeys:
2626 [connectionNameDict allKeysForObject:svrConn]];
2628 // HACK! Assume connection uses mach ports.
2629 int port = [(NSMachPort*)[svrConn sendPort] machPort];
2630 NSNumber *key = [NSNumber numberWithInt:port];
2632 [clientProxyDict removeObjectForKey:key];
2633 [serverReplyDict removeObjectForKey:key];
2636 - (void)addClient:(NSDistantObject *)client
2638 NSConnection *conn = [client connectionForProxy];
2639 // HACK! Assume connection uses mach ports.
2640 int port = [(NSMachPort*)[conn sendPort] machPort];
2641 NSNumber *key = [NSNumber numberWithInt:port];
2643 if (![clientProxyDict objectForKey:key]) {
2644 [client setProtocolForProxy:@protocol(MMVimClientProtocol)];
2645 [clientProxyDict setObject:client forKey:key];
2648 // NOTE: 'clientWindow' is a global variable which is used by <client>
2649 clientWindow = port;
2652 - (NSString *)alternateServerNameForName:(NSString *)name
2654 if (!(name && [name length] > 0))
2657 // Only look for alternates if 'name' doesn't end in a digit.
2658 unichar lastChar = [name characterAtIndex:[name length]-1];
2659 if (lastChar >= '0' && lastChar <= '9')
2662 // Look for alternates among all current servers.
2663 NSArray *list = [self serverList];
2664 if (!(list && [list count] > 0))
2667 // Filter out servers starting with 'name' and ending with a number. The
2668 // (?i) pattern ensures that the match is case insensitive.
2669 NSString *pat = [NSString stringWithFormat:@"(?i)%@[0-9]+\\z", name];
2670 NSPredicate *pred = [NSPredicate predicateWithFormat:
2671 @"SELF MATCHES %@", pat];
2672 list = [list filteredArrayUsingPredicate:pred];
2673 if ([list count] > 0) {
2674 list = [list sortedArrayUsingSelector:@selector(serverNameCompare:)];
2675 return [list objectAtIndex:0];
2681 @end // MMBackend (ClientServer)
2686 @implementation NSString (MMServerNameCompare)
2687 - (NSComparisonResult)serverNameCompare:(NSString *)string
2689 return [self compare:string
2690 options:NSCaseInsensitiveSearch|NSNumericSearch];
2697 static int eventModifierFlagsToVimModMask(int modifierFlags)
2701 if (modifierFlags & NSShiftKeyMask)
2702 modMask |= MOD_MASK_SHIFT;
2703 if (modifierFlags & NSControlKeyMask)
2704 modMask |= MOD_MASK_CTRL;
2705 if (modifierFlags & NSAlternateKeyMask)
2706 modMask |= MOD_MASK_ALT;
2707 if (modifierFlags & NSCommandKeyMask)
2708 modMask |= MOD_MASK_CMD;
2713 static int eventModifierFlagsToVimMouseModMask(int modifierFlags)
2717 if (modifierFlags & NSShiftKeyMask)
2718 modMask |= MOUSE_SHIFT;
2719 if (modifierFlags & NSControlKeyMask)
2720 modMask |= MOUSE_CTRL;
2721 if (modifierFlags & NSAlternateKeyMask)
2722 modMask |= MOUSE_ALT;
2727 static int eventButtonNumberToVimMouseButton(int buttonNumber)
2729 static int mouseButton[] = { MOUSE_LEFT, MOUSE_RIGHT, MOUSE_MIDDLE };
2731 return (buttonNumber >= 0 && buttonNumber < 3)
2732 ? mouseButton[buttonNumber] : -1;
2737 // This function is modeled after the VimToPython function found in if_python.c
2738 // NB This does a deep copy by value, it does not lookup references like the
2739 // VimToPython function does. This is because I didn't want to deal with the
2740 // retain cycles that this would create, and we can cover 99% of the use cases
2741 // by ignoring it. If we ever switch to using GC in MacVim then this
2742 // functionality can be implemented easily.
2743 static id vimToCocoa(typval_T * tv, int depth)
2749 // Avoid infinite recursion
2754 if (tv->v_type == VAR_STRING) {
2755 char_u * val = tv->vval.v_string;
2756 // val can be NULL if the string is empty
2758 result = [NSString string];
2761 val = CONVERT_TO_UTF8(val);
2763 result = [NSString stringWithUTF8String:(char*)val];
2765 CONVERT_TO_UTF8_FREE(val);
2768 } else if (tv->v_type == VAR_NUMBER) {
2769 // looks like sizeof(varnumber_T) is always <= sizeof(long)
2770 result = [NSNumber numberWithLong:(long)tv->vval.v_number];
2771 } else if (tv->v_type == VAR_LIST) {
2772 list_T * list = tv->vval.v_list;
2775 NSMutableArray * arr = result = [NSMutableArray array];
2778 for (curr = list->lv_first; curr != NULL; curr = curr->li_next) {
2779 newObj = vimToCocoa(&curr->li_tv, depth + 1);
2780 [arr addObject:newObj];
2783 } else if (tv->v_type == VAR_DICT) {
2784 NSMutableDictionary * dict = result = [NSMutableDictionary dictionary];
2786 if (tv->vval.v_dict != NULL) {
2787 hashtab_T * ht = &tv->vval.v_dict->dv_hashtab;
2788 int todo = ht->ht_used;
2792 for (hi = ht->ht_array; todo > 0; ++hi) {
2793 if (!HASHITEM_EMPTY(hi)) {
2796 di = dict_lookup(hi);
2797 newObj = vimToCocoa(&di->di_tv, depth + 1);
2799 char_u * keyval = hi->hi_key;
2801 keyval = CONVERT_TO_UTF8(keyval);
2803 NSString * key = [NSString stringWithUTF8String:(char*)keyval];
2805 CONVERT_TO_UTF8_FREE(keyval);
2807 [dict setObject:newObj forKey:key];
2811 } else { // only func refs should fall into this category?
2819 // This function is modeled after eval_client_expr_to_string found in main.c
2820 // Returns nil if there was an error evaluating the expression, and writes a
2821 // message to errorStr.
2822 // TODO Get the error that occurred while evaluating the expression in vim
2824 static id evalExprCocoa(NSString * expr, NSString ** errstr)
2827 char_u *s = (char_u*)[expr UTF8String];
2830 s = CONVERT_FROM_UTF8(s);
2833 int save_dbl = debug_break_level;
2834 int save_ro = redir_off;
2836 debug_break_level = -1;
2840 typval_T * tvres = eval_expr(s, NULL);
2842 debug_break_level = save_dbl;
2843 redir_off = save_ro;
2850 CONVERT_FROM_UTF8_FREE(s);
2855 gui_update_cursor(FALSE, FALSE);
2858 if (tvres == NULL) {
2860 *errstr = @"Expression evaluation failed.";
2863 id res = vimToCocoa(tvres, 1);
2868 *errstr = @"Conversion to cocoa values failed.";
2877 @implementation NSString (VimStrings)
2879 + (id)stringWithVimString:(char_u *)s
2881 // This method ensures a non-nil string is returned. If 's' cannot be
2882 // converted to a utf-8 string it is assumed to be latin-1. If conversion
2883 // still fails an empty NSString is returned.
2884 NSString *string = nil;
2887 s = CONVERT_TO_UTF8(s);
2889 string = [NSString stringWithUTF8String:(char*)s];
2891 // HACK! Apparently 's' is not a valid utf-8 string, maybe it is
2893 string = [NSString stringWithCString:(char*)s
2894 encoding:NSISOLatin1StringEncoding];
2897 CONVERT_TO_UTF8_FREE(s);
2901 return string != nil ? string : [NSString string];
2904 - (char_u *)vimStringSave
2906 char_u *s = (char_u*)[self UTF8String], *ret = NULL;
2909 s = CONVERT_FROM_UTF8(s);
2911 ret = vim_strsave(s);
2913 CONVERT_FROM_UTF8_FREE(s);
2919 @end // NSString (VimStrings)