1 /* vi:set ts=8 sts=4 sw=4 ft=objc:
3 * VIM - Vi IMproved by Bram Moolenaar
4 * MacVim GUI port by Bjorn Winckler
6 * Do ":help uganda" in Vim to read copying and usage conditions.
7 * Do ":help credits" in Vim to see a list of people who contributed.
8 * See README.txt for an overview of the Vim source code.
13 * MMBackend communicates with the frontend (MacVim). It maintains a queue of
14 * output which is flushed to the frontend under controlled circumstances (so
15 * as to maintain a steady framerate). Input from the frontend is also handled
18 * The frontend communicates with the backend via the MMBackendProtocol. In
19 * particular, input is sent to the backend via processInput:data: and Vim
20 * state can be queried from the frontend with evaluateExpression:.
22 * It is very important to realize that all state is held by the backend, the
23 * frontend must either ask for state [MMBackend evaluateExpression:] or wait
24 * for the backend to update [MMAppController processInput:forIdentifier:].
26 * The client/server functionality of Vim is handled by the backend. It sets
27 * up a named NSConnection to which other Vim processes can connect.
34 // NOTE: Colors in MMBackend are stored as unsigned ints on the form 0xaarrggbb
35 // whereas colors in Vim are int without the alpha component. Also note that
36 // 'transp' is assumed to be a value between 0 and 100.
37 #define MM_COLOR(col) ((unsigned)( ((col)&0xffffff) | 0xff000000 ))
38 #define MM_COLOR_WITH_TRANSP(col,transp) \
39 ((unsigned)( ((col)&0xffffff) \
40 | ((((unsigned)((((100-(transp))*255)/100)+.5f))&0xff)<<24) ))
42 // Values for window layout (must match values in main.c).
43 #define WIN_HOR 1 // "-o" horizontally split windows
44 #define WIN_VER 2 // "-O" vertically split windows
45 #define WIN_TABS 3 // "-p" windows on tab pages
47 static unsigned MMServerMax = 1000;
49 // TODO: Move to separate file.
50 static int eventModifierFlagsToVimModMask(int modifierFlags);
51 static int eventModifierFlagsToVimMouseModMask(int modifierFlags);
52 static int eventButtonNumberToVimMouseButton(int buttonNumber);
55 vimmenu_T *menu_for_descriptor(NSArray *desc);
57 static id evalExprCocoa(NSString * expr, NSString ** errstr);
59 extern void im_preedit_start_macvim();
60 extern void im_preedit_end_macvim();
61 extern void im_preedit_abandon_macvim();
62 extern void im_preedit_changed_macvim(char *preedit_string, int cursor_index);
70 static NSString *MMSymlinkWarningString =
71 @"\n\n\tMost likely this is because you have symlinked directly to\n"
72 "\tthe Vim binary, which Cocoa does not allow. Please use an\n"
73 "\talias or the mvim shell script instead. If you have not used\n"
74 "\ta symlink, then your MacVim.app bundle is incomplete.\n\n";
77 // Keycodes recognized by Vim (struct taken from gui_x11.c and gui_w48.c)
78 // (The key codes were taken from Carbon/HIToolbox/Events.)
79 static struct specialkey
86 {0x7e /*kVK_UpArrow*/, 'k', 'u'},
87 {0x7d /*kVK_DownArrow*/, 'k', 'd'},
88 {0x7b /*kVK_LeftArrow*/, 'k', 'l'},
89 {0x7c /*kVK_RightArrow*/, 'k', 'r'},
91 {0x7a /*kVK_F1*/, 'k', '1'},
92 {0x78 /*kVK_F2*/, 'k', '2'},
93 {0x63 /*kVK_F3*/, 'k', '3'},
94 {0x76 /*kVK_F4*/, 'k', '4'},
95 {0x60 /*kVK_F5*/, 'k', '5'},
96 {0x61 /*kVK_F6*/, 'k', '6'},
97 {0x62 /*kVK_F7*/, 'k', '7'},
98 {0x64 /*kVK_F8*/, 'k', '8'},
99 {0x65 /*kVK_F9*/, 'k', '9'},
100 {0x6d /*kVK_F10*/, 'k', ';'},
102 {0x67 /*kVK_F11*/, 'F', '1'},
103 {0x6f /*kVK_F12*/, 'F', '2'},
104 {0x69 /*kVK_F13*/, 'F', '3'},
105 {0x6b /*kVK_F14*/, 'F', '4'},
106 {0x71 /*kVK_F15*/, 'F', '5'},
107 {0x6a /*kVK_F16*/, 'F', '6'},
108 {0x40 /*kVK_F17*/, 'F', '7'},
109 {0x4f /*kVK_F18*/, 'F', '8'},
110 {0x50 /*kVK_F19*/, 'F', '9'},
111 {0x5a /*kVK_F20*/, 'F', 'A'},
113 {0x72 /*kVK_Help*/, '%', '1'},
114 {0x33 /*kVK_Delete*/, 'k', 'b'},
115 {0x75 /*kVK_ForwardDelete*/, 'k', 'D'},
116 {0x73 /*kVK_Home*/, 'k', 'h'},
117 {0x77 /*kVK_End*/, '@', '7'},
118 {0x74 /*kVK_PageUp*/, 'k', 'P'},
119 {0x79 /*kVK_PageDown*/, 'k', 'N'},
122 {0x45 /*kVK_ANSI_KeypadPlus*/, 'K', '6'},
123 {0x4e /*kVK_ANSI_KeypadMinus*/, 'K', '7'},
124 {0x4b /*kVK_ANSI_KeypadDivide*/, 'K', '8'},
125 {0x43 /*kVK_ANSI_KeypadMultiply*/, 'K', '9'},
126 {0x4c /*kVK_ANSI_KeypadEnter*/, 'K', 'A'},
127 {0x41 /*kVK_ANSI_KeypadDecimal*/, 'K', 'B'},
128 {0x47 /*kVK_ANSI_KeypadClear*/, KS_EXTRA, (char_u)KE_KDEL},
130 {0x52 /*kVK_ANSI_Keypad0*/, 'K', 'C'},
131 {0x53 /*kVK_ANSI_Keypad1*/, 'K', 'D'},
132 {0x54 /*kVK_ANSI_Keypad2*/, 'K', 'E'},
133 {0x55 /*kVK_ANSI_Keypad3*/, 'K', 'F'},
134 {0x56 /*kVK_ANSI_Keypad4*/, 'K', 'G'},
135 {0x57 /*kVK_ANSI_Keypad5*/, 'K', 'H'},
136 {0x58 /*kVK_ANSI_Keypad6*/, 'K', 'I'},
137 {0x59 /*kVK_ANSI_Keypad7*/, 'K', 'J'},
138 {0x5b /*kVK_ANSI_Keypad8*/, 'K', 'K'},
139 {0x5c /*kVK_ANSI_Keypad9*/, 'K', 'L'},
141 /* Keys that we want to be able to use any modifier with: */
142 {0x31 /*kVK_Space*/, ' ', NUL},
143 {0x30 /*kVK_Tab*/, TAB, NUL},
144 {0x35 /*kVK_Escape*/, ESC, NUL},
145 {0x24 /*kVK_Return*/, CAR, NUL},
147 /* End of list marker: */
152 extern GuiFont gui_mch_retain_font(GuiFont font);
156 @interface NSString (MMServerNameCompare)
157 - (NSComparisonResult)serverNameCompare:(NSString *)string;
163 @interface MMBackend (Private)
164 - (void)clearDrawData;
165 - (void)didChangeWholeLine;
166 - (void)waitForDialogReturn;
167 - (void)insertVimStateMessage;
168 - (void)processInputQueue;
169 - (void)handleInputEvent:(int)msgid data:(NSData *)data;
170 - (void)doKeyDown:(NSString *)key
171 keyCode:(unsigned)code
173 - (BOOL)handleSpecialKey:(NSString *)key
174 keyCode:(unsigned)code
176 - (BOOL)handleMacMetaKey:(int)ikey modifiers:(int)mods;
177 - (void)queueMessage:(int)msgid data:(NSData *)data;
178 - (void)connectionDidDie:(NSNotification *)notification;
179 - (void)blinkTimerFired:(NSTimer *)timer;
180 - (void)focusChange:(BOOL)on;
181 - (void)handleToggleToolbar;
182 - (void)handleScrollbarEvent:(NSData *)data;
183 - (void)handleSetFont:(NSData *)data;
184 - (void)handleDropFiles:(NSData *)data;
185 - (void)handleDropString:(NSData *)data;
186 - (void)startOdbEditWithArguments:(NSDictionary *)args;
187 - (void)handleXcodeMod:(NSData *)data;
188 - (void)handleOpenWithArguments:(NSDictionary *)args;
189 - (BOOL)checkForModifiedBuffers;
190 - (void)addInput:(NSString *)input;
191 - (BOOL)unusedEditor;
192 - (void)redrawScreen;
193 - (void)handleFindReplace:(NSDictionary *)args;
194 - (void)handleMarkedText:(NSData *)data;
199 @interface MMBackend (ClientServer)
200 - (NSString *)connectionNameFromServerName:(NSString *)name;
201 - (NSConnection *)connectionForServerName:(NSString *)name;
202 - (NSConnection *)connectionForServerPort:(int)port;
203 - (void)serverConnectionDidDie:(NSNotification *)notification;
204 - (void)addClient:(NSDistantObject *)client;
205 - (NSString *)alternateServerNameForName:(NSString *)name;
210 @implementation MMBackend
212 + (MMBackend *)sharedInstance
214 static MMBackend *singleton = nil;
215 return singleton ? singleton : (singleton = [MMBackend new]);
221 if (!self) return nil;
223 outputQueue = [[NSMutableArray alloc] init];
224 inputQueue = [[NSMutableArray alloc] init];
225 drawData = [[NSMutableData alloc] initWithCapacity:1024];
226 connectionNameDict = [[NSMutableDictionary alloc] init];
227 clientProxyDict = [[NSMutableDictionary alloc] init];
228 serverReplyDict = [[NSMutableDictionary alloc] init];
230 NSBundle *mainBundle = [NSBundle mainBundle];
231 NSString *path = [mainBundle pathForResource:@"Colors" ofType:@"plist"];
233 colorDict = [[NSDictionary dictionaryWithContentsOfFile:path] retain];
235 path = [mainBundle pathForResource:@"SystemColors" ofType:@"plist"];
237 sysColorDict = [[NSDictionary dictionaryWithContentsOfFile:path]
240 path = [mainBundle pathForResource:@"Actions" ofType:@"plist"];
242 actionDict = [[NSDictionary dictionaryWithContentsOfFile:path] retain];
244 if (!(colorDict && sysColorDict && actionDict)) {
245 ASLogNotice(@"Failed to load dictionaries.%@", MMSymlinkWarningString);
255 [[NSNotificationCenter defaultCenter] removeObserver:self];
257 gui_mch_free_font(oldWideFont); oldWideFont = NOFONT;
258 [blinkTimer release]; blinkTimer = nil;
259 [alternateServerName release]; alternateServerName = nil;
260 [serverReplyDict release]; serverReplyDict = nil;
261 [clientProxyDict release]; clientProxyDict = nil;
262 [connectionNameDict release]; connectionNameDict = nil;
263 [inputQueue release]; inputQueue = nil;
264 [outputQueue release]; outputQueue = nil;
265 [drawData release]; drawData = nil;
266 [connection release]; connection = nil;
267 [actionDict release]; actionDict = nil;
268 [sysColorDict release]; sysColorDict = nil;
269 [colorDict release]; colorDict = nil;
270 [vimServerConnection release]; vimServerConnection = nil;
275 - (void)setBackgroundColor:(int)color
277 backgroundColor = MM_COLOR_WITH_TRANSP(color,p_transp);
280 - (void)setForegroundColor:(int)color
282 foregroundColor = MM_COLOR(color);
285 - (void)setSpecialColor:(int)color
287 specialColor = MM_COLOR(color);
290 - (void)setDefaultColorsBackground:(int)bg foreground:(int)fg
292 defaultBackgroundColor = MM_COLOR_WITH_TRANSP(bg,p_transp);
293 defaultForegroundColor = MM_COLOR(fg);
295 NSMutableData *data = [NSMutableData data];
297 [data appendBytes:&defaultBackgroundColor length:sizeof(unsigned)];
298 [data appendBytes:&defaultForegroundColor length:sizeof(unsigned)];
300 [self queueMessage:SetDefaultColorsMsgID data:data];
303 - (NSConnection *)connection
306 // NOTE! If the name of the connection changes here it must also be
307 // updated in MMAppController.m.
308 NSString *name = [NSString stringWithFormat:@"%@-connection",
309 [[NSBundle mainBundle] bundlePath]];
311 connection = [NSConnection connectionWithRegisteredName:name host:nil];
315 // NOTE: 'connection' may be nil here.
319 - (NSDictionary *)actionDict
324 - (int)initialWindowLayout
326 return initialWindowLayout;
329 - (void)getWindowPositionX:(int*)x Y:(int*)y
331 // NOTE: winposX and winposY are set by the SetWindowPositionMsgID message.
336 - (void)setWindowPositionX:(int)x Y:(int)y
338 // NOTE: Setting the window position has no immediate effect on the cached
339 // variables winposX and winposY. These are set by the frontend when the
340 // window actually moves (see SetWindowPositionMsgID).
341 ASLogDebug(@"x=%d y=%d", x, y);
342 int pos[2] = { x, y };
343 NSData *data = [NSData dataWithBytes:pos length:2*sizeof(int)];
344 [self queueMessage:SetWindowPositionMsgID data:data];
347 - (void)queueMessage:(int)msgid properties:(NSDictionary *)props
349 [self queueMessage:msgid data:[props dictionaryAsData]];
354 if (![self connection]) {
356 // This is a preloaded process and as such should not cause the
357 // MacVim to be opened. We probably got here as a result of the
358 // user quitting MacVim while the process was preloading, so exit
360 // (Don't use mch_exit() since it assumes the process has properly
365 NSBundle *mainBundle = [NSBundle mainBundle];
370 // Launch MacVim using Launch Services (NSWorkspace would be nicer, but
371 // the API to pass Apple Event parameters is broken on 10.4).
372 NSString *path = [mainBundle bundlePath];
373 status = FSPathMakeRef((const UInt8 *)[path UTF8String], &ref, NULL);
374 if (noErr == status) {
375 // Pass parameter to the 'Open' Apple Event that tells MacVim not
376 // to open an untitled window.
377 NSAppleEventDescriptor *desc =
378 [NSAppleEventDescriptor recordDescriptor];
379 [desc setParamDescriptor:
380 [NSAppleEventDescriptor descriptorWithBoolean:NO]
381 forKeyword:keyMMUntitledWindow];
383 LSLaunchFSRefSpec spec = { &ref, 0, NULL, [desc aeDesc],
384 kLSLaunchDefaults, NULL };
385 status = LSOpenFromRefSpec(&spec, NULL);
388 if (noErr != status) {
389 ASLogCrit(@"Failed to launch MacVim (path=%@).%@",
390 path, MMSymlinkWarningString);
394 // Launch MacVim using NSTask. For some reason the above code using
395 // Launch Services sometimes fails on LSOpenFromRefSpec() (when it
396 // fails, the dock icon starts bouncing and never stops). It seems
397 // like rebuilding the Launch Services database takes care of this
398 // problem, but the NSTask way seems more stable so stick with it.
400 // NOTE! Using NSTask to launch the GUI has the negative side-effect
401 // that the GUI won't be activated (or raised) so there is a hack in
402 // MMAppController which raises the app when a new window is opened.
403 NSMutableArray *args = [NSMutableArray arrayWithObjects:
404 [NSString stringWithFormat:@"-%@", MMNoWindowKey], @"yes", nil];
405 NSString *exeName = [[mainBundle infoDictionary]
406 objectForKey:@"CFBundleExecutable"];
407 NSString *path = [mainBundle pathForAuxiliaryExecutable:exeName];
409 ASLogCrit(@"Could not find MacVim executable in bundle.%@",
410 MMSymlinkWarningString);
414 [NSTask launchedTaskWithLaunchPath:path arguments:args];
417 // HACK! Poll the mach bootstrap server until it returns a valid
418 // connection to detect that MacVim has finished launching. Also set a
419 // time-out date so that we don't get stuck doing this forever.
420 NSDate *timeOutDate = [NSDate dateWithTimeIntervalSinceNow:10];
421 while (![self connection] &&
422 NSOrderedDescending == [timeOutDate compare:[NSDate date]])
423 [[NSRunLoop currentRunLoop]
424 runMode:NSDefaultRunLoopMode
425 beforeDate:[NSDate dateWithTimeIntervalSinceNow:.1]];
427 // NOTE: [self connection] will set 'connection' as a side-effect.
429 ASLogCrit(@"Timed-out waiting for GUI to launch.");
435 [[NSNotificationCenter defaultCenter] addObserver:self
436 selector:@selector(connectionDidDie:)
437 name:NSConnectionDidDieNotification object:connection];
439 appProxy = [[connection rootProxy] retain];
440 [appProxy setProtocolForProxy:@protocol(MMAppProtocol)];
442 // NOTE: We do not set any new timeout values for the connection to the
443 // frontend. This means that if the frontend is "stuck" (e.g. in a
444 // modal loop) then any calls to the frontend will block indefinitely
445 // (the default timeouts are huge).
447 int pid = [[NSProcessInfo processInfo] processIdentifier];
449 identifier = [appProxy connectBackend:self pid:pid];
452 @catch (NSException *ex) {
453 ASLogDebug(@"Connect backend failed: reason=%@", ex);
459 - (BOOL)openGUIWindow
461 if (gui_win_x != -1 && gui_win_y != -1) {
462 // NOTE: the gui_win_* coordinates are both set to -1 if no :winpos
463 // command is in .[g]vimrc. (This way of detecting if :winpos has been
464 // used may cause problems if a second monitor is located to the left
465 // and underneath the main monitor as it will have negative
466 // coordinates. However, this seems like a minor problem that is not
467 // worth fixing since all GUIs work this way.)
468 ASLogDebug(@"default x=%d y=%d", gui_win_x, gui_win_y);
469 int pos[2] = { gui_win_x, gui_win_y };
470 NSData *data = [NSData dataWithBytes:pos length:2*sizeof(int)];
471 [self queueMessage:SetWindowPositionMsgID data:data];
474 [self queueMessage:OpenWindowMsgID data:nil];
481 int type = ClearAllDrawType;
483 // Any draw commands in queue are effectively obsolete since this clearAll
484 // will negate any effect they have, therefore we may as well clear the
486 [self clearDrawData];
488 [drawData appendBytes:&type length:sizeof(int)];
491 - (void)clearBlockFromRow:(int)row1 column:(int)col1
492 toRow:(int)row2 column:(int)col2
494 int type = ClearBlockDrawType;
496 [drawData appendBytes:&type length:sizeof(int)];
498 [drawData appendBytes:&defaultBackgroundColor length:sizeof(unsigned)];
499 [drawData appendBytes:&row1 length:sizeof(int)];
500 [drawData appendBytes:&col1 length:sizeof(int)];
501 [drawData appendBytes:&row2 length:sizeof(int)];
502 [drawData appendBytes:&col2 length:sizeof(int)];
505 - (void)deleteLinesFromRow:(int)row count:(int)count
506 scrollBottom:(int)bottom left:(int)left right:(int)right
508 int type = DeleteLinesDrawType;
510 [drawData appendBytes:&type length:sizeof(int)];
512 [drawData appendBytes:&defaultBackgroundColor length:sizeof(unsigned)];
513 [drawData appendBytes:&row length:sizeof(int)];
514 [drawData appendBytes:&count length:sizeof(int)];
515 [drawData appendBytes:&bottom length:sizeof(int)];
516 [drawData appendBytes:&left length:sizeof(int)];
517 [drawData appendBytes:&right length:sizeof(int)];
519 if (left == 0 && right == gui.num_cols-1)
520 [self didChangeWholeLine];
523 - (void)drawString:(char_u*)s length:(int)len row:(int)row
524 column:(int)col cells:(int)cells flags:(int)flags
526 if (len <= 0) return;
528 int type = DrawStringDrawType;
530 [drawData appendBytes:&type length:sizeof(int)];
532 [drawData appendBytes:&backgroundColor length:sizeof(unsigned)];
533 [drawData appendBytes:&foregroundColor length:sizeof(unsigned)];
534 [drawData appendBytes:&specialColor length:sizeof(unsigned)];
535 [drawData appendBytes:&row length:sizeof(int)];
536 [drawData appendBytes:&col length:sizeof(int)];
537 [drawData appendBytes:&cells length:sizeof(int)];
538 [drawData appendBytes:&flags length:sizeof(int)];
539 [drawData appendBytes:&len length:sizeof(int)];
540 [drawData appendBytes:s length:len];
543 - (void)insertLinesFromRow:(int)row count:(int)count
544 scrollBottom:(int)bottom left:(int)left right:(int)right
546 int type = InsertLinesDrawType;
548 [drawData appendBytes:&type length:sizeof(int)];
550 [drawData appendBytes:&defaultBackgroundColor length:sizeof(unsigned)];
551 [drawData appendBytes:&row length:sizeof(int)];
552 [drawData appendBytes:&count length:sizeof(int)];
553 [drawData appendBytes:&bottom length:sizeof(int)];
554 [drawData appendBytes:&left length:sizeof(int)];
555 [drawData appendBytes:&right length:sizeof(int)];
557 if (left == 0 && right == gui.num_cols-1)
558 [self didChangeWholeLine];
561 - (void)drawCursorAtRow:(int)row column:(int)col shape:(int)shape
562 fraction:(int)percent color:(int)color
564 int type = DrawCursorDrawType;
565 unsigned uc = MM_COLOR(color);
567 [drawData appendBytes:&type length:sizeof(int)];
569 [drawData appendBytes:&uc length:sizeof(unsigned)];
570 [drawData appendBytes:&row length:sizeof(int)];
571 [drawData appendBytes:&col length:sizeof(int)];
572 [drawData appendBytes:&shape length:sizeof(int)];
573 [drawData appendBytes:&percent length:sizeof(int)];
576 - (void)drawInvertedRectAtRow:(int)row column:(int)col numRows:(int)nr
577 numColumns:(int)nc invert:(int)invert
579 int type = DrawInvertedRectDrawType;
580 [drawData appendBytes:&type length:sizeof(int)];
582 [drawData appendBytes:&row length:sizeof(int)];
583 [drawData appendBytes:&col length:sizeof(int)];
584 [drawData appendBytes:&nr length:sizeof(int)];
585 [drawData appendBytes:&nc length:sizeof(int)];
586 [drawData appendBytes:&invert length:sizeof(int)];
591 // Keep running the run-loop until there is no more input to process.
592 while (CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0, true)
593 == kCFRunLoopRunHandledSource)
597 - (void)flushQueue:(BOOL)force
599 // NOTE: This variable allows for better control over when the queue is
600 // flushed. It can be set to YES at the beginning of a sequence of calls
601 // that may potentially add items to the queue, and then restored back to
603 if (flushDisabled) return;
605 if ([drawData length] > 0) {
606 // HACK! Detect changes to 'guifontwide'.
607 if (gui.wide_font != oldWideFont) {
608 gui_mch_free_font(oldWideFont);
609 oldWideFont = gui_mch_retain_font(gui.wide_font);
610 [self setFont:oldWideFont wide:YES];
613 int type = SetCursorPosDrawType;
614 [drawData appendBytes:&type length:sizeof(type)];
615 [drawData appendBytes:&gui.row length:sizeof(gui.row)];
616 [drawData appendBytes:&gui.col length:sizeof(gui.col)];
618 [self queueMessage:BatchDrawMsgID data:[drawData copy]];
619 [self clearDrawData];
622 if ([outputQueue count] > 0) {
623 [self insertVimStateMessage];
626 ASLogDebug(@"Flushing queue: %@",
627 debugStringForMessageQueue(outputQueue));
628 [appProxy processInput:outputQueue forIdentifier:identifier];
630 @catch (NSException *ex) {
631 ASLogDebug(@"processInput:forIdentifer failed: reason=%@", ex);
632 if (![connection isValid]) {
633 ASLogDebug(@"Connection is invalid, exit now!");
634 ASLogDebug(@"waitForAck=%d got_int=%d", waitForAck, got_int);
639 [outputQueue removeAllObjects];
643 - (BOOL)waitForInput:(int)milliseconds
645 // Return NO if we timed out waiting for input, otherwise return YES.
646 BOOL inputReceived = NO;
648 // Only start the run loop if the input queue is empty, otherwise process
649 // the input first so that the input on queue isn't delayed.
650 if ([inputQueue count]) {
653 // Wait for the specified amount of time, unless 'milliseconds' is
654 // negative in which case we wait "forever" (1e6 seconds translates to
655 // approximately 11 days).
656 CFTimeInterval dt = (milliseconds >= 0 ? .001*milliseconds : 1e6);
658 while (CFRunLoopRunInMode(kCFRunLoopDefaultMode, dt, true)
659 == kCFRunLoopRunHandledSource) {
660 // In order to ensure that all input on the run-loop has been
661 // processed we set the timeout to 0 and keep processing until the
662 // run-loop times out.
668 // The above calls may have placed messages on the input queue so process
669 // it now. This call may enter a blocking loop.
670 if ([inputQueue count] > 0)
671 [self processInputQueue];
673 return inputReceived;
678 // NOTE: This is called if mch_exit() is called. Since we assume here that
679 // the process has started properly, be sure to use exit() instead of
680 // mch_exit() to prematurely terminate a process (or set 'isTerminating'
683 // Make sure no connectionDidDie: notification is received now that we are
685 [[NSNotificationCenter defaultCenter] removeObserver:self];
687 // The 'isTerminating' flag indicates that the frontend is also exiting so
688 // there is no need to flush any more output since the frontend won't look
690 if (!isTerminating && [connection isValid]) {
692 // Flush the entire queue in case a VimLeave autocommand added
693 // something to the queue.
694 [self queueMessage:CloseWindowMsgID data:nil];
695 ASLogDebug(@"Flush output queue before exit: %@",
696 debugStringForMessageQueue(outputQueue));
697 [appProxy processInput:outputQueue forIdentifier:identifier];
699 @catch (NSException *ex) {
700 ASLogDebug(@"CloseWindowMsgID send failed: reason=%@", ex);
703 // NOTE: If Cmd-w was pressed to close the window the menu is briefly
704 // highlighted and during this pause the frontend won't receive any DO
705 // messages. If the Vim process exits before this highlighting has
706 // finished Cocoa will emit the following error message:
707 // *** -[NSMachPort handlePortMessage:]: dropping incoming DO message
708 // because the connection or ports are invalid
709 // To avoid this warning we delay here. If the warning still appears
710 // this delay may need to be increased.
714 #ifdef MAC_CLIENTSERVER
715 // The default connection is used for the client/server code.
716 if (vimServerConnection) {
717 [vimServerConnection setRootObject:nil];
718 [vimServerConnection invalidate];
723 - (void)selectTab:(int)index
726 NSData *data = [NSData dataWithBytes:&index length:sizeof(int)];
727 [self queueMessage:SelectTabMsgID data:data];
732 NSMutableData *data = [NSMutableData data];
734 int idx = tabpage_index(curtab) - 1;
735 [data appendBytes:&idx length:sizeof(int)];
738 for (tp = first_tabpage; tp != NULL; tp = tp->tp_next) {
739 // Count the number of windows in the tabpage.
740 //win_T *wp = tp->tp_firstwin;
742 //for (wincount = 0; wp != NULL; wp = wp->w_next, ++wincount);
743 //[data appendBytes:&wincount length:sizeof(int)];
745 int tabProp = MMTabInfoCount;
746 [data appendBytes:&tabProp length:sizeof(int)];
747 for (tabProp = MMTabLabel; tabProp < MMTabInfoCount; ++tabProp) {
748 // This function puts the label of the tab in the global 'NameBuff'.
749 get_tabline_label(tp, (tabProp == MMTabToolTip));
750 NSString *s = [NSString stringWithVimString:NameBuff];
751 int len = [s lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
755 [data appendBytes:&len length:sizeof(int)];
757 [data appendBytes:[s UTF8String] length:len];
761 [self queueMessage:UpdateTabBarMsgID data:data];
764 - (BOOL)tabBarVisible
766 return tabBarVisible;
769 - (void)showTabBar:(BOOL)enable
771 tabBarVisible = enable;
773 int msgid = enable ? ShowTabBarMsgID : HideTabBarMsgID;
774 [self queueMessage:msgid data:nil];
777 - (void)setRows:(int)rows columns:(int)cols
779 int dim[] = { rows, cols };
780 NSData *data = [NSData dataWithBytes:&dim length:2*sizeof(int)];
782 [self queueMessage:SetTextDimensionsMsgID data:data];
785 - (void)setWindowTitle:(char *)title
787 NSMutableData *data = [NSMutableData data];
788 int len = strlen(title);
789 if (len <= 0) return;
791 [data appendBytes:&len length:sizeof(int)];
792 [data appendBytes:title length:len];
794 [self queueMessage:SetWindowTitleMsgID data:data];
797 - (void)setDocumentFilename:(char *)filename
799 NSMutableData *data = [NSMutableData data];
800 int len = filename ? strlen(filename) : 0;
802 [data appendBytes:&len length:sizeof(int)];
804 [data appendBytes:filename length:len];
806 [self queueMessage:SetDocumentFilenameMsgID data:data];
809 - (char *)browseForFileWithAttributes:(NSDictionary *)attr
813 [self queueMessage:BrowseForFileMsgID properties:attr];
814 [self flushQueue:YES];
817 [self waitForDialogReturn];
819 if (dialogReturn && [dialogReturn isKindOfClass:[NSString class]])
820 s = [dialogReturn vimStringSave];
822 [dialogReturn release]; dialogReturn = nil;
824 @catch (NSException *ex) {
825 ASLogDebug(@"Exception: reason=%@", ex);
831 - (oneway void)setDialogReturn:(in bycopy id)obj
833 ASLogDebug(@"%@", obj);
835 // NOTE: This is called by
836 // - [MMVimController panelDidEnd:::], and
837 // - [MMVimController alertDidEnd:::],
838 // to indicate that a save/open panel or alert has finished.
840 // We want to distinguish between "no dialog return yet" and "dialog
841 // returned nothing". The former can be tested with dialogReturn == nil,
842 // the latter with dialogReturn == [NSNull null].
843 if (!obj) obj = [NSNull null];
845 if (obj != dialogReturn) {
846 [dialogReturn release];
847 dialogReturn = [obj retain];
851 - (int)showDialogWithAttributes:(NSDictionary *)attr textField:(char *)txtfield
855 [self queueMessage:ShowDialogMsgID properties:attr];
856 [self flushQueue:YES];
859 [self waitForDialogReturn];
861 if (dialogReturn && [dialogReturn isKindOfClass:[NSArray class]]
862 && [dialogReturn count]) {
863 retval = [[dialogReturn objectAtIndex:0] intValue];
864 if (txtfield && [dialogReturn count] > 1) {
865 NSString *retString = [dialogReturn objectAtIndex:1];
866 char_u *ret = (char_u*)[retString UTF8String];
868 ret = CONVERT_FROM_UTF8(ret);
870 vim_strncpy((char_u*)txtfield, ret, IOSIZE - 1);
872 CONVERT_FROM_UTF8_FREE(ret);
877 [dialogReturn release]; dialogReturn = nil;
879 @catch (NSException *ex) {
880 ASLogDebug(@"Exception: reason=%@", ex);
886 - (void)showToolbar:(int)enable flags:(int)flags
888 NSMutableData *data = [NSMutableData data];
890 [data appendBytes:&enable length:sizeof(int)];
891 [data appendBytes:&flags length:sizeof(int)];
893 [self queueMessage:ShowToolbarMsgID data:data];
896 - (void)createScrollbarWithIdentifier:(int32_t)ident type:(int)type
898 NSMutableData *data = [NSMutableData data];
900 [data appendBytes:&ident length:sizeof(int32_t)];
901 [data appendBytes:&type length:sizeof(int)];
903 [self queueMessage:CreateScrollbarMsgID data:data];
906 - (void)destroyScrollbarWithIdentifier:(int32_t)ident
908 NSMutableData *data = [NSMutableData data];
909 [data appendBytes:&ident length:sizeof(int32_t)];
911 [self queueMessage:DestroyScrollbarMsgID data:data];
914 - (void)showScrollbarWithIdentifier:(int32_t)ident state:(int)visible
916 NSMutableData *data = [NSMutableData data];
918 [data appendBytes:&ident length:sizeof(int32_t)];
919 [data appendBytes:&visible length:sizeof(int)];
921 [self queueMessage:ShowScrollbarMsgID data:data];
924 - (void)setScrollbarPosition:(int)pos length:(int)len identifier:(int32_t)ident
926 NSMutableData *data = [NSMutableData data];
928 [data appendBytes:&ident length:sizeof(int32_t)];
929 [data appendBytes:&pos length:sizeof(int)];
930 [data appendBytes:&len length:sizeof(int)];
932 [self queueMessage:SetScrollbarPositionMsgID data:data];
935 - (void)setScrollbarThumbValue:(long)val size:(long)size max:(long)max
936 identifier:(int32_t)ident
938 float fval = max-size+1 > 0 ? (float)val/(max-size+1) : 0;
939 float prop = (float)size/(max+1);
940 if (fval < 0) fval = 0;
941 else if (fval > 1.0f) fval = 1.0f;
942 if (prop < 0) prop = 0;
943 else if (prop > 1.0f) prop = 1.0f;
945 NSMutableData *data = [NSMutableData data];
947 [data appendBytes:&ident length:sizeof(int32_t)];
948 [data appendBytes:&fval length:sizeof(float)];
949 [data appendBytes:&prop length:sizeof(float)];
951 [self queueMessage:SetScrollbarThumbMsgID data:data];
954 - (void)setFont:(GuiFont)font wide:(BOOL)wide
956 NSString *fontName = (NSString *)font;
958 NSArray *components = [fontName componentsSeparatedByString:@":"];
959 if ([components count] == 2) {
960 size = [[components lastObject] floatValue];
961 fontName = [components objectAtIndex:0];
964 int len = [fontName lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
965 NSMutableData *data = [NSMutableData data];
966 [data appendBytes:&size length:sizeof(float)];
967 [data appendBytes:&len length:sizeof(int)];
970 [data appendBytes:[fontName UTF8String] length:len];
972 return; // Only the wide font can be set to nothing
974 [self queueMessage:(wide ? SetWideFontMsgID : SetFontMsgID) data:data];
977 - (void)executeActionWithName:(NSString *)name
979 int len = [name lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
982 NSMutableData *data = [NSMutableData data];
984 [data appendBytes:&len length:sizeof(int)];
985 [data appendBytes:[name UTF8String] length:len];
987 [self queueMessage:ExecuteActionMsgID data:data];
991 - (void)setMouseShape:(int)shape
993 NSMutableData *data = [NSMutableData data];
994 [data appendBytes:&shape length:sizeof(int)];
995 [self queueMessage:SetMouseShapeMsgID data:data];
998 - (void)setBlinkWait:(int)wait on:(int)on off:(int)off
1000 // Vim specifies times in milliseconds, whereas Cocoa wants them in
1002 blinkWaitInterval = .001f*wait;
1003 blinkOnInterval = .001f*on;
1004 blinkOffInterval = .001f*off;
1010 [blinkTimer invalidate];
1011 [blinkTimer release];
1015 if (blinkWaitInterval > 0 && blinkOnInterval > 0 && blinkOffInterval > 0
1017 blinkState = MMBlinkStateOn;
1019 [[NSTimer scheduledTimerWithTimeInterval:blinkWaitInterval
1021 selector:@selector(blinkTimerFired:)
1022 userInfo:nil repeats:NO] retain];
1023 gui_update_cursor(TRUE, FALSE);
1024 [self flushQueue:YES];
1030 if (MMBlinkStateOff == blinkState) {
1031 gui_update_cursor(TRUE, FALSE);
1032 [self flushQueue:YES];
1035 blinkState = MMBlinkStateNone;
1038 - (void)adjustLinespace:(int)linespace
1040 NSMutableData *data = [NSMutableData data];
1041 [data appendBytes:&linespace length:sizeof(int)];
1042 [self queueMessage:AdjustLinespaceMsgID data:data];
1047 [self queueMessage:ActivateMsgID data:nil];
1050 - (void)setPreEditRow:(int)row column:(int)col
1052 NSMutableData *data = [NSMutableData data];
1053 [data appendBytes:&row length:sizeof(int)];
1054 [data appendBytes:&col length:sizeof(int)];
1055 [self queueMessage:SetPreEditPositionMsgID data:data];
1058 - (int)lookupColorWithKey:(NSString *)key
1060 if (!(key && [key length] > 0))
1063 NSString *stripKey = [[[[key lowercaseString]
1064 stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]
1065 componentsSeparatedByString:@" "]
1066 componentsJoinedByString:@""];
1068 if (stripKey && [stripKey length] > 0) {
1069 // First of all try to lookup key in the color dictionary; note that
1070 // all keys in this dictionary are lowercase with no whitespace.
1071 id obj = [colorDict objectForKey:stripKey];
1072 if (obj) return [obj intValue];
1074 // The key was not in the dictionary; is it perhaps of the form
1076 if ([stripKey length] > 1 && [stripKey characterAtIndex:0] == '#') {
1077 NSScanner *scanner = [NSScanner scannerWithString:stripKey];
1078 [scanner setScanLocation:1];
1080 if ([scanner scanHexInt:&hex]) {
1085 // As a last resort, check if it is one of the system defined colors.
1086 // The keys in this dictionary are also lowercase with no whitespace.
1087 obj = [sysColorDict objectForKey:stripKey];
1089 NSColor *col = [NSColor performSelector:NSSelectorFromString(obj)];
1092 col = [col colorUsingColorSpaceName:NSCalibratedRGBColorSpace];
1093 [col getRed:&r green:&g blue:&b alpha:&a];
1094 return (((int)(r*255+.5f) & 0xff) << 16)
1095 + (((int)(g*255+.5f) & 0xff) << 8)
1096 + ((int)(b*255+.5f) & 0xff);
1101 ASLogNotice(@"No color with key %@ found.", stripKey);
1105 - (BOOL)hasSpecialKeyWithValue:(char_u *)value
1108 for (i = 0; special_keys[i].key_sym != 0; i++) {
1109 if (value[0] == special_keys[i].vim_code0
1110 && value[1] == special_keys[i].vim_code1)
1117 - (void)enterFullscreen:(int)fuoptions background:(int)bg
1119 NSMutableData *data = [NSMutableData data];
1120 [data appendBytes:&fuoptions length:sizeof(int)];
1122 [data appendBytes:&bg length:sizeof(int)];
1123 [self queueMessage:EnterFullscreenMsgID data:data];
1126 - (void)leaveFullscreen
1128 [self queueMessage:LeaveFullscreenMsgID data:nil];
1131 - (void)setFullscreenBackgroundColor:(int)color
1133 NSMutableData *data = [NSMutableData data];
1134 color = MM_COLOR(color);
1135 [data appendBytes:&color length:sizeof(int)];
1137 [self queueMessage:SetFullscreenColorMsgID data:data];
1140 - (void)setAntialias:(BOOL)antialias
1142 int msgid = antialias ? EnableAntialiasMsgID : DisableAntialiasMsgID;
1144 [self queueMessage:msgid data:nil];
1147 - (void)updateModifiedFlag
1149 // Notify MacVim if _any_ buffer has changed from unmodified to modified or
1151 int msgid = [self checkForModifiedBuffers]
1152 ? BuffersModifiedMsgID : BuffersNotModifiedMsgID;
1154 [self queueMessage:msgid data:nil];
1157 - (oneway void)processInput:(int)msgid data:(in bycopy NSData *)data
1159 // Look for Ctrl-C immediately instead of waiting until the input queue is
1160 // processed since that only happens in waitForInput: (and Vim regularly
1161 // checks for Ctrl-C in between waiting for input). Note that the flag
1162 // ctrl_c_interrupts is 0 e.g. when the user has mappings to something like
1163 // <C-c>g. Also it seems the flag intr_char is 0 when MacVim was started
1164 // from Finder whereas it is 0x03 (= Ctrl_C) when started from Terminal.
1166 // Similarly, TerminateNowMsgID must be checked immediately otherwise code
1167 // which waits on the run loop will fail to detect this message (e.g. in
1168 // waitForConnectionAcknowledgement).
1170 if (KeyDownMsgID == msgid && data != nil && ctrl_c_interrupts) {
1171 const void *bytes = [data bytes];
1172 /*unsigned mods = *((unsigned*)bytes);*/ bytes += sizeof(unsigned);
1173 /*unsigned code = *((unsigned*)bytes);*/ bytes += sizeof(unsigned);
1174 unsigned len = *((unsigned*)bytes); bytes += sizeof(unsigned);
1176 char_u *str = (char_u*)bytes;
1177 if (str[0] == Ctrl_C || (str[0] == intr_char && intr_char != 0)) {
1178 ASLogDebug(@"Got INT, str[0]=%#x ctrl_c_interrupts=%d "
1179 "intr_char=%#x", str[0], ctrl_c_interrupts, intr_char);
1181 [inputQueue removeAllObjects];
1185 } else if (TerminateNowMsgID == msgid) {
1186 // Terminate immediately (the frontend is about to quit or this process
1187 // was aborted). Don't preserve modified files since the user would
1188 // already have been presented with a dialog warning if there were any
1189 // modified files when we get here.
1190 isTerminating = YES;
1195 // Remove all previous instances of this message from the input queue, else
1196 // the input queue may fill up as a result of Vim not being able to keep up
1197 // with the speed at which new messages are received.
1198 // Keyboard input is never dropped, unless the input represents an
1199 // auto-repeated key.
1201 BOOL isKeyRepeat = NO;
1202 BOOL isKeyboardInput = NO;
1204 if (data && KeyDownMsgID == msgid) {
1205 isKeyboardInput = YES;
1207 // The lowest bit of the first int is set if this key is a repeat.
1208 int flags = *((int*)[data bytes]);
1213 // Keyboard input is not removed from the queue; repeats are ignored if
1214 // there already is keyboard input on the input queue.
1215 if (isKeyRepeat || !isKeyboardInput) {
1216 int i, count = [inputQueue count];
1217 for (i = 1; i < count; i+=2) {
1218 if ([[inputQueue objectAtIndex:i-1] intValue] == msgid) {
1222 [inputQueue removeObjectAtIndex:i];
1223 [inputQueue removeObjectAtIndex:i-1];
1229 [inputQueue addObject:[NSNumber numberWithInt:msgid]];
1230 [inputQueue addObject:(data ? (id)data : [NSNull null])];
1233 - (id)evaluateExpressionCocoa:(in bycopy NSString *)expr
1234 errorString:(out bycopy NSString **)errstr
1236 return evalExprCocoa(expr, errstr);
1240 - (NSString *)evaluateExpression:(in bycopy NSString *)expr
1242 NSString *eval = nil;
1243 char_u *s = (char_u*)[expr UTF8String];
1246 s = CONVERT_FROM_UTF8(s);
1249 char_u *res = eval_client_expr_to_string(s);
1252 CONVERT_FROM_UTF8_FREE(s);
1258 s = CONVERT_TO_UTF8(s);
1260 eval = [NSString stringWithUTF8String:(char*)s];
1262 CONVERT_TO_UTF8_FREE(s);
1270 - (BOOL)starRegisterToPasteboard:(byref NSPasteboard *)pboard
1272 // TODO: This method should share code with clip_mch_request_selection().
1274 if (VIsual_active && (State & NORMAL) && clip_star.available) {
1275 // If there is no pasteboard, return YES to indicate that there is text
1280 clip_copy_selection();
1282 // Get the text to put on the pasteboard.
1283 long_u llen = 0; char_u *str = 0;
1284 int type = clip_convert_selection(&str, &llen, &clip_star);
1288 // TODO: Avoid overflow.
1289 int len = (int)llen;
1291 if (output_conv.vc_type != CONV_NONE) {
1292 char_u *conv_str = string_convert(&output_conv, str, &len);
1300 NSString *string = [[NSString alloc]
1301 initWithBytes:str length:len encoding:NSUTF8StringEncoding];
1303 NSArray *types = [NSArray arrayWithObject:NSStringPboardType];
1304 [pboard declareTypes:types owner:nil];
1305 BOOL ok = [pboard setString:string forType:NSStringPboardType];
1316 - (oneway void)addReply:(in bycopy NSString *)reply
1317 server:(in byref id <MMVimServerProtocol>)server
1319 ASLogDebug(@"reply=%@ server=%@", reply, (id)server);
1321 // Replies might come at any time and in any order so we keep them in an
1322 // array inside a dictionary with the send port used as key.
1324 NSConnection *conn = [(NSDistantObject*)server connectionForProxy];
1325 // HACK! Assume connection uses mach ports.
1326 int port = [(NSMachPort*)[conn sendPort] machPort];
1327 NSNumber *key = [NSNumber numberWithInt:port];
1329 NSMutableArray *replies = [serverReplyDict objectForKey:key];
1331 replies = [NSMutableArray array];
1332 [serverReplyDict setObject:replies forKey:key];
1335 [replies addObject:reply];
1338 - (void)addInput:(in bycopy NSString *)input
1339 client:(in byref id <MMVimClientProtocol>)client
1341 ASLogDebug(@"input=%@ client=%@", input, (id)client);
1343 // NOTE: We don't call addInput: here because it differs from
1344 // server_to_input_buf() in that it always sets the 'silent' flag and we
1345 // don't want the MacVim client/server code to behave differently from
1347 char_u *s = [input vimStringSave];
1348 server_to_input_buf(s);
1351 [self addClient:(id)client];
1354 - (NSString *)evaluateExpression:(in bycopy NSString *)expr
1355 client:(in byref id <MMVimClientProtocol>)client
1357 [self addClient:(id)client];
1358 return [self evaluateExpression:expr];
1361 - (void)registerServerWithName:(NSString *)name
1363 NSString *svrName = name;
1366 if (vimServerConnection) // Paranoia check, should always be nil
1367 [vimServerConnection release];
1369 vimServerConnection = [[NSConnection alloc]
1370 initWithReceivePort:[NSPort port]
1373 for (i = 0; i < MMServerMax; ++i) {
1374 NSString *connName = [self connectionNameFromServerName:svrName];
1376 if ([vimServerConnection registerName:connName]) {
1377 ASLogInfo(@"Registered server with name: %@", svrName);
1379 // TODO: Set request/reply time-outs to something else?
1381 // Don't wait for requests (time-out means that the message is
1383 [vimServerConnection setRequestTimeout:0];
1384 //[vimServerConnection setReplyTimeout:MMReplyTimeout];
1385 [vimServerConnection setRootObject:self];
1387 // NOTE: 'serverName' is a global variable
1388 serverName = [svrName vimStringSave];
1390 set_vim_var_string(VV_SEND_SERVER, serverName, -1);
1393 need_maketitle = TRUE;
1395 [self queueMessage:SetServerNameMsgID
1396 data:[svrName dataUsingEncoding:NSUTF8StringEncoding]];
1400 svrName = [NSString stringWithFormat:@"%@%d", name, i+1];
1404 - (BOOL)sendToServer:(NSString *)name string:(NSString *)string
1405 reply:(char_u **)reply port:(int *)port expression:(BOOL)expr
1408 // NOTE: If 'name' equals 'serverName' then the request is local (client
1409 // and server are the same). This case is not handled separately, so a
1410 // connection will be set up anyway (this simplifies the code).
1412 NSConnection *conn = [self connectionForServerName:name];
1415 char_u *s = (char_u*)[name UTF8String];
1417 s = CONVERT_FROM_UTF8(s);
1419 EMSG2(_(e_noserver), s);
1421 CONVERT_FROM_UTF8_FREE(s);
1428 // HACK! Assume connection uses mach ports.
1429 *port = [(NSMachPort*)[conn sendPort] machPort];
1432 id proxy = [conn rootProxy];
1433 [proxy setProtocolForProxy:@protocol(MMVimServerProtocol)];
1437 NSString *eval = [proxy evaluateExpression:string client:self];
1440 *reply = [eval vimStringSave];
1442 *reply = vim_strsave((char_u*)_(e_invexprmsg));
1449 [proxy addInput:string client:self];
1452 @catch (NSException *ex) {
1453 ASLogDebug(@"Exception: reason=%@", ex);
1460 - (NSArray *)serverList
1462 NSArray *list = nil;
1464 if ([self connection]) {
1465 id proxy = [connection rootProxy];
1466 [proxy setProtocolForProxy:@protocol(MMAppProtocol)];
1469 list = [proxy serverList];
1471 @catch (NSException *ex) {
1472 ASLogDebug(@"serverList failed: reason=%@", ex);
1475 // We get here if a --remote flag is used before MacVim has started.
1476 ASLogInfo(@"No connection to MacVim, server listing not possible.");
1482 - (NSString *)peekForReplyOnPort:(int)port
1484 ASLogDebug(@"port=%d", port);
1486 NSNumber *key = [NSNumber numberWithInt:port];
1487 NSMutableArray *replies = [serverReplyDict objectForKey:key];
1488 if (replies && [replies count]) {
1489 ASLogDebug(@" %d replies, topmost is: %@", [replies count],
1490 [replies objectAtIndex:0]);
1491 return [replies objectAtIndex:0];
1494 ASLogDebug(@" No replies");
1498 - (NSString *)waitForReplyOnPort:(int)port
1500 ASLogDebug(@"port=%d", port);
1502 NSConnection *conn = [self connectionForServerPort:port];
1506 NSNumber *key = [NSNumber numberWithInt:port];
1507 NSMutableArray *replies = nil;
1508 NSString *reply = nil;
1510 // Wait for reply as long as the connection to the server is valid (unless
1511 // user interrupts wait with Ctrl-C).
1512 while (!got_int && [conn isValid] &&
1513 !(replies = [serverReplyDict objectForKey:key])) {
1514 [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
1515 beforeDate:[NSDate distantFuture]];
1519 if ([replies count] > 0) {
1520 reply = [[replies objectAtIndex:0] retain];
1521 ASLogDebug(@" Got reply: %@", reply);
1522 [replies removeObjectAtIndex:0];
1523 [reply autorelease];
1526 if ([replies count] == 0)
1527 [serverReplyDict removeObjectForKey:key];
1533 - (BOOL)sendReply:(NSString *)reply toPort:(int)port
1535 id client = [clientProxyDict objectForKey:[NSNumber numberWithInt:port]];
1538 ASLogDebug(@"reply=%@ port=%d", reply, port);
1539 [client addReply:reply server:self];
1542 @catch (NSException *ex) {
1543 ASLogDebug(@"addReply:server: failed: reason=%@", ex);
1546 ASLogNotice(@"server2client failed; no client with id %d", port);
1557 - (void)setWaitForAck:(BOOL)yn
1562 - (void)waitForConnectionAcknowledgement
1564 if (!waitForAck) return;
1566 while (waitForAck && !got_int && [connection isValid]) {
1567 [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
1568 beforeDate:[NSDate distantFuture]];
1569 ASLogDebug(@" waitForAck=%d got_int=%d isValid=%d",
1570 waitForAck, got_int, [connection isValid]);
1574 ASLogDebug(@"Never received a connection acknowledgement");
1575 [[NSNotificationCenter defaultCenter] removeObserver:self];
1576 [appProxy release]; appProxy = nil;
1578 // NOTE: We intentionally do not call mch_exit() since this in turn
1579 // will lead to -[MMBackend exit] getting called which we want to
1584 ASLogInfo(@"Connection acknowledgement received");
1585 [self processInputQueue];
1588 - (oneway void)acknowledgeConnection
1599 - (void)setImState:(BOOL)activated
1601 imState = activated;
1604 static void netbeansReadCallback(CFSocketRef s,
1605 CFSocketCallBackType callbackType,
1610 // NetBeans socket is readable.
1611 [[MMBackend sharedInstance] messageFromNetbeans];
1614 - (void)messageFromNetbeans
1616 [inputQueue addObject:[NSNumber numberWithInt:NetBeansMsgID]];
1617 [inputQueue addObject:[NSNull null]];
1620 - (void)setNetbeansSocket:(int)socket
1622 if (netbeansSocket) {
1623 CFRelease(netbeansSocket);
1624 netbeansSocket = NULL;
1627 if (netbeansRunLoopSource) {
1628 CFRunLoopSourceInvalidate(netbeansRunLoopSource);
1629 netbeansRunLoopSource = NULL;
1635 // Tell CFRunLoop that we are interested in NetBeans socket input.
1636 netbeansSocket = CFSocketCreateWithNative(kCFAllocatorDefault,
1638 kCFSocketReadCallBack,
1639 &netbeansReadCallback,
1641 netbeansRunLoopSource = CFSocketCreateRunLoopSource(NULL,
1644 CFRunLoopAddSource(CFRunLoopGetCurrent(),
1645 netbeansRunLoopSource,
1646 kCFRunLoopCommonModes);
1653 @implementation MMBackend (Private)
1655 - (void)clearDrawData
1657 [drawData setLength:0];
1658 numWholeLineChanges = offsetForDrawDataPrune = 0;
1661 - (void)didChangeWholeLine
1663 // It may happen that draw queue is filled up with lots of changes that
1664 // affect a whole row. If the number of such changes equals twice the
1665 // number of visible rows then we can prune some commands off the queue.
1667 // NOTE: If we don't perform this pruning the draw queue may grow
1668 // indefinitely if Vim were to repeatedly send draw commands without ever
1669 // waiting for new input (that's when the draw queue is flushed). The one
1670 // instance I know where this can happen is when a command is executed in
1671 // the shell (think ":grep" with thousands of matches).
1673 ++numWholeLineChanges;
1674 if (numWholeLineChanges == gui.num_rows) {
1675 // Remember the offset to prune up to.
1676 offsetForDrawDataPrune = [drawData length];
1677 } else if (numWholeLineChanges == 2*gui.num_rows) {
1678 // Delete all the unnecessary draw commands.
1679 NSMutableData *d = [[NSMutableData alloc]
1680 initWithBytes:[drawData bytes] + offsetForDrawDataPrune
1681 length:[drawData length] - offsetForDrawDataPrune];
1682 offsetForDrawDataPrune = [d length];
1683 numWholeLineChanges -= gui.num_rows;
1689 - (void)waitForDialogReturn
1691 // Keep processing the run loop until a dialog returns. To avoid getting
1692 // stuck in an endless loop (could happen if the setDialogReturn: message
1693 // was lost) we also do some paranoia checks.
1695 // Note that in Cocoa the user can still resize windows and select menu
1696 // items while a sheet is being displayed, so we can't just wait for the
1697 // first message to arrive and assume that is the setDialogReturn: call.
1699 while (nil == dialogReturn && !got_int && [connection isValid])
1700 [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
1701 beforeDate:[NSDate distantFuture]];
1703 // Search for any resize messages on the input queue. All other messages
1704 // on the input queue are dropped. The reason why we single out resize
1705 // messages is because the user may have resized the window while a sheet
1707 int i, count = [inputQueue count];
1709 id textDimData = nil;
1711 for (i = count-2; i >= 0; i -= 2) {
1712 int msgid = [[inputQueue objectAtIndex:i] intValue];
1713 if (SetTextDimensionsMsgID == msgid) {
1714 textDimData = [[inputQueue objectAtIndex:i+1] retain];
1720 [inputQueue removeAllObjects];
1723 [inputQueue addObject:
1724 [NSNumber numberWithInt:SetTextDimensionsMsgID]];
1725 [inputQueue addObject:textDimData];
1726 [textDimData release];
1731 - (void)insertVimStateMessage
1733 // NOTE: This is the place to add Vim state that needs to be accessed from
1734 // MacVim. Do not add state that could potentially require lots of memory
1735 // since this message gets sent each time the output queue is forcibly
1736 // flushed (e.g. storing the currently selected text would be a bad idea).
1737 // We take this approach of "pushing" the state to MacVim to avoid having
1738 // to make synchronous calls from MacVim to Vim in order to get state.
1740 BOOL mmta = curbuf ? curbuf->b_p_mmta : NO;
1741 int numTabs = tabpage_index(NULL) - 1;
1745 NSDictionary *vimState = [NSDictionary dictionaryWithObjectsAndKeys:
1746 [[NSFileManager defaultManager] currentDirectoryPath], @"pwd",
1747 [NSNumber numberWithInt:p_mh], @"p_mh",
1748 [NSNumber numberWithBool:mmta], @"p_mmta",
1749 [NSNumber numberWithInt:numTabs], @"numTabs",
1752 // Put the state before all other messages.
1753 int msgid = SetVimStateMsgID;
1754 [outputQueue insertObject:[vimState dictionaryAsData] atIndex:0];
1755 [outputQueue insertObject:[NSData dataWithBytes:&msgid length:sizeof(int)]
1759 - (void)processInputQueue
1761 if ([inputQueue count] == 0) return;
1763 // NOTE: One of the input events may cause this method to be called
1764 // recursively, so copy the input queue to a local variable and clear the
1765 // queue before starting to process input events (otherwise we could get
1766 // stuck in an endless loop).
1767 NSArray *q = [inputQueue copy];
1768 unsigned i, count = [q count];
1770 [inputQueue removeAllObjects];
1772 for (i = 1; i < count; i+=2) {
1773 int msgid = [[q objectAtIndex:i-1] intValue];
1774 id data = [q objectAtIndex:i];
1775 if ([data isEqual:[NSNull null]])
1778 ASLogDebug(@"(%d) %s", i, MessageStrings[msgid]);
1779 [self handleInputEvent:msgid data:data];
1786 - (void)handleInputEvent:(int)msgid data:(NSData *)data
1788 if (KeyDownMsgID == msgid) {
1790 const void *bytes = [data bytes];
1791 unsigned mods = *((unsigned*)bytes); bytes += sizeof(unsigned);
1792 unsigned code = *((unsigned*)bytes); bytes += sizeof(unsigned);
1793 unsigned len = *((unsigned*)bytes); bytes += sizeof(unsigned);
1794 NSString *key = [[NSString alloc] initWithBytes:bytes
1796 encoding:NSUTF8StringEncoding];
1797 mods = eventModifierFlagsToVimModMask(mods);
1799 [self doKeyDown:key keyCode:code modifiers:mods];
1801 } else if (ScrollWheelMsgID == msgid) {
1803 const void *bytes = [data bytes];
1805 int row = *((int*)bytes); bytes += sizeof(int);
1806 int col = *((int*)bytes); bytes += sizeof(int);
1807 int flags = *((int*)bytes); bytes += sizeof(int);
1808 float dy = *((float*)bytes); bytes += sizeof(float);
1810 int button = MOUSE_5;
1811 if (dy > 0) button = MOUSE_4;
1813 flags = eventModifierFlagsToVimMouseModMask(flags);
1815 int numLines = (int)round(dy);
1816 if (numLines < 0) numLines = -numLines;
1817 if (numLines == 0) numLines = 1;
1819 #ifdef FEAT_GUI_SCROLL_WHEEL_FORCE
1820 gui.scroll_wheel_force = numLines;
1823 gui_send_mouse_event(button, col, row, NO, flags);
1824 } else if (MouseDownMsgID == msgid) {
1826 const void *bytes = [data bytes];
1828 int row = *((int*)bytes); bytes += sizeof(int);
1829 int col = *((int*)bytes); bytes += sizeof(int);
1830 int button = *((int*)bytes); bytes += sizeof(int);
1831 int flags = *((int*)bytes); bytes += sizeof(int);
1832 int count = *((int*)bytes); bytes += sizeof(int);
1834 button = eventButtonNumberToVimMouseButton(button);
1836 flags = eventModifierFlagsToVimMouseModMask(flags);
1837 gui_send_mouse_event(button, col, row, count>1, flags);
1839 } else if (MouseUpMsgID == msgid) {
1841 const void *bytes = [data bytes];
1843 int row = *((int*)bytes); bytes += sizeof(int);
1844 int col = *((int*)bytes); bytes += sizeof(int);
1845 int flags = *((int*)bytes); bytes += sizeof(int);
1847 flags = eventModifierFlagsToVimMouseModMask(flags);
1849 gui_send_mouse_event(MOUSE_RELEASE, col, row, NO, flags);
1850 } else if (MouseDraggedMsgID == msgid) {
1852 const void *bytes = [data bytes];
1854 int row = *((int*)bytes); bytes += sizeof(int);
1855 int col = *((int*)bytes); bytes += sizeof(int);
1856 int flags = *((int*)bytes); bytes += sizeof(int);
1858 flags = eventModifierFlagsToVimMouseModMask(flags);
1860 gui_send_mouse_event(MOUSE_DRAG, col, row, NO, flags);
1861 } else if (MouseMovedMsgID == msgid) {
1862 const void *bytes = [data bytes];
1863 int row = *((int*)bytes); bytes += sizeof(int);
1864 int col = *((int*)bytes); bytes += sizeof(int);
1866 gui_mouse_moved(col, row);
1867 } else if (AddInputMsgID == msgid) {
1868 NSString *string = [[NSString alloc] initWithData:data
1869 encoding:NSUTF8StringEncoding];
1871 [self addInput:string];
1874 } else if (SelectTabMsgID == msgid) {
1876 const void *bytes = [data bytes];
1877 int idx = *((int*)bytes) + 1;
1878 send_tabline_event(idx);
1879 } else if (CloseTabMsgID == msgid) {
1881 const void *bytes = [data bytes];
1882 int idx = *((int*)bytes) + 1;
1883 send_tabline_menu_event(idx, TABLINE_MENU_CLOSE);
1884 } else if (AddNewTabMsgID == msgid) {
1885 send_tabline_menu_event(0, TABLINE_MENU_NEW);
1886 } else if (DraggedTabMsgID == msgid) {
1888 const void *bytes = [data bytes];
1889 // NOTE! The destination index is 0 based, so do not add 1 to make it 1
1891 int idx = *((int*)bytes);
1894 } else if (SetTextDimensionsMsgID == msgid || LiveResizeMsgID == msgid
1895 || SetTextRowsMsgID == msgid || SetTextColumnsMsgID == msgid) {
1897 const void *bytes = [data bytes];
1899 if (SetTextColumnsMsgID != msgid) {
1900 rows = *((int*)bytes); bytes += sizeof(int);
1903 if (SetTextRowsMsgID != msgid) {
1904 cols = *((int*)bytes); bytes += sizeof(int);
1908 if (SetTextRowsMsgID == msgid || SetTextColumnsMsgID == msgid) {
1909 int dim[2] = { rows, cols };
1910 d = [NSData dataWithBytes:dim length:2*sizeof(int)];
1911 msgid = SetTextDimensionsReplyMsgID;
1914 if (SetTextDimensionsMsgID == msgid)
1915 msgid = SetTextDimensionsReplyMsgID;
1917 // NOTE! Vim doesn't call gui_mch_set_shellsize() after
1918 // gui_resize_shell(), so we have to manually set the rows and columns
1919 // here since MacVim doesn't change the rows and columns to avoid
1920 // inconsistent states between Vim and MacVim. The message sent back
1921 // indicates that it is a reply to a message that originated in MacVim
1922 // since we need to be able to determine where a message originated.
1923 [self queueMessage:msgid data:d];
1925 gui_resize_shell(cols, rows);
1926 } else if (ExecuteMenuMsgID == msgid) {
1927 NSDictionary *attrs = [NSDictionary dictionaryWithData:data];
1929 NSArray *desc = [attrs objectForKey:@"descriptor"];
1930 vimmenu_T *menu = menu_for_descriptor(desc);
1934 } else if (ToggleToolbarMsgID == msgid) {
1935 [self handleToggleToolbar];
1936 } else if (ScrollbarEventMsgID == msgid) {
1937 [self handleScrollbarEvent:data];
1938 } else if (SetFontMsgID == msgid) {
1939 [self handleSetFont:data];
1940 } else if (VimShouldCloseMsgID == msgid) {
1942 } else if (DropFilesMsgID == msgid) {
1943 [self handleDropFiles:data];
1944 } else if (DropStringMsgID == msgid) {
1945 [self handleDropString:data];
1946 } else if (GotFocusMsgID == msgid) {
1948 [self focusChange:YES];
1949 } else if (LostFocusMsgID == msgid) {
1951 [self focusChange:NO];
1952 } else if (SetMouseShapeMsgID == msgid) {
1953 const void *bytes = [data bytes];
1954 int shape = *((int*)bytes); bytes += sizeof(int);
1955 update_mouseshape(shape);
1956 } else if (XcodeModMsgID == msgid) {
1957 [self handleXcodeMod:data];
1958 } else if (OpenWithArgumentsMsgID == msgid) {
1959 [self handleOpenWithArguments:[NSDictionary dictionaryWithData:data]];
1960 } else if (FindReplaceMsgID == msgid) {
1961 [self handleFindReplace:[NSDictionary dictionaryWithData:data]];
1962 } else if (ActivatedImMsgID == msgid) {
1963 [self setImState:YES];
1964 } else if (DeactivatedImMsgID == msgid) {
1965 [self setImState:NO];
1966 } else if (NetBeansMsgID == msgid) {
1967 #ifdef FEAT_NETBEANS_INTG
1968 messageFromNetbeansMacVim();
1970 } else if (SetMarkedTextMsgID == msgid) {
1971 [self handleMarkedText:data];
1972 } else if (ZoomMsgID == msgid) {
1974 const void *bytes = [data bytes];
1975 int rows = *((int*)bytes); bytes += sizeof(int);
1976 int cols = *((int*)bytes); bytes += sizeof(int);
1977 //int zoom = *((int*)bytes); bytes += sizeof(int);
1979 // NOTE: The frontend sends zoom messages here causing us to
1980 // immediately resize the shell and mirror the message back to the
1981 // frontend. This is done to ensure that the draw commands reach the
1982 // frontend before the window actually changes size in order to avoid
1983 // flickering. (Also see comment in SetTextDimensionsReplyMsgID
1984 // regarding resizing.)
1985 [self queueMessage:ZoomMsgID data:data];
1986 gui_resize_shell(cols, rows);
1987 } else if (SetWindowPositionMsgID == msgid) {
1989 const void *bytes = [data bytes];
1990 winposX = *((int*)bytes); bytes += sizeof(int);
1991 winposY = *((int*)bytes); bytes += sizeof(int);
1992 ASLogDebug(@"SetWindowPositionMsgID: x=%d y=%d", winposX, winposY);
1994 ASLogWarn(@"Unknown message received (msgid=%d)", msgid);
1998 - (void)doKeyDown:(NSString *)key
1999 keyCode:(unsigned)code
2002 ASLogDebug(@"key='%@' code=%#x mods=%#x length=%d", key, code, mods,
2006 char_u *str = (char_u*)[key UTF8String];
2007 int i, len = [key lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
2009 if ([self handleSpecialKey:key keyCode:code modifiers:mods])
2013 char_u *conv_str = NULL;
2014 if (input_conv.vc_type != CONV_NONE) {
2015 conv_str = string_convert(&input_conv, str, &len);
2021 if (mods & MOD_MASK_CMD) {
2022 // NOTE: For normal input (non-special, 'macmeta' off) the modifier
2023 // flags are already included in the key event. However, the Cmd key
2024 // flag is special and must always be added manually.
2025 // The Shift flag is already included in the key when the Command
2026 // key is held. The same goes for Alt, unless Ctrl is held or
2027 // 'macmeta' is set. It is important that these flags are cleared
2028 // _after_ special keys have been handled, since they should never be
2029 // cleared for special keys.
2030 mods &= ~MOD_MASK_SHIFT;
2031 if (!(mods & MOD_MASK_CTRL)) {
2032 BOOL mmta = curbuf ? curbuf->b_p_mmta : YES;
2034 mods &= ~MOD_MASK_ALT;
2037 ASLogDebug(@"add mods=%#x", mods);
2038 char_u modChars[3] = { CSI, KS_MODIFIER, mods };
2039 add_to_input_buf(modChars, 3);
2040 } else if (mods & MOD_MASK_ALT && 1 == len && str[0] < 0x80
2041 && curbuf && curbuf->b_p_mmta) {
2042 // HACK! The 'macmeta' is set so we have to handle Alt key presses
2043 // separately. Normally Alt key presses are interpreted by the
2044 // frontend but now we have to manually set the 8th bit and deal with
2045 // UTF-8 conversion.
2046 if ([self handleMacMetaKey:str[0] modifiers:mods])
2051 for (i = 0; i < len; ++i) {
2052 ASLogDebug(@"add byte [%d/%d]: %#x", i, len, str[i]);
2053 add_to_input_buf(str+i, 1);
2054 if (CSI == str[i]) {
2055 // NOTE: If the converted string contains the byte CSI, then it
2056 // must be followed by the bytes KS_EXTRA, KE_CSI or things
2058 static char_u extra[2] = { KS_EXTRA, KE_CSI };
2059 ASLogDebug(@"add KS_EXTRA, KE_CSI");
2060 add_to_input_buf(extra, 2);
2070 - (BOOL)handleSpecialKey:(NSString *)key
2071 keyCode:(unsigned)code
2075 for (i = 0; special_keys[i].key_sym != 0; i++) {
2076 if (special_keys[i].key_sym == code) {
2077 ASLogDebug(@"Special key: %#x", code);
2081 if (special_keys[i].key_sym == 0)
2084 int ikey = special_keys[i].vim_code1 == NUL ? special_keys[i].vim_code0 :
2085 TO_SPECIAL(special_keys[i].vim_code0, special_keys[i].vim_code1);
2086 ikey = simplify_key(ikey, &mods);
2093 if (IS_SPECIAL(ikey)) {
2095 chars[1] = K_SECOND(ikey);
2096 chars[2] = K_THIRD(ikey);
2098 } else if (mods & MOD_MASK_ALT && special_keys[i].vim_code1 == 0
2100 && !enc_dbcs // TODO: ? (taken from gui_gtk_x11.c)
2103 ASLogDebug(@"Alt special=%d", ikey);
2105 // NOTE: The last entries in the special_keys struct when pressed
2106 // together with Alt need to be handled separately or they will not
2108 // The following code was gleaned from gui_gtk_x11.c.
2109 mods &= ~MOD_MASK_ALT;
2110 int mkey = 0x80 | ikey;
2112 if (enc_utf8) { // TODO: What about other encodings?
2114 chars[0] = (mkey >> 6) + 0xc0;
2115 chars[1] = mkey & 0xbf;
2116 if (chars[1] == CSI) {
2117 // We end up here when ikey == ESC
2118 chars[2] = KS_EXTRA;
2131 ASLogDebug(@"Just ikey=%d", ikey);
2138 ASLogDebug(@"Adding mods to special: %d", mods);
2139 char_u modChars[3] = { CSI, KS_MODIFIER, (char_u)mods };
2140 add_to_input_buf(modChars, 3);
2143 ASLogDebug(@"Adding special (%d): %x,%x,%x", len,
2144 chars[0], chars[1], chars[2]);
2145 add_to_input_buf(chars, len);
2151 - (BOOL)handleMacMetaKey:(int)ikey modifiers:(int)mods
2153 ASLogDebug(@"ikey=%d mods=%d", ikey, mods);
2155 // This code was taken from gui_w48.c and gui_gtk_x11.c.
2157 int ch = simplify_key(ikey, &mods);
2159 // Remove the SHIFT modifier for keys where it's already included,
2160 // e.g., '(' and '*'
2161 if (ch < 0x100 && !isalpha(ch) && isprint(ch))
2162 mods &= ~MOD_MASK_SHIFT;
2164 // Interpret the ALT key as making the key META, include SHIFT, etc.
2165 ch = extract_modifiers(ch, &mods);
2171 string[len++] = CSI;
2172 string[len++] = KS_MODIFIER;
2173 string[len++] = mods;
2178 // TODO: What if 'enc' is not "utf-8"?
2179 if (enc_utf8 && (ch & 0x80)) { // convert to utf-8
2180 string[len++] = ch & 0xbf;
2181 string[len-2] = ((unsigned)ch >> 6) + 0xc0;
2182 if (string[len-1] == CSI) {
2183 string[len++] = KS_EXTRA;
2184 string[len++] = (int)KE_CSI;
2189 add_to_input_buf(string, len);
2193 - (void)queueMessage:(int)msgid data:(NSData *)data
2195 [outputQueue addObject:[NSData dataWithBytes:&msgid length:sizeof(int)]];
2197 [outputQueue addObject:data];
2199 [outputQueue addObject:[NSData data]];
2202 - (void)connectionDidDie:(NSNotification *)notification
2204 // If the main connection to MacVim is lost this means that either MacVim
2205 // has crashed or this process did not receive its termination message
2206 // properly (e.g. if the TerminateNowMsgID was dropped).
2208 // NOTE: This is not called if a Vim controller invalidates its connection.
2210 ASLogNotice(@"Main connection was lost before process had a chance "
2211 "to terminate; preserving swap files.");
2212 getout_preserve_modified(1);
2215 - (void)blinkTimerFired:(NSTimer *)timer
2217 NSTimeInterval timeInterval = 0;
2219 [blinkTimer release];
2222 if (MMBlinkStateOn == blinkState) {
2223 gui_undraw_cursor();
2224 blinkState = MMBlinkStateOff;
2225 timeInterval = blinkOffInterval;
2226 } else if (MMBlinkStateOff == blinkState) {
2227 gui_update_cursor(TRUE, FALSE);
2228 blinkState = MMBlinkStateOn;
2229 timeInterval = blinkOnInterval;
2232 if (timeInterval > 0) {
2234 [[NSTimer scheduledTimerWithTimeInterval:timeInterval target:self
2235 selector:@selector(blinkTimerFired:)
2236 userInfo:nil repeats:NO] retain];
2237 [self flushQueue:YES];
2241 - (void)focusChange:(BOOL)on
2243 gui_focus_change(on);
2246 - (void)handleToggleToolbar
2248 // If 'go' contains 'T', then remove it, else add it.
2250 char_u go[sizeof(GO_ALL)+2];
2255 p = vim_strchr(go, GO_TOOLBAR);
2259 char_u *end = go + len;
2265 go[len] = GO_TOOLBAR;
2269 set_option_value((char_u*)"guioptions", 0, go, 0);
2272 - (void)handleScrollbarEvent:(NSData *)data
2276 const void *bytes = [data bytes];
2277 int32_t ident = *((int32_t*)bytes); bytes += sizeof(int32_t);
2278 int hitPart = *((int*)bytes); bytes += sizeof(int);
2279 float fval = *((float*)bytes); bytes += sizeof(float);
2280 scrollbar_T *sb = gui_find_scrollbar(ident);
2283 scrollbar_T *sb_info = sb->wp ? &sb->wp->w_scrollbars[0] : sb;
2284 long value = sb_info->value;
2285 long size = sb_info->size;
2286 long max = sb_info->max;
2287 BOOL isStillDragging = NO;
2288 BOOL updateKnob = YES;
2291 case NSScrollerDecrementPage:
2292 value -= (size > 2 ? size - 2 : 1);
2294 case NSScrollerIncrementPage:
2295 value += (size > 2 ? size - 2 : 1);
2297 case NSScrollerDecrementLine:
2300 case NSScrollerIncrementLine:
2303 case NSScrollerKnob:
2304 isStillDragging = YES;
2306 case NSScrollerKnobSlot:
2307 value = (long)(fval * (max - size + 1));
2314 gui_drag_scrollbar(sb, value, isStillDragging);
2317 // Dragging the knob or option+clicking automatically updates
2318 // the knob position (on the actual NSScroller), so we only
2319 // need to set the knob position in the other cases.
2321 // Update both the left&right vertical scrollbars.
2322 int32_t idL = (int32_t)sb->wp->w_scrollbars[SBAR_LEFT].ident;
2323 int32_t idR = (int32_t)sb->wp->w_scrollbars[SBAR_RIGHT].ident;
2324 [self setScrollbarThumbValue:value size:size max:max
2326 [self setScrollbarThumbValue:value size:size max:max
2329 // Update the horizontal scrollbar.
2330 [self setScrollbarThumbValue:value size:size max:max
2337 - (void)handleSetFont:(NSData *)data
2341 const void *bytes = [data bytes];
2342 int pointSize = (int)*((float*)bytes); bytes += sizeof(float);
2344 unsigned len = *((unsigned*)bytes); bytes += sizeof(unsigned);
2345 NSMutableString *name = [NSMutableString stringWithUTF8String:bytes];
2348 [name appendString:[NSString stringWithFormat:@":h%d", pointSize]];
2349 char_u *s = (char_u*)[name UTF8String];
2351 unsigned wlen = *((unsigned*)bytes); bytes += sizeof(unsigned);
2354 NSMutableString *wname = [NSMutableString stringWithUTF8String:bytes];
2357 [wname appendString:[NSString stringWithFormat:@":h%d", pointSize]];
2358 ws = (char_u*)[wname UTF8String];
2362 s = CONVERT_FROM_UTF8(s);
2364 ws = CONVERT_FROM_UTF8(ws);
2368 set_option_value((char_u*)"guifont", 0, s, 0);
2370 if (ws && gui.wide_font != NOFONT) {
2371 // NOTE: This message is sent on Cmd-+/Cmd-- and as such should only
2372 // change the wide font if 'gfw' is non-empty (the frontend always has
2373 // some wide font set, even if 'gfw' is empty).
2374 set_option_value((char_u*)"guifontwide", 0, ws, 0);
2379 CONVERT_FROM_UTF8_FREE(ws);
2381 CONVERT_FROM_UTF8_FREE(s);
2384 [self redrawScreen];
2387 - (void)handleDropFiles:(NSData *)data
2389 // TODO: Get rid of this method; instead use Vim script directly. At the
2390 // moment I know how to do this to open files in tabs, but I'm not sure how
2391 // to add the filenames to the command line when in command line mode.
2395 NSMutableDictionary *args = [NSMutableDictionary dictionaryWithData:data];
2398 id obj = [args objectForKey:@"forceOpen"];
2399 BOOL forceOpen = YES;
2401 forceOpen = [obj boolValue];
2403 NSArray *filenames = [args objectForKey:@"filenames"];
2404 if (!(filenames && [filenames count] > 0)) return;
2407 if (!forceOpen && (State & CMDLINE)) {
2408 // HACK! If Vim is in command line mode then the files names
2409 // should be added to the command line, instead of opening the
2410 // files in tabs (unless forceOpen is set). This is taken care of by
2411 // gui_handle_drop().
2412 int n = [filenames count];
2413 char_u **fnames = (char_u **)alloc(n * sizeof(char_u *));
2416 for (i = 0; i < n; ++i)
2417 fnames[i] = [[filenames objectAtIndex:i] vimStringSave];
2419 // NOTE! This function will free 'fnames'.
2420 // HACK! It is assumed that the 'x' and 'y' arguments are
2421 // unused when in command line mode.
2422 gui_handle_drop(0, 0, 0, fnames, n);
2427 [self handleOpenWithArguments:args];
2431 - (void)handleDropString:(NSData *)data
2436 char_u dropkey[3] = { CSI, KS_EXTRA, (char_u)KE_DROP };
2437 const void *bytes = [data bytes];
2438 int len = *((int*)bytes); bytes += sizeof(int);
2439 NSMutableString *string = [NSMutableString stringWithUTF8String:bytes];
2441 // Replace unrecognized end-of-line sequences with \x0a (line feed).
2442 NSRange range = { 0, [string length] };
2443 unsigned n = [string replaceOccurrencesOfString:@"\x0d\x0a"
2444 withString:@"\x0a" options:0
2447 n = [string replaceOccurrencesOfString:@"\x0d" withString:@"\x0a"
2448 options:0 range:range];
2451 len = [string lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
2452 char_u *s = (char_u*)[string UTF8String];
2454 if (input_conv.vc_type != CONV_NONE)
2455 s = string_convert(&input_conv, s, &len);
2457 dnd_yank_drag_data(s, len);
2459 if (input_conv.vc_type != CONV_NONE)
2462 add_to_input_buf(dropkey, sizeof(dropkey));
2466 - (void)startOdbEditWithArguments:(NSDictionary *)args
2468 #ifdef FEAT_ODB_EDITOR
2469 id obj = [args objectForKey:@"remoteID"];
2472 OSType serverID = [obj unsignedIntValue];
2473 NSString *remotePath = [args objectForKey:@"remotePath"];
2475 NSAppleEventDescriptor *token = nil;
2476 NSData *tokenData = [args objectForKey:@"remoteTokenData"];
2477 obj = [args objectForKey:@"remoteTokenDescType"];
2478 if (tokenData && obj) {
2479 DescType tokenType = [obj unsignedLongValue];
2480 token = [NSAppleEventDescriptor descriptorWithDescriptorType:tokenType
2484 NSArray *filenames = [args objectForKey:@"filenames"];
2485 unsigned i, numFiles = [filenames count];
2486 for (i = 0; i < numFiles; ++i) {
2487 NSString *filename = [filenames objectAtIndex:i];
2488 char_u *s = [filename vimStringSave];
2489 buf_T *buf = buflist_findname(s);
2493 if (buf->b_odb_token) {
2494 [(NSAppleEventDescriptor*)(buf->b_odb_token) release];
2495 buf->b_odb_token = NULL;
2498 if (buf->b_odb_fname) {
2499 vim_free(buf->b_odb_fname);
2500 buf->b_odb_fname = NULL;
2503 buf->b_odb_server_id = serverID;
2506 buf->b_odb_token = [token retain];
2508 buf->b_odb_fname = [remotePath vimStringSave];
2510 ASLogWarn(@"Could not find buffer '%@' for ODB editing.", filename);
2513 #endif // FEAT_ODB_EDITOR
2516 - (void)handleXcodeMod:(NSData *)data
2519 const void *bytes = [data bytes];
2520 DescType type = *((DescType*)bytes); bytes += sizeof(DescType);
2521 unsigned len = *((unsigned*)bytes); bytes += sizeof(unsigned);
2525 NSAppleEventDescriptor *replyEvent = [NSAppleEventDescriptor
2526 descriptorWithDescriptorType:type
2532 - (void)handleOpenWithArguments:(NSDictionary *)args
2534 // ARGUMENT: DESCRIPTION:
2535 // -------------------------------------------------------------
2536 // filenames list of filenames
2537 // dontOpen don't open files specified in above argument
2538 // layout which layout to use to open files
2539 // selectionRange range of lines to select
2540 // searchText string to search for
2541 // cursorLine line to position the cursor on
2542 // cursorColumn column to position the cursor on
2543 // (only valid when "cursorLine" is set)
2544 // remoteID ODB parameter
2545 // remotePath ODB parameter
2546 // remoteTokenDescType ODB parameter
2547 // remoteTokenData ODB parameter
2549 ASLogDebug(@"args=%@ (starting=%d)", args, starting);
2551 NSArray *filenames = [args objectForKey:@"filenames"];
2552 int i, numFiles = filenames ? [filenames count] : 0;
2553 BOOL openFiles = ![[args objectForKey:@"dontOpen"] boolValue];
2554 int layout = [[args objectForKey:@"layout"] intValue];
2556 // Change to directory of first file to open if this is an "unused" editor
2557 // (but do not do this if editing remotely).
2558 if (openFiles && numFiles > 0 && ![args objectForKey:@"remoteID"]
2559 && (starting || [self unusedEditor]) ) {
2560 char_u *s = [[filenames objectAtIndex:0] vimStringSave];
2562 mch_chdir((char*)s);
2570 // When Vim is starting we simply add the files to be opened to the
2571 // global arglist and Vim will take care of opening them for us.
2572 if (openFiles && numFiles > 0) {
2573 for (i = 0; i < numFiles; i++) {
2574 NSString *fname = [filenames objectAtIndex:i];
2577 if (ga_grow(&global_alist.al_ga, 1) == FAIL
2578 || (p = [fname vimStringSave]) == NULL)
2579 exit(2); // See comment in -[MMBackend exit]
2581 alist_add(&global_alist, p, 2);
2584 // Vim will take care of arranging the files added to the arglist
2585 // in windows or tabs; all we must do is to specify which layout to
2587 initialWindowLayout = layout;
2590 // When Vim is already open we resort to some trickery to open the
2591 // files with the specified layout.
2593 // TODO: Figure out a better way to handle this?
2594 if (openFiles && numFiles > 0) {
2595 BOOL oneWindowInTab = topframe ? YES
2596 : (topframe->fr_layout == FR_LEAF);
2597 BOOL bufChanged = NO;
2598 BOOL bufHasFilename = NO;
2600 bufChanged = curbufIsChanged();
2601 bufHasFilename = curbuf->b_ffname != NULL;
2604 // Temporarily disable flushing since the following code may
2605 // potentially cause multiple redraws.
2606 flushDisabled = YES;
2608 BOOL onlyOneTab = (first_tabpage->tp_next == NULL);
2609 if (WIN_TABS == layout && !onlyOneTab) {
2610 // By going to the last tabpage we ensure that the new tabs
2611 // will appear last (if this call is left out, the taborder
2616 // Make sure we're in normal mode first.
2617 [self addInput:@"<C-\\><C-N>"];
2620 // With "split layout" we open a new tab before opening
2621 // multiple files if the current tab has more than one window
2622 // or if there is exactly one window but whose buffer has a
2623 // filename. (The :drop command ensures modified buffers get
2624 // their own window.)
2625 if ((WIN_HOR == layout || WIN_VER == layout) &&
2626 (!oneWindowInTab || bufHasFilename))
2627 [self addInput:@":tabnew<CR>"];
2629 // The files are opened by constructing a ":drop ..." command
2630 // and executing it.
2631 NSMutableString *cmd = (WIN_TABS == layout)
2632 ? [NSMutableString stringWithString:@":tab drop"]
2633 : [NSMutableString stringWithString:@":drop"];
2635 for (i = 0; i < numFiles; ++i) {
2636 NSString *file = [filenames objectAtIndex:i];
2637 file = [file stringByEscapingSpecialFilenameCharacters];
2638 [cmd appendString:@" "];
2639 [cmd appendString:file];
2642 // Temporarily clear 'suffixes' so that the files are opened in
2643 // the same order as they appear in the "filenames" array.
2644 [self addInput:@":let mvim_oldsu=&su|set su=<CR>"];
2646 [self addInput:cmd];
2648 // Split the view into multiple windows if requested.
2649 if (WIN_HOR == layout)
2650 [self addInput:@"|sall"];
2651 else if (WIN_VER == layout)
2652 [self addInput:@"|vert sall"];
2654 // Restore the old value of 'suffixes'.
2655 [self addInput:@"|let &su=mvim_oldsu|unlet mvim_oldsu<CR>"];
2657 // When opening one file we try to reuse the current window,
2658 // but not if its buffer is modified or has a filename.
2659 // However, the 'arglist' layout always opens the file in the
2661 NSString *file = [[filenames lastObject]
2662 stringByEscapingSpecialFilenameCharacters];
2664 if (WIN_HOR == layout) {
2665 if (!(bufHasFilename || bufChanged))
2666 cmd = [NSString stringWithFormat:@":e %@", file];
2668 cmd = [NSString stringWithFormat:@":sp %@", file];
2669 } else if (WIN_VER == layout) {
2670 if (!(bufHasFilename || bufChanged))
2671 cmd = [NSString stringWithFormat:@":e %@", file];
2673 cmd = [NSString stringWithFormat:@":vsp %@", file];
2674 } else if (WIN_TABS == layout) {
2675 if (oneWindowInTab && !(bufHasFilename || bufChanged))
2676 cmd = [NSString stringWithFormat:@":e %@", file];
2678 cmd = [NSString stringWithFormat:@":tabe %@", file];
2680 // (The :drop command will split if there is a modified
2682 cmd = [NSString stringWithFormat:@":drop %@", file];
2685 [self addInput:cmd];
2686 [self addInput:@"<CR>"];
2689 // Force screen redraw (does it have to be this complicated?).
2690 // (This code was taken from the end of gui_handle_drop().)
2691 update_screen(NOT_VALID);
2694 gui_update_cursor(FALSE, FALSE);
2701 if ([args objectForKey:@"remoteID"]) {
2702 // NOTE: We have to delay processing any ODB related arguments since
2703 // the file(s) may not be opened until the input buffer is processed.
2704 [self performSelector:@selector(startOdbEditWithArguments:)
2709 NSString *lineString = [args objectForKey:@"cursorLine"];
2710 if (lineString && [lineString intValue] > 0) {
2711 NSString *columnString = [args objectForKey:@"cursorColumn"];
2712 if (!(columnString && [columnString intValue] > 0))
2713 columnString = @"1";
2715 NSString *cmd = [NSString stringWithFormat:@"<C-\\><C-N>:cal "
2716 "cursor(%@,%@)|norm! zz<CR>:f<CR>", lineString, columnString];
2717 [self addInput:cmd];
2720 NSString *rangeString = [args objectForKey:@"selectionRange"];
2722 // Build a command line string that will select the given range of
2723 // lines. If range.length == 0, then position the cursor on the given
2724 // line but do not select.
2725 NSRange range = NSRangeFromString(rangeString);
2727 if (range.length > 0) {
2728 cmd = [NSString stringWithFormat:@"<C-\\><C-N>%dGV%dGz.0",
2729 NSMaxRange(range), range.location];
2731 cmd = [NSString stringWithFormat:@"<C-\\><C-N>%dGz.0",
2735 [self addInput:cmd];
2738 NSString *searchText = [args objectForKey:@"searchText"];
2740 [self addInput:[NSString stringWithFormat:@"<C-\\><C-N>gg/\\c%@/e<CR>",
2745 - (BOOL)checkForModifiedBuffers
2748 for (buf = firstbuf; buf != NULL; buf = buf->b_next) {
2749 if (bufIsChanged(buf)) {
2757 - (void)addInput:(NSString *)input
2759 // NOTE: This code is essentially identical to server_to_input_buf(),
2760 // except the 'silent' flag is TRUE in the call to ins_typebuf() below.
2761 char_u *string = [input vimStringSave];
2762 if (!string) return;
2764 /* Set 'cpoptions' the way we want it.
2765 * B set - backslashes are *not* treated specially
2766 * k set - keycodes are *not* reverse-engineered
2767 * < unset - <Key> sequences *are* interpreted
2768 * The last but one parameter of replace_termcodes() is TRUE so that the
2769 * <lt> sequence is recognised - needed for a real backslash.
2772 char_u *cpo_save = p_cpo;
2773 p_cpo = (char_u *)"Bk";
2774 char_u *str = replace_termcodes((char_u *)string, &ptr, FALSE, TRUE, FALSE);
2777 if (*ptr != NUL) /* trailing CTRL-V results in nothing */
2780 * Add the string to the input stream.
2781 * Can't use add_to_input_buf() here, we now have K_SPECIAL bytes.
2783 * First clear typed characters from the typeahead buffer, there could
2784 * be half a mapping there. Then append to the existing string, so
2785 * that multiple commands from a client are concatenated.
2787 if (typebuf.tb_maplen < typebuf.tb_len)
2788 del_typebuf(typebuf.tb_len - typebuf.tb_maplen, typebuf.tb_maplen);
2789 (void)ins_typebuf(str, REMAP_NONE, typebuf.tb_len, TRUE, TRUE);
2791 /* Let input_available() know we inserted text in the typeahead
2793 typebuf_was_filled = TRUE;
2799 - (BOOL)unusedEditor
2801 BOOL oneWindowInTab = topframe ? YES
2802 : (topframe->fr_layout == FR_LEAF);
2803 BOOL bufChanged = NO;
2804 BOOL bufHasFilename = NO;
2806 bufChanged = curbufIsChanged();
2807 bufHasFilename = curbuf->b_ffname != NULL;
2810 BOOL onlyOneTab = (first_tabpage->tp_next == NULL);
2812 return onlyOneTab && oneWindowInTab && !bufChanged && !bufHasFilename;
2815 - (void)redrawScreen
2817 // Force screen redraw (does it have to be this complicated?).
2818 redraw_all_later(CLEAR);
2819 update_screen(NOT_VALID);
2822 gui_update_cursor(FALSE, FALSE);
2824 // HACK! The cursor is not put back at the command line by the above
2825 // "redraw commands". The following test seems to do the trick though.
2826 if (State & CMDLINE)
2830 - (void)handleFindReplace:(NSDictionary *)args
2834 NSString *findString = [args objectForKey:@"find"];
2835 if (!findString) return;
2837 char_u *find = [findString vimStringSave];
2838 char_u *replace = [[args objectForKey:@"replace"] vimStringSave];
2839 int flags = [[args objectForKey:@"flags"] intValue];
2841 // NOTE: The flag 0x100 is used to indicate a backward search.
2842 gui_do_findrepl(flags, find, replace, (flags & 0x100) == 0);
2849 - (void)handleMarkedText:(NSData *)data
2851 const void *bytes = [data bytes];
2852 int32_t pos = *((int32_t*)bytes); bytes += sizeof(int32_t);
2853 unsigned len = *((unsigned*)bytes); bytes += sizeof(unsigned);
2854 char *chars = (char *)bytes;
2856 ASLogDebug(@"pos=%d len=%d chars=%s", pos, len, chars);
2859 im_preedit_abandon_macvim();
2860 } else if (len == 0) {
2861 im_preedit_end_macvim();
2863 if (!preedit_get_status())
2864 im_preedit_start_macvim();
2866 im_preedit_changed_macvim(chars, pos);
2870 @end // MMBackend (Private)
2875 @implementation MMBackend (ClientServer)
2877 - (NSString *)connectionNameFromServerName:(NSString *)name
2879 NSString *bundlePath = [[NSBundle mainBundle] bundlePath];
2881 return [[NSString stringWithFormat:@"%@.%@", bundlePath, name]
2885 - (NSConnection *)connectionForServerName:(NSString *)name
2887 // TODO: Try 'name%d' if 'name' fails.
2888 NSString *connName = [self connectionNameFromServerName:name];
2889 NSConnection *svrConn = [connectionNameDict objectForKey:connName];
2892 svrConn = [NSConnection connectionWithRegisteredName:connName
2894 // Try alternate server...
2895 if (!svrConn && alternateServerName) {
2896 ASLogInfo(@" trying to connect to alternate server: %@",
2897 alternateServerName);
2898 connName = [self connectionNameFromServerName:alternateServerName];
2899 svrConn = [NSConnection connectionWithRegisteredName:connName
2903 // Try looking for alternate servers...
2905 ASLogInfo(@" looking for alternate servers...");
2906 NSString *alt = [self alternateServerNameForName:name];
2907 if (alt != alternateServerName) {
2908 ASLogInfo(@" found alternate server: %@", alt);
2909 [alternateServerName release];
2910 alternateServerName = [alt copy];
2914 // Try alternate server again...
2915 if (!svrConn && alternateServerName) {
2916 ASLogInfo(@" trying to connect to alternate server: %@",
2917 alternateServerName);
2918 connName = [self connectionNameFromServerName:alternateServerName];
2919 svrConn = [NSConnection connectionWithRegisteredName:connName
2924 [connectionNameDict setObject:svrConn forKey:connName];
2926 ASLogDebug(@"Adding %@ as connection observer for %@",
2928 [[NSNotificationCenter defaultCenter] addObserver:self
2929 selector:@selector(serverConnectionDidDie:)
2930 name:NSConnectionDidDieNotification object:svrConn];
2937 - (NSConnection *)connectionForServerPort:(int)port
2940 NSEnumerator *e = [connectionNameDict objectEnumerator];
2942 while ((conn = [e nextObject])) {
2943 // HACK! Assume connection uses mach ports.
2944 if (port == [(NSMachPort*)[conn sendPort] machPort])
2951 - (void)serverConnectionDidDie:(NSNotification *)notification
2953 ASLogDebug(@"notification=%@", notification);
2955 NSConnection *svrConn = [notification object];
2957 ASLogDebug(@"Removing %@ as connection observer from %@", self, svrConn);
2958 [[NSNotificationCenter defaultCenter]
2960 name:NSConnectionDidDieNotification
2963 [connectionNameDict removeObjectsForKeys:
2964 [connectionNameDict allKeysForObject:svrConn]];
2966 // HACK! Assume connection uses mach ports.
2967 int port = [(NSMachPort*)[svrConn sendPort] machPort];
2968 NSNumber *key = [NSNumber numberWithInt:port];
2970 [clientProxyDict removeObjectForKey:key];
2971 [serverReplyDict removeObjectForKey:key];
2974 - (void)addClient:(NSDistantObject *)client
2976 NSConnection *conn = [client connectionForProxy];
2977 // HACK! Assume connection uses mach ports.
2978 int port = [(NSMachPort*)[conn sendPort] machPort];
2979 NSNumber *key = [NSNumber numberWithInt:port];
2981 if (![clientProxyDict objectForKey:key]) {
2982 [client setProtocolForProxy:@protocol(MMVimClientProtocol)];
2983 [clientProxyDict setObject:client forKey:key];
2986 // NOTE: 'clientWindow' is a global variable which is used by <client>
2987 clientWindow = port;
2990 - (NSString *)alternateServerNameForName:(NSString *)name
2992 if (!(name && [name length] > 0))
2995 // Only look for alternates if 'name' doesn't end in a digit.
2996 unichar lastChar = [name characterAtIndex:[name length]-1];
2997 if (lastChar >= '0' && lastChar <= '9')
3000 // Look for alternates among all current servers.
3001 NSArray *list = [self serverList];
3002 if (!(list && [list count] > 0))
3005 // Filter out servers starting with 'name' and ending with a number. The
3006 // (?i) pattern ensures that the match is case insensitive.
3007 NSString *pat = [NSString stringWithFormat:@"(?i)%@[0-9]+\\z", name];
3008 NSPredicate *pred = [NSPredicate predicateWithFormat:
3009 @"SELF MATCHES %@", pat];
3010 list = [list filteredArrayUsingPredicate:pred];
3011 if ([list count] > 0) {
3012 list = [list sortedArrayUsingSelector:@selector(serverNameCompare:)];
3013 return [list objectAtIndex:0];
3019 @end // MMBackend (ClientServer)
3024 @implementation NSString (MMServerNameCompare)
3025 - (NSComparisonResult)serverNameCompare:(NSString *)string
3027 return [self compare:string
3028 options:NSCaseInsensitiveSearch|NSNumericSearch];
3035 static int eventModifierFlagsToVimModMask(int modifierFlags)
3039 if (modifierFlags & NSShiftKeyMask)
3040 modMask |= MOD_MASK_SHIFT;
3041 if (modifierFlags & NSControlKeyMask)
3042 modMask |= MOD_MASK_CTRL;
3043 if (modifierFlags & NSAlternateKeyMask)
3044 modMask |= MOD_MASK_ALT;
3045 if (modifierFlags & NSCommandKeyMask)
3046 modMask |= MOD_MASK_CMD;
3051 static int eventModifierFlagsToVimMouseModMask(int modifierFlags)
3055 if (modifierFlags & NSShiftKeyMask)
3056 modMask |= MOUSE_SHIFT;
3057 if (modifierFlags & NSControlKeyMask)
3058 modMask |= MOUSE_CTRL;
3059 if (modifierFlags & NSAlternateKeyMask)
3060 modMask |= MOUSE_ALT;
3065 static int eventButtonNumberToVimMouseButton(int buttonNumber)
3067 static int mouseButton[] = { MOUSE_LEFT, MOUSE_RIGHT, MOUSE_MIDDLE };
3069 return (buttonNumber >= 0 && buttonNumber < 3)
3070 ? mouseButton[buttonNumber] : -1;
3075 // This function is modeled after the VimToPython function found in if_python.c
3076 // NB This does a deep copy by value, it does not lookup references like the
3077 // VimToPython function does. This is because I didn't want to deal with the
3078 // retain cycles that this would create, and we can cover 99% of the use cases
3079 // by ignoring it. If we ever switch to using GC in MacVim then this
3080 // functionality can be implemented easily.
3081 static id vimToCocoa(typval_T * tv, int depth)
3087 // Avoid infinite recursion
3092 if (tv->v_type == VAR_STRING) {
3093 char_u * val = tv->vval.v_string;
3094 // val can be NULL if the string is empty
3096 result = [NSString string];
3099 val = CONVERT_TO_UTF8(val);
3101 result = [NSString stringWithUTF8String:(char*)val];
3103 CONVERT_TO_UTF8_FREE(val);
3106 } else if (tv->v_type == VAR_NUMBER) {
3107 // looks like sizeof(varnumber_T) is always <= sizeof(long)
3108 result = [NSNumber numberWithLong:(long)tv->vval.v_number];
3109 } else if (tv->v_type == VAR_LIST) {
3110 list_T * list = tv->vval.v_list;
3113 NSMutableArray * arr = result = [NSMutableArray array];
3116 for (curr = list->lv_first; curr != NULL; curr = curr->li_next) {
3117 newObj = vimToCocoa(&curr->li_tv, depth + 1);
3118 [arr addObject:newObj];
3121 } else if (tv->v_type == VAR_DICT) {
3122 NSMutableDictionary * dict = result = [NSMutableDictionary dictionary];
3124 if (tv->vval.v_dict != NULL) {
3125 hashtab_T * ht = &tv->vval.v_dict->dv_hashtab;
3126 int todo = ht->ht_used;
3130 for (hi = ht->ht_array; todo > 0; ++hi) {
3131 if (!HASHITEM_EMPTY(hi)) {
3134 di = dict_lookup(hi);
3135 newObj = vimToCocoa(&di->di_tv, depth + 1);
3137 char_u * keyval = hi->hi_key;
3139 keyval = CONVERT_TO_UTF8(keyval);
3141 NSString * key = [NSString stringWithUTF8String:(char*)keyval];
3143 CONVERT_TO_UTF8_FREE(keyval);
3145 [dict setObject:newObj forKey:key];
3149 } else { // only func refs should fall into this category?
3157 // This function is modeled after eval_client_expr_to_string found in main.c
3158 // Returns nil if there was an error evaluating the expression, and writes a
3159 // message to errorStr.
3160 // TODO Get the error that occurred while evaluating the expression in vim
3162 static id evalExprCocoa(NSString * expr, NSString ** errstr)
3165 char_u *s = (char_u*)[expr UTF8String];
3168 s = CONVERT_FROM_UTF8(s);
3171 int save_dbl = debug_break_level;
3172 int save_ro = redir_off;
3174 debug_break_level = -1;
3178 typval_T * tvres = eval_expr(s, NULL);
3180 debug_break_level = save_dbl;
3181 redir_off = save_ro;
3188 CONVERT_FROM_UTF8_FREE(s);
3193 gui_update_cursor(FALSE, FALSE);
3196 if (tvres == NULL) {
3198 *errstr = @"Expression evaluation failed.";
3201 id res = vimToCocoa(tvres, 1);
3206 *errstr = @"Conversion to cocoa values failed.";
3214 @implementation NSString (VimStrings)
3216 + (id)stringWithVimString:(char_u *)s
3218 // This method ensures a non-nil string is returned. If 's' cannot be
3219 // converted to a utf-8 string it is assumed to be latin-1. If conversion
3220 // still fails an empty NSString is returned.
3221 NSString *string = nil;
3224 s = CONVERT_TO_UTF8(s);
3226 string = [NSString stringWithUTF8String:(char*)s];
3228 // HACK! Apparently 's' is not a valid utf-8 string, maybe it is
3230 string = [NSString stringWithCString:(char*)s
3231 encoding:NSISOLatin1StringEncoding];
3234 CONVERT_TO_UTF8_FREE(s);
3238 return string != nil ? string : [NSString string];
3241 - (char_u *)vimStringSave
3243 char_u *s = (char_u*)[self UTF8String], *ret = NULL;
3246 s = CONVERT_FROM_UTF8(s);
3248 ret = vim_strsave(s);
3250 CONVERT_FROM_UTF8_FREE(s);
3256 @end // NSString (VimStrings)