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)queueMessage:(int)msgid properties:(NSDictionary *)props
331 [self queueMessage:msgid data:[props dictionaryAsData]];
336 if (![self connection]) {
338 // This is a preloaded process and as such should not cause the
339 // MacVim to be opened. We probably got here as a result of the
340 // user quitting MacVim while the process was preloading, so exit
342 // (Don't use mch_exit() since it assumes the process has properly
347 NSBundle *mainBundle = [NSBundle mainBundle];
352 // Launch MacVim using Launch Services (NSWorkspace would be nicer, but
353 // the API to pass Apple Event parameters is broken on 10.4).
354 NSString *path = [mainBundle bundlePath];
355 status = FSPathMakeRef((const UInt8 *)[path UTF8String], &ref, NULL);
356 if (noErr == status) {
357 // Pass parameter to the 'Open' Apple Event that tells MacVim not
358 // to open an untitled window.
359 NSAppleEventDescriptor *desc =
360 [NSAppleEventDescriptor recordDescriptor];
361 [desc setParamDescriptor:
362 [NSAppleEventDescriptor descriptorWithBoolean:NO]
363 forKeyword:keyMMUntitledWindow];
365 LSLaunchFSRefSpec spec = { &ref, 0, NULL, [desc aeDesc],
366 kLSLaunchDefaults, NULL };
367 status = LSOpenFromRefSpec(&spec, NULL);
370 if (noErr != status) {
371 ASLogCrit(@"Failed to launch MacVim (path=%@).%@",
372 path, MMSymlinkWarningString);
376 // Launch MacVim using NSTask. For some reason the above code using
377 // Launch Services sometimes fails on LSOpenFromRefSpec() (when it
378 // fails, the dock icon starts bouncing and never stops). It seems
379 // like rebuilding the Launch Services database takes care of this
380 // problem, but the NSTask way seems more stable so stick with it.
382 // NOTE! Using NSTask to launch the GUI has the negative side-effect
383 // that the GUI won't be activated (or raised) so there is a hack in
384 // MMAppController which raises the app when a new window is opened.
385 NSMutableArray *args = [NSMutableArray arrayWithObjects:
386 [NSString stringWithFormat:@"-%@", MMNoWindowKey], @"yes", nil];
387 NSString *exeName = [[mainBundle infoDictionary]
388 objectForKey:@"CFBundleExecutable"];
389 NSString *path = [mainBundle pathForAuxiliaryExecutable:exeName];
391 ASLogCrit(@"Could not find MacVim executable in bundle.%@",
392 MMSymlinkWarningString);
396 [NSTask launchedTaskWithLaunchPath:path arguments:args];
399 // HACK! Poll the mach bootstrap server until it returns a valid
400 // connection to detect that MacVim has finished launching. Also set a
401 // time-out date so that we don't get stuck doing this forever.
402 NSDate *timeOutDate = [NSDate dateWithTimeIntervalSinceNow:10];
403 while (![self connection] &&
404 NSOrderedDescending == [timeOutDate compare:[NSDate date]])
405 [[NSRunLoop currentRunLoop]
406 runMode:NSDefaultRunLoopMode
407 beforeDate:[NSDate dateWithTimeIntervalSinceNow:.1]];
409 // NOTE: [self connection] will set 'connection' as a side-effect.
411 ASLogCrit(@"Timed-out waiting for GUI to launch.");
417 [[NSNotificationCenter defaultCenter] addObserver:self
418 selector:@selector(connectionDidDie:)
419 name:NSConnectionDidDieNotification object:connection];
421 appProxy = [[connection rootProxy] retain];
422 [appProxy setProtocolForProxy:@protocol(MMAppProtocol)];
424 // NOTE: We do not set any new timeout values for the connection to the
425 // frontend. This means that if the frontend is "stuck" (e.g. in a
426 // modal loop) then any calls to the frontend will block indefinitely
427 // (the default timeouts are huge).
429 int pid = [[NSProcessInfo processInfo] processIdentifier];
431 identifier = [appProxy connectBackend:self pid:pid];
434 @catch (NSException *ex) {
435 ASLogDebug(@"Connect backend failed: reason=%@", ex);
441 - (BOOL)openGUIWindow
443 [self queueMessage:OpenWindowMsgID data:nil];
449 int type = ClearAllDrawType;
451 // Any draw commands in queue are effectively obsolete since this clearAll
452 // will negate any effect they have, therefore we may as well clear the
454 [self clearDrawData];
456 [drawData appendBytes:&type length:sizeof(int)];
459 - (void)clearBlockFromRow:(int)row1 column:(int)col1
460 toRow:(int)row2 column:(int)col2
462 int type = ClearBlockDrawType;
464 [drawData appendBytes:&type length:sizeof(int)];
466 [drawData appendBytes:&defaultBackgroundColor length:sizeof(unsigned)];
467 [drawData appendBytes:&row1 length:sizeof(int)];
468 [drawData appendBytes:&col1 length:sizeof(int)];
469 [drawData appendBytes:&row2 length:sizeof(int)];
470 [drawData appendBytes:&col2 length:sizeof(int)];
473 - (void)deleteLinesFromRow:(int)row count:(int)count
474 scrollBottom:(int)bottom left:(int)left right:(int)right
476 int type = DeleteLinesDrawType;
478 [drawData appendBytes:&type length:sizeof(int)];
480 [drawData appendBytes:&defaultBackgroundColor length:sizeof(unsigned)];
481 [drawData appendBytes:&row length:sizeof(int)];
482 [drawData appendBytes:&count length:sizeof(int)];
483 [drawData appendBytes:&bottom length:sizeof(int)];
484 [drawData appendBytes:&left length:sizeof(int)];
485 [drawData appendBytes:&right length:sizeof(int)];
487 if (left == 0 && right == gui.num_cols-1)
488 [self didChangeWholeLine];
491 - (void)drawString:(char_u*)s length:(int)len row:(int)row
492 column:(int)col cells:(int)cells flags:(int)flags
494 if (len <= 0) return;
496 int type = DrawStringDrawType;
498 [drawData appendBytes:&type length:sizeof(int)];
500 [drawData appendBytes:&backgroundColor length:sizeof(unsigned)];
501 [drawData appendBytes:&foregroundColor length:sizeof(unsigned)];
502 [drawData appendBytes:&specialColor length:sizeof(unsigned)];
503 [drawData appendBytes:&row length:sizeof(int)];
504 [drawData appendBytes:&col length:sizeof(int)];
505 [drawData appendBytes:&cells length:sizeof(int)];
506 [drawData appendBytes:&flags length:sizeof(int)];
507 [drawData appendBytes:&len length:sizeof(int)];
508 [drawData appendBytes:s length:len];
511 - (void)insertLinesFromRow:(int)row count:(int)count
512 scrollBottom:(int)bottom left:(int)left right:(int)right
514 int type = InsertLinesDrawType;
516 [drawData appendBytes:&type length:sizeof(int)];
518 [drawData appendBytes:&defaultBackgroundColor length:sizeof(unsigned)];
519 [drawData appendBytes:&row length:sizeof(int)];
520 [drawData appendBytes:&count length:sizeof(int)];
521 [drawData appendBytes:&bottom length:sizeof(int)];
522 [drawData appendBytes:&left length:sizeof(int)];
523 [drawData appendBytes:&right length:sizeof(int)];
525 if (left == 0 && right == gui.num_cols-1)
526 [self didChangeWholeLine];
529 - (void)drawCursorAtRow:(int)row column:(int)col shape:(int)shape
530 fraction:(int)percent color:(int)color
532 int type = DrawCursorDrawType;
533 unsigned uc = MM_COLOR(color);
535 [drawData appendBytes:&type length:sizeof(int)];
537 [drawData appendBytes:&uc length:sizeof(unsigned)];
538 [drawData appendBytes:&row length:sizeof(int)];
539 [drawData appendBytes:&col length:sizeof(int)];
540 [drawData appendBytes:&shape length:sizeof(int)];
541 [drawData appendBytes:&percent length:sizeof(int)];
544 - (void)drawInvertedRectAtRow:(int)row column:(int)col numRows:(int)nr
545 numColumns:(int)nc invert:(int)invert
547 int type = DrawInvertedRectDrawType;
548 [drawData appendBytes:&type length:sizeof(int)];
550 [drawData appendBytes:&row length:sizeof(int)];
551 [drawData appendBytes:&col length:sizeof(int)];
552 [drawData appendBytes:&nr length:sizeof(int)];
553 [drawData appendBytes:&nc length:sizeof(int)];
554 [drawData appendBytes:&invert length:sizeof(int)];
559 // Keep running the run-loop until there is no more input to process.
560 while (CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0, true)
561 == kCFRunLoopRunHandledSource)
565 - (void)flushQueue:(BOOL)force
567 // NOTE: This variable allows for better control over when the queue is
568 // flushed. It can be set to YES at the beginning of a sequence of calls
569 // that may potentially add items to the queue, and then restored back to
571 if (flushDisabled) return;
573 if ([drawData length] > 0) {
574 // HACK! Detect changes to 'guifontwide'.
575 if (gui.wide_font != oldWideFont) {
576 gui_mch_free_font(oldWideFont);
577 oldWideFont = gui_mch_retain_font(gui.wide_font);
578 [self setFont:oldWideFont wide:YES];
581 int type = SetCursorPosDrawType;
582 [drawData appendBytes:&type length:sizeof(type)];
583 [drawData appendBytes:&gui.row length:sizeof(gui.row)];
584 [drawData appendBytes:&gui.col length:sizeof(gui.col)];
586 [self queueMessage:BatchDrawMsgID data:[drawData copy]];
587 [self clearDrawData];
590 if ([outputQueue count] > 0) {
591 [self insertVimStateMessage];
594 ASLogDebug(@"Flushing queue: %@",
595 debugStringForMessageQueue(outputQueue));
596 [appProxy processInput:outputQueue forIdentifier:identifier];
598 @catch (NSException *ex) {
599 ASLogDebug(@"processInput:forIdentifer failed: reason=%@", ex);
600 if (![connection isValid]) {
601 ASLogDebug(@"Connection is invalid, exit now!");
602 ASLogDebug(@"waitForAck=%d got_int=%d", waitForAck, got_int);
607 [outputQueue removeAllObjects];
611 - (BOOL)waitForInput:(int)milliseconds
613 // Return NO if we timed out waiting for input, otherwise return YES.
614 BOOL inputReceived = NO;
616 // Only start the run loop if the input queue is empty, otherwise process
617 // the input first so that the input on queue isn't delayed.
618 if ([inputQueue count]) {
621 // Wait for the specified amount of time, unless 'milliseconds' is
622 // negative in which case we wait "forever" (1e6 seconds translates to
623 // approximately 11 days).
624 CFTimeInterval dt = (milliseconds >= 0 ? .001*milliseconds : 1e6);
626 while (CFRunLoopRunInMode(kCFRunLoopDefaultMode, dt, true)
627 == kCFRunLoopRunHandledSource) {
628 // In order to ensure that all input on the run-loop has been
629 // processed we set the timeout to 0 and keep processing until the
630 // run-loop times out.
636 // The above calls may have placed messages on the input queue so process
637 // it now. This call may enter a blocking loop.
638 if ([inputQueue count] > 0)
639 [self processInputQueue];
641 return inputReceived;
646 // NOTE: This is called if mch_exit() is called. Since we assume here that
647 // the process has started properly, be sure to use exit() instead of
648 // mch_exit() to prematurely terminate a process (or set 'isTerminating'
651 // Make sure no connectionDidDie: notification is received now that we are
653 [[NSNotificationCenter defaultCenter] removeObserver:self];
655 // The 'isTerminating' flag indicates that the frontend is also exiting so
656 // there is no need to flush any more output since the frontend won't look
658 if (!isTerminating && [connection isValid]) {
660 // Flush the entire queue in case a VimLeave autocommand added
661 // something to the queue.
662 [self queueMessage:CloseWindowMsgID data:nil];
663 ASLogDebug(@"Flush output queue before exit: %@",
664 debugStringForMessageQueue(outputQueue));
665 [appProxy processInput:outputQueue forIdentifier:identifier];
667 @catch (NSException *ex) {
668 ASLogDebug(@"CloseWindowMsgID send failed: reason=%@", ex);
671 // NOTE: If Cmd-w was pressed to close the window the menu is briefly
672 // highlighted and during this pause the frontend won't receive any DO
673 // messages. If the Vim process exits before this highlighting has
674 // finished Cocoa will emit the following error message:
675 // *** -[NSMachPort handlePortMessage:]: dropping incoming DO message
676 // because the connection or ports are invalid
677 // To avoid this warning we delay here. If the warning still appears
678 // this delay may need to be increased.
682 #ifdef MAC_CLIENTSERVER
683 // The default connection is used for the client/server code.
684 if (vimServerConnection) {
685 [vimServerConnection setRootObject:nil];
686 [vimServerConnection invalidate];
691 - (void)selectTab:(int)index
694 NSData *data = [NSData dataWithBytes:&index length:sizeof(int)];
695 [self queueMessage:SelectTabMsgID data:data];
700 NSMutableData *data = [NSMutableData data];
702 int idx = tabpage_index(curtab) - 1;
703 [data appendBytes:&idx length:sizeof(int)];
706 for (tp = first_tabpage; tp != NULL; tp = tp->tp_next) {
707 // Count the number of windows in the tabpage.
708 //win_T *wp = tp->tp_firstwin;
710 //for (wincount = 0; wp != NULL; wp = wp->w_next, ++wincount);
711 //[data appendBytes:&wincount length:sizeof(int)];
713 int tabProp = MMTabInfoCount;
714 [data appendBytes:&tabProp length:sizeof(int)];
715 for (tabProp = MMTabLabel; tabProp < MMTabInfoCount; ++tabProp) {
716 // This function puts the label of the tab in the global 'NameBuff'.
717 get_tabline_label(tp, (tabProp == MMTabToolTip));
718 NSString *s = [NSString stringWithVimString:NameBuff];
719 int len = [s lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
723 [data appendBytes:&len length:sizeof(int)];
725 [data appendBytes:[s UTF8String] length:len];
729 [self queueMessage:UpdateTabBarMsgID data:data];
732 - (BOOL)tabBarVisible
734 return tabBarVisible;
737 - (void)showTabBar:(BOOL)enable
739 tabBarVisible = enable;
741 int msgid = enable ? ShowTabBarMsgID : HideTabBarMsgID;
742 [self queueMessage:msgid data:nil];
745 - (void)setRows:(int)rows columns:(int)cols
747 int dim[] = { rows, cols };
748 NSData *data = [NSData dataWithBytes:&dim length:2*sizeof(int)];
750 [self queueMessage:SetTextDimensionsMsgID data:data];
753 - (void)setWindowTitle:(char *)title
755 NSMutableData *data = [NSMutableData data];
756 int len = strlen(title);
757 if (len <= 0) return;
759 [data appendBytes:&len length:sizeof(int)];
760 [data appendBytes:title length:len];
762 [self queueMessage:SetWindowTitleMsgID data:data];
765 - (void)setDocumentFilename:(char *)filename
767 NSMutableData *data = [NSMutableData data];
768 int len = filename ? strlen(filename) : 0;
770 [data appendBytes:&len length:sizeof(int)];
772 [data appendBytes:filename length:len];
774 [self queueMessage:SetDocumentFilenameMsgID data:data];
777 - (char *)browseForFileWithAttributes:(NSDictionary *)attr
781 [self queueMessage:BrowseForFileMsgID properties:attr];
782 [self flushQueue:YES];
785 [self waitForDialogReturn];
787 if (dialogReturn && [dialogReturn isKindOfClass:[NSString class]])
788 s = [dialogReturn vimStringSave];
790 [dialogReturn release]; dialogReturn = nil;
792 @catch (NSException *ex) {
793 ASLogDebug(@"Exception: reason=%@", ex);
799 - (oneway void)setDialogReturn:(in bycopy id)obj
801 ASLogDebug(@"%@", obj);
803 // NOTE: This is called by
804 // - [MMVimController panelDidEnd:::], and
805 // - [MMVimController alertDidEnd:::],
806 // to indicate that a save/open panel or alert has finished.
808 // We want to distinguish between "no dialog return yet" and "dialog
809 // returned nothing". The former can be tested with dialogReturn == nil,
810 // the latter with dialogReturn == [NSNull null].
811 if (!obj) obj = [NSNull null];
813 if (obj != dialogReturn) {
814 [dialogReturn release];
815 dialogReturn = [obj retain];
819 - (int)showDialogWithAttributes:(NSDictionary *)attr textField:(char *)txtfield
823 [self queueMessage:ShowDialogMsgID properties:attr];
824 [self flushQueue:YES];
827 [self waitForDialogReturn];
829 if (dialogReturn && [dialogReturn isKindOfClass:[NSArray class]]
830 && [dialogReturn count]) {
831 retval = [[dialogReturn objectAtIndex:0] intValue];
832 if (txtfield && [dialogReturn count] > 1) {
833 NSString *retString = [dialogReturn objectAtIndex:1];
834 char_u *ret = (char_u*)[retString UTF8String];
836 ret = CONVERT_FROM_UTF8(ret);
838 vim_strncpy((char_u*)txtfield, ret, IOSIZE - 1);
840 CONVERT_FROM_UTF8_FREE(ret);
845 [dialogReturn release]; dialogReturn = nil;
847 @catch (NSException *ex) {
848 ASLogDebug(@"Exception: reason=%@", ex);
854 - (void)showToolbar:(int)enable flags:(int)flags
856 NSMutableData *data = [NSMutableData data];
858 [data appendBytes:&enable length:sizeof(int)];
859 [data appendBytes:&flags length:sizeof(int)];
861 [self queueMessage:ShowToolbarMsgID data:data];
864 - (void)createScrollbarWithIdentifier:(int32_t)ident type:(int)type
866 NSMutableData *data = [NSMutableData data];
868 [data appendBytes:&ident length:sizeof(int32_t)];
869 [data appendBytes:&type length:sizeof(int)];
871 [self queueMessage:CreateScrollbarMsgID data:data];
874 - (void)destroyScrollbarWithIdentifier:(int32_t)ident
876 NSMutableData *data = [NSMutableData data];
877 [data appendBytes:&ident length:sizeof(int32_t)];
879 [self queueMessage:DestroyScrollbarMsgID data:data];
882 - (void)showScrollbarWithIdentifier:(int32_t)ident state:(int)visible
884 NSMutableData *data = [NSMutableData data];
886 [data appendBytes:&ident length:sizeof(int32_t)];
887 [data appendBytes:&visible length:sizeof(int)];
889 [self queueMessage:ShowScrollbarMsgID data:data];
892 - (void)setScrollbarPosition:(int)pos length:(int)len identifier:(int32_t)ident
894 NSMutableData *data = [NSMutableData data];
896 [data appendBytes:&ident length:sizeof(int32_t)];
897 [data appendBytes:&pos length:sizeof(int)];
898 [data appendBytes:&len length:sizeof(int)];
900 [self queueMessage:SetScrollbarPositionMsgID data:data];
903 - (void)setScrollbarThumbValue:(long)val size:(long)size max:(long)max
904 identifier:(int32_t)ident
906 float fval = max-size+1 > 0 ? (float)val/(max-size+1) : 0;
907 float prop = (float)size/(max+1);
908 if (fval < 0) fval = 0;
909 else if (fval > 1.0f) fval = 1.0f;
910 if (prop < 0) prop = 0;
911 else if (prop > 1.0f) prop = 1.0f;
913 NSMutableData *data = [NSMutableData data];
915 [data appendBytes:&ident length:sizeof(int32_t)];
916 [data appendBytes:&fval length:sizeof(float)];
917 [data appendBytes:&prop length:sizeof(float)];
919 [self queueMessage:SetScrollbarThumbMsgID data:data];
922 - (void)setFont:(GuiFont)font wide:(BOOL)wide
924 NSString *fontName = (NSString *)font;
926 NSArray *components = [fontName componentsSeparatedByString:@":"];
927 if ([components count] == 2) {
928 size = [[components lastObject] floatValue];
929 fontName = [components objectAtIndex:0];
932 int len = [fontName lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
933 NSMutableData *data = [NSMutableData data];
934 [data appendBytes:&size length:sizeof(float)];
935 [data appendBytes:&len length:sizeof(int)];
938 [data appendBytes:[fontName UTF8String] length:len];
940 return; // Only the wide font can be set to nothing
942 [self queueMessage:(wide ? SetWideFontMsgID : SetFontMsgID) data:data];
945 - (void)executeActionWithName:(NSString *)name
947 int len = [name lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
950 NSMutableData *data = [NSMutableData data];
952 [data appendBytes:&len length:sizeof(int)];
953 [data appendBytes:[name UTF8String] length:len];
955 [self queueMessage:ExecuteActionMsgID data:data];
959 - (void)setMouseShape:(int)shape
961 NSMutableData *data = [NSMutableData data];
962 [data appendBytes:&shape length:sizeof(int)];
963 [self queueMessage:SetMouseShapeMsgID data:data];
966 - (void)setBlinkWait:(int)wait on:(int)on off:(int)off
968 // Vim specifies times in milliseconds, whereas Cocoa wants them in
970 blinkWaitInterval = .001f*wait;
971 blinkOnInterval = .001f*on;
972 blinkOffInterval = .001f*off;
978 [blinkTimer invalidate];
979 [blinkTimer release];
983 if (blinkWaitInterval > 0 && blinkOnInterval > 0 && blinkOffInterval > 0
985 blinkState = MMBlinkStateOn;
987 [[NSTimer scheduledTimerWithTimeInterval:blinkWaitInterval
989 selector:@selector(blinkTimerFired:)
990 userInfo:nil repeats:NO] retain];
991 gui_update_cursor(TRUE, FALSE);
992 [self flushQueue:YES];
998 if (MMBlinkStateOff == blinkState) {
999 gui_update_cursor(TRUE, FALSE);
1000 [self flushQueue:YES];
1003 blinkState = MMBlinkStateNone;
1006 - (void)adjustLinespace:(int)linespace
1008 NSMutableData *data = [NSMutableData data];
1009 [data appendBytes:&linespace length:sizeof(int)];
1010 [self queueMessage:AdjustLinespaceMsgID data:data];
1015 [self queueMessage:ActivateMsgID data:nil];
1018 - (void)setPreEditRow:(int)row column:(int)col
1020 NSMutableData *data = [NSMutableData data];
1021 [data appendBytes:&row length:sizeof(int)];
1022 [data appendBytes:&col length:sizeof(int)];
1023 [self queueMessage:SetPreEditPositionMsgID data:data];
1026 - (int)lookupColorWithKey:(NSString *)key
1028 if (!(key && [key length] > 0))
1031 NSString *stripKey = [[[[key lowercaseString]
1032 stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]
1033 componentsSeparatedByString:@" "]
1034 componentsJoinedByString:@""];
1036 if (stripKey && [stripKey length] > 0) {
1037 // First of all try to lookup key in the color dictionary; note that
1038 // all keys in this dictionary are lowercase with no whitespace.
1039 id obj = [colorDict objectForKey:stripKey];
1040 if (obj) return [obj intValue];
1042 // The key was not in the dictionary; is it perhaps of the form
1044 if ([stripKey length] > 1 && [stripKey characterAtIndex:0] == '#') {
1045 NSScanner *scanner = [NSScanner scannerWithString:stripKey];
1046 [scanner setScanLocation:1];
1048 if ([scanner scanHexInt:&hex]) {
1053 // As a last resort, check if it is one of the system defined colors.
1054 // The keys in this dictionary are also lowercase with no whitespace.
1055 obj = [sysColorDict objectForKey:stripKey];
1057 NSColor *col = [NSColor performSelector:NSSelectorFromString(obj)];
1060 col = [col colorUsingColorSpaceName:NSCalibratedRGBColorSpace];
1061 [col getRed:&r green:&g blue:&b alpha:&a];
1062 return (((int)(r*255+.5f) & 0xff) << 16)
1063 + (((int)(g*255+.5f) & 0xff) << 8)
1064 + ((int)(b*255+.5f) & 0xff);
1069 ASLogNotice(@"No color with key %@ found.", stripKey);
1073 - (BOOL)hasSpecialKeyWithValue:(char_u *)value
1076 for (i = 0; special_keys[i].key_sym != 0; i++) {
1077 if (value[0] == special_keys[i].vim_code0
1078 && value[1] == special_keys[i].vim_code1)
1085 - (void)enterFullscreen:(int)fuoptions background:(int)bg
1087 NSMutableData *data = [NSMutableData data];
1088 [data appendBytes:&fuoptions length:sizeof(int)];
1090 [data appendBytes:&bg length:sizeof(int)];
1091 [self queueMessage:EnterFullscreenMsgID data:data];
1094 - (void)leaveFullscreen
1096 [self queueMessage:LeaveFullscreenMsgID data:nil];
1099 - (void)setFullscreenBackgroundColor:(int)color
1101 NSMutableData *data = [NSMutableData data];
1102 color = MM_COLOR(color);
1103 [data appendBytes:&color length:sizeof(int)];
1105 [self queueMessage:SetFullscreenColorMsgID data:data];
1108 - (void)setAntialias:(BOOL)antialias
1110 int msgid = antialias ? EnableAntialiasMsgID : DisableAntialiasMsgID;
1112 [self queueMessage:msgid data:nil];
1115 - (void)updateModifiedFlag
1117 // Notify MacVim if _any_ buffer has changed from unmodified to modified or
1119 int msgid = [self checkForModifiedBuffers]
1120 ? BuffersModifiedMsgID : BuffersNotModifiedMsgID;
1122 [self queueMessage:msgid data:nil];
1125 - (oneway void)processInput:(int)msgid data:(in bycopy NSData *)data
1127 // Look for Ctrl-C immediately instead of waiting until the input queue is
1128 // processed since that only happens in waitForInput: (and Vim regularly
1129 // checks for Ctrl-C in between waiting for input). Note that the flag
1130 // ctrl_c_interrupts is 0 e.g. when the user has mappings to something like
1131 // <C-c>g. Also it seems the flag intr_char is 0 when MacVim was started
1132 // from Finder whereas it is 0x03 (= Ctrl_C) when started from Terminal.
1134 // Similarly, TerminateNowMsgID must be checked immediately otherwise code
1135 // which waits on the run loop will fail to detect this message (e.g. in
1136 // waitForConnectionAcknowledgement).
1138 if (KeyDownMsgID == msgid && data != nil && ctrl_c_interrupts) {
1139 const void *bytes = [data bytes];
1140 /*unsigned mods = *((unsigned*)bytes);*/ bytes += sizeof(unsigned);
1141 /*unsigned code = *((unsigned*)bytes);*/ bytes += sizeof(unsigned);
1142 unsigned len = *((unsigned*)bytes); bytes += sizeof(unsigned);
1144 char_u *str = (char_u*)bytes;
1145 if (str[0] == Ctrl_C || (str[0] == intr_char && intr_char != 0)) {
1146 ASLogDebug(@"Got INT, str[0]=%#x ctrl_c_interrupts=%d "
1147 "intr_char=%#x", str[0], ctrl_c_interrupts, intr_char);
1149 [inputQueue removeAllObjects];
1153 } else if (TerminateNowMsgID == msgid) {
1154 // Terminate immediately (the frontend is about to quit or this process
1155 // was aborted). Don't preserve modified files since the user would
1156 // already have been presented with a dialog warning if there were any
1157 // modified files when we get here.
1158 isTerminating = YES;
1163 // Remove all previous instances of this message from the input queue, else
1164 // the input queue may fill up as a result of Vim not being able to keep up
1165 // with the speed at which new messages are received.
1166 // Keyboard input is never dropped, unless the input represents an
1167 // auto-repeated key.
1169 BOOL isKeyRepeat = NO;
1170 BOOL isKeyboardInput = NO;
1172 if (data && KeyDownMsgID == msgid) {
1173 isKeyboardInput = YES;
1175 // The lowest bit of the first int is set if this key is a repeat.
1176 int flags = *((int*)[data bytes]);
1181 // Keyboard input is not removed from the queue; repeats are ignored if
1182 // there already is keyboard input on the input queue.
1183 if (isKeyRepeat || !isKeyboardInput) {
1184 int i, count = [inputQueue count];
1185 for (i = 1; i < count; i+=2) {
1186 if ([[inputQueue objectAtIndex:i-1] intValue] == msgid) {
1190 [inputQueue removeObjectAtIndex:i];
1191 [inputQueue removeObjectAtIndex:i-1];
1197 [inputQueue addObject:[NSNumber numberWithInt:msgid]];
1198 [inputQueue addObject:(data ? (id)data : [NSNull null])];
1201 - (id)evaluateExpressionCocoa:(in bycopy NSString *)expr
1202 errorString:(out bycopy NSString **)errstr
1204 return evalExprCocoa(expr, errstr);
1208 - (NSString *)evaluateExpression:(in bycopy NSString *)expr
1210 NSString *eval = nil;
1211 char_u *s = (char_u*)[expr UTF8String];
1214 s = CONVERT_FROM_UTF8(s);
1217 char_u *res = eval_client_expr_to_string(s);
1220 CONVERT_FROM_UTF8_FREE(s);
1226 s = CONVERT_TO_UTF8(s);
1228 eval = [NSString stringWithUTF8String:(char*)s];
1230 CONVERT_TO_UTF8_FREE(s);
1238 - (BOOL)starRegisterToPasteboard:(byref NSPasteboard *)pboard
1240 // TODO: This method should share code with clip_mch_request_selection().
1242 if (VIsual_active && (State & NORMAL) && clip_star.available) {
1243 // If there is no pasteboard, return YES to indicate that there is text
1248 clip_copy_selection();
1250 // Get the text to put on the pasteboard.
1251 long_u llen = 0; char_u *str = 0;
1252 int type = clip_convert_selection(&str, &llen, &clip_star);
1256 // TODO: Avoid overflow.
1257 int len = (int)llen;
1259 if (output_conv.vc_type != CONV_NONE) {
1260 char_u *conv_str = string_convert(&output_conv, str, &len);
1268 NSString *string = [[NSString alloc]
1269 initWithBytes:str length:len encoding:NSUTF8StringEncoding];
1271 NSArray *types = [NSArray arrayWithObject:NSStringPboardType];
1272 [pboard declareTypes:types owner:nil];
1273 BOOL ok = [pboard setString:string forType:NSStringPboardType];
1284 - (oneway void)addReply:(in bycopy NSString *)reply
1285 server:(in byref id <MMVimServerProtocol>)server
1287 ASLogDebug(@"reply=%@ server=%@", reply, (id)server);
1289 // Replies might come at any time and in any order so we keep them in an
1290 // array inside a dictionary with the send port used as key.
1292 NSConnection *conn = [(NSDistantObject*)server connectionForProxy];
1293 // HACK! Assume connection uses mach ports.
1294 int port = [(NSMachPort*)[conn sendPort] machPort];
1295 NSNumber *key = [NSNumber numberWithInt:port];
1297 NSMutableArray *replies = [serverReplyDict objectForKey:key];
1299 replies = [NSMutableArray array];
1300 [serverReplyDict setObject:replies forKey:key];
1303 [replies addObject:reply];
1306 - (void)addInput:(in bycopy NSString *)input
1307 client:(in byref id <MMVimClientProtocol>)client
1309 ASLogDebug(@"input=%@ client=%@", input, (id)client);
1311 // NOTE: We don't call addInput: here because it differs from
1312 // server_to_input_buf() in that it always sets the 'silent' flag and we
1313 // don't want the MacVim client/server code to behave differently from
1315 char_u *s = [input vimStringSave];
1316 server_to_input_buf(s);
1319 [self addClient:(id)client];
1322 - (NSString *)evaluateExpression:(in bycopy NSString *)expr
1323 client:(in byref id <MMVimClientProtocol>)client
1325 [self addClient:(id)client];
1326 return [self evaluateExpression:expr];
1329 - (void)registerServerWithName:(NSString *)name
1331 NSString *svrName = name;
1334 if (vimServerConnection) // Paranoia check, should always be nil
1335 [vimServerConnection release];
1337 vimServerConnection = [[NSConnection alloc]
1338 initWithReceivePort:[NSPort port]
1341 for (i = 0; i < MMServerMax; ++i) {
1342 NSString *connName = [self connectionNameFromServerName:svrName];
1344 if ([vimServerConnection registerName:connName]) {
1345 ASLogInfo(@"Registered server with name: %@", svrName);
1347 // TODO: Set request/reply time-outs to something else?
1349 // Don't wait for requests (time-out means that the message is
1351 [vimServerConnection setRequestTimeout:0];
1352 //[vimServerConnection setReplyTimeout:MMReplyTimeout];
1353 [vimServerConnection setRootObject:self];
1355 // NOTE: 'serverName' is a global variable
1356 serverName = [svrName vimStringSave];
1358 set_vim_var_string(VV_SEND_SERVER, serverName, -1);
1361 need_maketitle = TRUE;
1363 [self queueMessage:SetServerNameMsgID
1364 data:[svrName dataUsingEncoding:NSUTF8StringEncoding]];
1368 svrName = [NSString stringWithFormat:@"%@%d", name, i+1];
1372 - (BOOL)sendToServer:(NSString *)name string:(NSString *)string
1373 reply:(char_u **)reply port:(int *)port expression:(BOOL)expr
1376 // NOTE: If 'name' equals 'serverName' then the request is local (client
1377 // and server are the same). This case is not handled separately, so a
1378 // connection will be set up anyway (this simplifies the code).
1380 NSConnection *conn = [self connectionForServerName:name];
1383 char_u *s = (char_u*)[name UTF8String];
1385 s = CONVERT_FROM_UTF8(s);
1387 EMSG2(_(e_noserver), s);
1389 CONVERT_FROM_UTF8_FREE(s);
1396 // HACK! Assume connection uses mach ports.
1397 *port = [(NSMachPort*)[conn sendPort] machPort];
1400 id proxy = [conn rootProxy];
1401 [proxy setProtocolForProxy:@protocol(MMVimServerProtocol)];
1405 NSString *eval = [proxy evaluateExpression:string client:self];
1408 *reply = [eval vimStringSave];
1410 *reply = vim_strsave((char_u*)_(e_invexprmsg));
1417 [proxy addInput:string client:self];
1420 @catch (NSException *ex) {
1421 ASLogDebug(@"Exception: reason=%@", ex);
1428 - (NSArray *)serverList
1430 NSArray *list = nil;
1432 if ([self connection]) {
1433 id proxy = [connection rootProxy];
1434 [proxy setProtocolForProxy:@protocol(MMAppProtocol)];
1437 list = [proxy serverList];
1439 @catch (NSException *ex) {
1440 ASLogDebug(@"serverList failed: reason=%@", ex);
1443 // We get here if a --remote flag is used before MacVim has started.
1444 ASLogInfo(@"No connection to MacVim, server listing not possible.");
1450 - (NSString *)peekForReplyOnPort:(int)port
1452 ASLogDebug(@"port=%d", port);
1454 NSNumber *key = [NSNumber numberWithInt:port];
1455 NSMutableArray *replies = [serverReplyDict objectForKey:key];
1456 if (replies && [replies count]) {
1457 ASLogDebug(@" %d replies, topmost is: %@", [replies count],
1458 [replies objectAtIndex:0]);
1459 return [replies objectAtIndex:0];
1462 ASLogDebug(@" No replies");
1466 - (NSString *)waitForReplyOnPort:(int)port
1468 ASLogDebug(@"port=%d", port);
1470 NSConnection *conn = [self connectionForServerPort:port];
1474 NSNumber *key = [NSNumber numberWithInt:port];
1475 NSMutableArray *replies = nil;
1476 NSString *reply = nil;
1478 // Wait for reply as long as the connection to the server is valid (unless
1479 // user interrupts wait with Ctrl-C).
1480 while (!got_int && [conn isValid] &&
1481 !(replies = [serverReplyDict objectForKey:key])) {
1482 [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
1483 beforeDate:[NSDate distantFuture]];
1487 if ([replies count] > 0) {
1488 reply = [[replies objectAtIndex:0] retain];
1489 ASLogDebug(@" Got reply: %@", reply);
1490 [replies removeObjectAtIndex:0];
1491 [reply autorelease];
1494 if ([replies count] == 0)
1495 [serverReplyDict removeObjectForKey:key];
1501 - (BOOL)sendReply:(NSString *)reply toPort:(int)port
1503 id client = [clientProxyDict objectForKey:[NSNumber numberWithInt:port]];
1506 ASLogDebug(@"reply=%@ port=%d", reply, port);
1507 [client addReply:reply server:self];
1510 @catch (NSException *ex) {
1511 ASLogDebug(@"addReply:server: failed: reason=%@", ex);
1514 ASLogNotice(@"server2client failed; no client with id %d", port);
1525 - (void)setWaitForAck:(BOOL)yn
1530 - (void)waitForConnectionAcknowledgement
1532 if (!waitForAck) return;
1534 while (waitForAck && !got_int && [connection isValid]) {
1535 [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
1536 beforeDate:[NSDate distantFuture]];
1537 ASLogDebug(@" waitForAck=%d got_int=%d isValid=%d",
1538 waitForAck, got_int, [connection isValid]);
1542 ASLogDebug(@"Never received a connection acknowledgement");
1543 [[NSNotificationCenter defaultCenter] removeObserver:self];
1544 [appProxy release]; appProxy = nil;
1546 // NOTE: We intentionally do not call mch_exit() since this in turn
1547 // will lead to -[MMBackend exit] getting called which we want to
1552 ASLogInfo(@"Connection acknowledgement received");
1553 [self processInputQueue];
1556 - (oneway void)acknowledgeConnection
1567 - (void)setImState:(BOOL)activated
1569 imState = activated;
1572 static void netbeansReadCallback(CFSocketRef s,
1573 CFSocketCallBackType callbackType,
1578 // NetBeans socket is readable.
1579 [[MMBackend sharedInstance] messageFromNetbeans];
1582 - (void)messageFromNetbeans
1584 [inputQueue addObject:[NSNumber numberWithInt:NetBeansMsgID]];
1585 [inputQueue addObject:[NSNull null]];
1588 - (void)setNetbeansSocket:(int)socket
1590 if (netbeansSocket) {
1591 CFRelease(netbeansSocket);
1592 netbeansSocket = NULL;
1595 if (netbeansRunLoopSource) {
1596 CFRunLoopSourceInvalidate(netbeansRunLoopSource);
1597 netbeansRunLoopSource = NULL;
1603 // Tell CFRunLoop that we are interested in NetBeans socket input.
1604 netbeansSocket = CFSocketCreateWithNative(kCFAllocatorDefault,
1606 kCFSocketReadCallBack,
1607 &netbeansReadCallback,
1609 netbeansRunLoopSource = CFSocketCreateRunLoopSource(NULL,
1612 CFRunLoopAddSource(CFRunLoopGetCurrent(),
1613 netbeansRunLoopSource,
1614 kCFRunLoopCommonModes);
1621 @implementation MMBackend (Private)
1623 - (void)clearDrawData
1625 [drawData setLength:0];
1626 numWholeLineChanges = offsetForDrawDataPrune = 0;
1629 - (void)didChangeWholeLine
1631 // It may happen that draw queue is filled up with lots of changes that
1632 // affect a whole row. If the number of such changes equals twice the
1633 // number of visible rows then we can prune some commands off the queue.
1635 // NOTE: If we don't perform this pruning the draw queue may grow
1636 // indefinitely if Vim were to repeatedly send draw commands without ever
1637 // waiting for new input (that's when the draw queue is flushed). The one
1638 // instance I know where this can happen is when a command is executed in
1639 // the shell (think ":grep" with thousands of matches).
1641 ++numWholeLineChanges;
1642 if (numWholeLineChanges == gui.num_rows) {
1643 // Remember the offset to prune up to.
1644 offsetForDrawDataPrune = [drawData length];
1645 } else if (numWholeLineChanges == 2*gui.num_rows) {
1646 // Delete all the unnecessary draw commands.
1647 NSMutableData *d = [[NSMutableData alloc]
1648 initWithBytes:[drawData bytes] + offsetForDrawDataPrune
1649 length:[drawData length] - offsetForDrawDataPrune];
1650 offsetForDrawDataPrune = [d length];
1651 numWholeLineChanges -= gui.num_rows;
1657 - (void)waitForDialogReturn
1659 // Keep processing the run loop until a dialog returns. To avoid getting
1660 // stuck in an endless loop (could happen if the setDialogReturn: message
1661 // was lost) we also do some paranoia checks.
1663 // Note that in Cocoa the user can still resize windows and select menu
1664 // items while a sheet is being displayed, so we can't just wait for the
1665 // first message to arrive and assume that is the setDialogReturn: call.
1667 while (nil == dialogReturn && !got_int && [connection isValid])
1668 [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
1669 beforeDate:[NSDate distantFuture]];
1671 // Search for any resize messages on the input queue. All other messages
1672 // on the input queue are dropped. The reason why we single out resize
1673 // messages is because the user may have resized the window while a sheet
1675 int i, count = [inputQueue count];
1677 id textDimData = nil;
1679 for (i = count-2; i >= 0; i -= 2) {
1680 int msgid = [[inputQueue objectAtIndex:i] intValue];
1681 if (SetTextDimensionsMsgID == msgid) {
1682 textDimData = [[inputQueue objectAtIndex:i+1] retain];
1688 [inputQueue removeAllObjects];
1691 [inputQueue addObject:
1692 [NSNumber numberWithInt:SetTextDimensionsMsgID]];
1693 [inputQueue addObject:textDimData];
1694 [textDimData release];
1699 - (void)insertVimStateMessage
1701 // NOTE: This is the place to add Vim state that needs to be accessed from
1702 // MacVim. Do not add state that could potentially require lots of memory
1703 // since this message gets sent each time the output queue is forcibly
1704 // flushed (e.g. storing the currently selected text would be a bad idea).
1705 // We take this approach of "pushing" the state to MacVim to avoid having
1706 // to make synchronous calls from MacVim to Vim in order to get state.
1708 BOOL mmta = curbuf ? curbuf->b_p_mmta : NO;
1709 int numTabs = tabpage_index(NULL) - 1;
1713 NSDictionary *vimState = [NSDictionary dictionaryWithObjectsAndKeys:
1714 [[NSFileManager defaultManager] currentDirectoryPath], @"pwd",
1715 [NSNumber numberWithInt:p_mh], @"p_mh",
1716 [NSNumber numberWithBool:[self unusedEditor]], @"unusedEditor",
1717 [NSNumber numberWithBool:mmta], @"p_mmta",
1718 [NSNumber numberWithInt:numTabs], @"numTabs",
1721 // Put the state before all other messages.
1722 int msgid = SetVimStateMsgID;
1723 [outputQueue insertObject:[vimState dictionaryAsData] atIndex:0];
1724 [outputQueue insertObject:[NSData dataWithBytes:&msgid length:sizeof(int)]
1728 - (void)processInputQueue
1730 if ([inputQueue count] == 0) return;
1732 // NOTE: One of the input events may cause this method to be called
1733 // recursively, so copy the input queue to a local variable and clear the
1734 // queue before starting to process input events (otherwise we could get
1735 // stuck in an endless loop).
1736 NSArray *q = [inputQueue copy];
1737 unsigned i, count = [q count];
1739 [inputQueue removeAllObjects];
1741 for (i = 1; i < count; i+=2) {
1742 int msgid = [[q objectAtIndex:i-1] intValue];
1743 id data = [q objectAtIndex:i];
1744 if ([data isEqual:[NSNull null]])
1747 ASLogDebug(@"(%d) %s", i, MessageStrings[msgid]);
1748 [self handleInputEvent:msgid data:data];
1755 - (void)handleInputEvent:(int)msgid data:(NSData *)data
1757 if (KeyDownMsgID == msgid) {
1759 const void *bytes = [data bytes];
1760 unsigned mods = *((unsigned*)bytes); bytes += sizeof(unsigned);
1761 unsigned code = *((unsigned*)bytes); bytes += sizeof(unsigned);
1762 unsigned len = *((unsigned*)bytes); bytes += sizeof(unsigned);
1763 NSString *key = [[NSString alloc] initWithBytes:bytes
1765 encoding:NSUTF8StringEncoding];
1766 mods = eventModifierFlagsToVimModMask(mods);
1768 [self doKeyDown:key keyCode:code modifiers:mods];
1770 } else if (ScrollWheelMsgID == msgid) {
1772 const void *bytes = [data bytes];
1774 int row = *((int*)bytes); bytes += sizeof(int);
1775 int col = *((int*)bytes); bytes += sizeof(int);
1776 int flags = *((int*)bytes); bytes += sizeof(int);
1777 float dy = *((float*)bytes); bytes += sizeof(float);
1779 int button = MOUSE_5;
1780 if (dy > 0) button = MOUSE_4;
1782 flags = eventModifierFlagsToVimMouseModMask(flags);
1784 int numLines = (int)round(dy);
1785 if (numLines < 0) numLines = -numLines;
1786 if (numLines == 0) numLines = 1;
1788 #ifdef FEAT_GUI_SCROLL_WHEEL_FORCE
1789 gui.scroll_wheel_force = numLines;
1792 gui_send_mouse_event(button, col, row, NO, flags);
1793 } else if (MouseDownMsgID == msgid) {
1795 const void *bytes = [data bytes];
1797 int row = *((int*)bytes); bytes += sizeof(int);
1798 int col = *((int*)bytes); bytes += sizeof(int);
1799 int button = *((int*)bytes); bytes += sizeof(int);
1800 int flags = *((int*)bytes); bytes += sizeof(int);
1801 int count = *((int*)bytes); bytes += sizeof(int);
1803 button = eventButtonNumberToVimMouseButton(button);
1805 flags = eventModifierFlagsToVimMouseModMask(flags);
1806 gui_send_mouse_event(button, col, row, count>1, flags);
1808 } else if (MouseUpMsgID == msgid) {
1810 const void *bytes = [data bytes];
1812 int row = *((int*)bytes); bytes += sizeof(int);
1813 int col = *((int*)bytes); bytes += sizeof(int);
1814 int flags = *((int*)bytes); bytes += sizeof(int);
1816 flags = eventModifierFlagsToVimMouseModMask(flags);
1818 gui_send_mouse_event(MOUSE_RELEASE, col, row, NO, flags);
1819 } else if (MouseDraggedMsgID == msgid) {
1821 const void *bytes = [data bytes];
1823 int row = *((int*)bytes); bytes += sizeof(int);
1824 int col = *((int*)bytes); bytes += sizeof(int);
1825 int flags = *((int*)bytes); bytes += sizeof(int);
1827 flags = eventModifierFlagsToVimMouseModMask(flags);
1829 gui_send_mouse_event(MOUSE_DRAG, col, row, NO, flags);
1830 } else if (MouseMovedMsgID == msgid) {
1831 const void *bytes = [data bytes];
1832 int row = *((int*)bytes); bytes += sizeof(int);
1833 int col = *((int*)bytes); bytes += sizeof(int);
1835 gui_mouse_moved(col, row);
1836 } else if (AddInputMsgID == msgid) {
1837 NSString *string = [[NSString alloc] initWithData:data
1838 encoding:NSUTF8StringEncoding];
1840 [self addInput:string];
1843 } else if (SelectTabMsgID == msgid) {
1845 const void *bytes = [data bytes];
1846 int idx = *((int*)bytes) + 1;
1847 send_tabline_event(idx);
1848 } else if (CloseTabMsgID == msgid) {
1850 const void *bytes = [data bytes];
1851 int idx = *((int*)bytes) + 1;
1852 send_tabline_menu_event(idx, TABLINE_MENU_CLOSE);
1853 } else if (AddNewTabMsgID == msgid) {
1854 send_tabline_menu_event(0, TABLINE_MENU_NEW);
1855 } else if (DraggedTabMsgID == msgid) {
1857 const void *bytes = [data bytes];
1858 // NOTE! The destination index is 0 based, so do not add 1 to make it 1
1860 int idx = *((int*)bytes);
1863 } else if (SetTextDimensionsMsgID == msgid || LiveResizeMsgID == msgid
1864 || SetTextRowsMsgID == msgid || SetTextColumnsMsgID == msgid) {
1866 const void *bytes = [data bytes];
1868 if (SetTextColumnsMsgID != msgid) {
1869 rows = *((int*)bytes); bytes += sizeof(int);
1872 if (SetTextRowsMsgID != msgid) {
1873 cols = *((int*)bytes); bytes += sizeof(int);
1877 if (SetTextRowsMsgID == msgid || SetTextColumnsMsgID == msgid) {
1878 int dim[2] = { rows, cols };
1879 d = [NSData dataWithBytes:dim length:2*sizeof(int)];
1880 msgid = SetTextDimensionsReplyMsgID;
1883 if (SetTextDimensionsMsgID == msgid)
1884 msgid = SetTextDimensionsReplyMsgID;
1886 // NOTE! Vim doesn't call gui_mch_set_shellsize() after
1887 // gui_resize_shell(), so we have to manually set the rows and columns
1888 // here since MacVim doesn't change the rows and columns to avoid
1889 // inconsistent states between Vim and MacVim. The message sent back
1890 // indicates that it is a reply to a message that originated in MacVim
1891 // since we need to be able to determine where a message originated.
1892 [self queueMessage:msgid data:d];
1894 gui_resize_shell(cols, rows);
1895 } else if (ExecuteMenuMsgID == msgid) {
1896 NSDictionary *attrs = [NSDictionary dictionaryWithData:data];
1898 NSArray *desc = [attrs objectForKey:@"descriptor"];
1899 vimmenu_T *menu = menu_for_descriptor(desc);
1903 } else if (ToggleToolbarMsgID == msgid) {
1904 [self handleToggleToolbar];
1905 } else if (ScrollbarEventMsgID == msgid) {
1906 [self handleScrollbarEvent:data];
1907 } else if (SetFontMsgID == msgid) {
1908 [self handleSetFont:data];
1909 } else if (VimShouldCloseMsgID == msgid) {
1911 } else if (DropFilesMsgID == msgid) {
1912 [self handleDropFiles:data];
1913 } else if (DropStringMsgID == msgid) {
1914 [self handleDropString:data];
1915 } else if (GotFocusMsgID == msgid) {
1917 [self focusChange:YES];
1918 } else if (LostFocusMsgID == msgid) {
1920 [self focusChange:NO];
1921 } else if (SetMouseShapeMsgID == msgid) {
1922 const void *bytes = [data bytes];
1923 int shape = *((int*)bytes); bytes += sizeof(int);
1924 update_mouseshape(shape);
1925 } else if (XcodeModMsgID == msgid) {
1926 [self handleXcodeMod:data];
1927 } else if (OpenWithArgumentsMsgID == msgid) {
1928 [self handleOpenWithArguments:[NSDictionary dictionaryWithData:data]];
1929 } else if (FindReplaceMsgID == msgid) {
1930 [self handleFindReplace:[NSDictionary dictionaryWithData:data]];
1931 } else if (ActivatedImMsgID == msgid) {
1932 [self setImState:YES];
1933 } else if (DeactivatedImMsgID == msgid) {
1934 [self setImState:NO];
1935 } else if (NetBeansMsgID == msgid) {
1936 #ifdef FEAT_NETBEANS_INTG
1937 messageFromNetbeansMacVim();
1939 } else if (SetMarkedTextMsgID == msgid) {
1940 [self handleMarkedText:data];
1941 } else if (ZoomMsgID == msgid) {
1943 const void *bytes = [data bytes];
1944 int rows = *((int*)bytes); bytes += sizeof(int);
1945 int cols = *((int*)bytes); bytes += sizeof(int);
1946 //int zoom = *((int*)bytes); bytes += sizeof(int);
1948 // NOTE: The frontend sends zoom messages here causing us to
1949 // immediately resize the shell and mirror the message back to the
1950 // frontend. This is done to ensure that the draw commands reach the
1951 // frontend before the window actually changes size in order to avoid
1952 // flickering. (Also see comment in SetTextDimensionsReplyMsgID
1953 // regarding resizing.)
1954 [self queueMessage:ZoomMsgID data:data];
1955 gui_resize_shell(cols, rows);
1957 ASLogWarn(@"Unknown message received (msgid=%d)", msgid);
1961 - (void)doKeyDown:(NSString *)key
1962 keyCode:(unsigned)code
1965 ASLogDebug(@"key='%@' code=%#x mods=%#x length=%d", key, code, mods,
1969 char_u *str = (char_u*)[key UTF8String];
1970 int i, len = [key lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
1972 if ([self handleSpecialKey:key keyCode:code modifiers:mods])
1976 char_u *conv_str = NULL;
1977 if (input_conv.vc_type != CONV_NONE) {
1978 conv_str = string_convert(&input_conv, str, &len);
1984 if (mods & MOD_MASK_CMD) {
1985 // NOTE: For normal input (non-special, 'macmeta' off) the modifier
1986 // flags are already included in the key event. However, the Cmd key
1987 // flag is special and must always be added manually.
1988 // The Shift flag is already included in the key when the Command
1989 // key is held. The same goes for Alt, unless Ctrl is held or
1990 // 'macmeta' is set. It is important that these flags are cleared
1991 // _after_ special keys have been handled, since they should never be
1992 // cleared for special keys.
1993 mods &= ~MOD_MASK_SHIFT;
1994 if (!(mods & MOD_MASK_CTRL)) {
1995 BOOL mmta = curbuf ? curbuf->b_p_mmta : YES;
1997 mods &= ~MOD_MASK_ALT;
2000 ASLogDebug(@"add mods=%#x", mods);
2001 char_u modChars[3] = { CSI, KS_MODIFIER, mods };
2002 add_to_input_buf(modChars, 3);
2003 } else if (mods & MOD_MASK_ALT && 1 == len && str[0] < 0x80
2004 && curbuf && curbuf->b_p_mmta) {
2005 // HACK! The 'macmeta' is set so we have to handle Alt key presses
2006 // separately. Normally Alt key presses are interpreted by the
2007 // frontend but now we have to manually set the 8th bit and deal with
2008 // UTF-8 conversion.
2009 if ([self handleMacMetaKey:str[0] modifiers:mods])
2014 for (i = 0; i < len; ++i) {
2015 ASLogDebug(@"add byte [%d/%d]: %#x", i, len, str[i]);
2016 add_to_input_buf(str+i, 1);
2017 if (CSI == str[i]) {
2018 // NOTE: If the converted string contains the byte CSI, then it
2019 // must be followed by the bytes KS_EXTRA, KE_CSI or things
2021 static char_u extra[2] = { KS_EXTRA, KE_CSI };
2022 ASLogDebug(@"add KS_EXTRA, KE_CSI");
2023 add_to_input_buf(extra, 2);
2033 - (BOOL)handleSpecialKey:(NSString *)key
2034 keyCode:(unsigned)code
2038 for (i = 0; special_keys[i].key_sym != 0; i++) {
2039 if (special_keys[i].key_sym == code) {
2040 ASLogDebug(@"Special key: %#x", code);
2044 if (special_keys[i].key_sym == 0)
2047 int ikey = special_keys[i].vim_code1 == NUL ? special_keys[i].vim_code0 :
2048 TO_SPECIAL(special_keys[i].vim_code0, special_keys[i].vim_code1);
2049 ikey = simplify_key(ikey, &mods);
2056 if (IS_SPECIAL(ikey)) {
2058 chars[1] = K_SECOND(ikey);
2059 chars[2] = K_THIRD(ikey);
2061 } else if (mods & MOD_MASK_ALT && special_keys[i].vim_code1 == 0
2063 && !enc_dbcs // TODO: ? (taken from gui_gtk_x11.c)
2066 ASLogDebug(@"Alt special=%d", ikey);
2068 // NOTE: The last entries in the special_keys struct when pressed
2069 // together with Alt need to be handled separately or they will not
2071 // The following code was gleaned from gui_gtk_x11.c.
2072 mods &= ~MOD_MASK_ALT;
2073 int mkey = 0x80 | ikey;
2075 if (enc_utf8) { // TODO: What about other encodings?
2077 chars[0] = (mkey >> 6) + 0xc0;
2078 chars[1] = mkey & 0xbf;
2079 if (chars[1] == CSI) {
2080 // We end up here when ikey == ESC
2081 chars[2] = KS_EXTRA;
2094 ASLogDebug(@"Just ikey=%d", ikey);
2101 ASLogDebug(@"Adding mods to special: %d", mods);
2102 char_u modChars[3] = { CSI, KS_MODIFIER, (char_u)mods };
2103 add_to_input_buf(modChars, 3);
2106 ASLogDebug(@"Adding special (%d): %x,%x,%x", len,
2107 chars[0], chars[1], chars[2]);
2108 add_to_input_buf(chars, len);
2114 - (BOOL)handleMacMetaKey:(int)ikey modifiers:(int)mods
2116 ASLogDebug(@"ikey=%d mods=%d", ikey, mods);
2118 // This code was taken from gui_w48.c and gui_gtk_x11.c.
2120 int ch = simplify_key(ikey, &mods);
2122 // Remove the SHIFT modifier for keys where it's already included,
2123 // e.g., '(' and '*'
2124 if (ch < 0x100 && !isalpha(ch) && isprint(ch))
2125 mods &= ~MOD_MASK_SHIFT;
2127 // Interpret the ALT key as making the key META, include SHIFT, etc.
2128 ch = extract_modifiers(ch, &mods);
2134 string[len++] = CSI;
2135 string[len++] = KS_MODIFIER;
2136 string[len++] = mods;
2141 // TODO: What if 'enc' is not "utf-8"?
2142 if (enc_utf8 && (ch & 0x80)) { // convert to utf-8
2143 string[len++] = ch & 0xbf;
2144 string[len-2] = ((unsigned)ch >> 6) + 0xc0;
2145 if (string[len-1] == CSI) {
2146 string[len++] = KS_EXTRA;
2147 string[len++] = (int)KE_CSI;
2152 add_to_input_buf(string, len);
2156 - (void)queueMessage:(int)msgid data:(NSData *)data
2158 [outputQueue addObject:[NSData dataWithBytes:&msgid length:sizeof(int)]];
2160 [outputQueue addObject:data];
2162 [outputQueue addObject:[NSData data]];
2165 - (void)connectionDidDie:(NSNotification *)notification
2167 // If the main connection to MacVim is lost this means that either MacVim
2168 // has crashed or this process did not receive its termination message
2169 // properly (e.g. if the TerminateNowMsgID was dropped).
2171 // NOTE: This is not called if a Vim controller invalidates its connection.
2173 ASLogNotice(@"Main connection was lost before process had a chance "
2174 "to terminate; preserving swap files.");
2175 getout_preserve_modified(1);
2178 - (void)blinkTimerFired:(NSTimer *)timer
2180 NSTimeInterval timeInterval = 0;
2182 [blinkTimer release];
2185 if (MMBlinkStateOn == blinkState) {
2186 gui_undraw_cursor();
2187 blinkState = MMBlinkStateOff;
2188 timeInterval = blinkOffInterval;
2189 } else if (MMBlinkStateOff == blinkState) {
2190 gui_update_cursor(TRUE, FALSE);
2191 blinkState = MMBlinkStateOn;
2192 timeInterval = blinkOnInterval;
2195 if (timeInterval > 0) {
2197 [[NSTimer scheduledTimerWithTimeInterval:timeInterval target:self
2198 selector:@selector(blinkTimerFired:)
2199 userInfo:nil repeats:NO] retain];
2200 [self flushQueue:YES];
2204 - (void)focusChange:(BOOL)on
2206 gui_focus_change(on);
2209 - (void)handleToggleToolbar
2211 // If 'go' contains 'T', then remove it, else add it.
2213 char_u go[sizeof(GO_ALL)+2];
2218 p = vim_strchr(go, GO_TOOLBAR);
2222 char_u *end = go + len;
2228 go[len] = GO_TOOLBAR;
2232 set_option_value((char_u*)"guioptions", 0, go, 0);
2235 - (void)handleScrollbarEvent:(NSData *)data
2239 const void *bytes = [data bytes];
2240 int32_t ident = *((int32_t*)bytes); bytes += sizeof(int32_t);
2241 int hitPart = *((int*)bytes); bytes += sizeof(int);
2242 float fval = *((float*)bytes); bytes += sizeof(float);
2243 scrollbar_T *sb = gui_find_scrollbar(ident);
2246 scrollbar_T *sb_info = sb->wp ? &sb->wp->w_scrollbars[0] : sb;
2247 long value = sb_info->value;
2248 long size = sb_info->size;
2249 long max = sb_info->max;
2250 BOOL isStillDragging = NO;
2251 BOOL updateKnob = YES;
2254 case NSScrollerDecrementPage:
2255 value -= (size > 2 ? size - 2 : 1);
2257 case NSScrollerIncrementPage:
2258 value += (size > 2 ? size - 2 : 1);
2260 case NSScrollerDecrementLine:
2263 case NSScrollerIncrementLine:
2266 case NSScrollerKnob:
2267 isStillDragging = YES;
2269 case NSScrollerKnobSlot:
2270 value = (long)(fval * (max - size + 1));
2277 gui_drag_scrollbar(sb, value, isStillDragging);
2280 // Dragging the knob or option+clicking automatically updates
2281 // the knob position (on the actual NSScroller), so we only
2282 // need to set the knob position in the other cases.
2284 // Update both the left&right vertical scrollbars.
2285 int32_t idL = (int32_t)sb->wp->w_scrollbars[SBAR_LEFT].ident;
2286 int32_t idR = (int32_t)sb->wp->w_scrollbars[SBAR_RIGHT].ident;
2287 [self setScrollbarThumbValue:value size:size max:max
2289 [self setScrollbarThumbValue:value size:size max:max
2292 // Update the horizontal scrollbar.
2293 [self setScrollbarThumbValue:value size:size max:max
2300 - (void)handleSetFont:(NSData *)data
2304 const void *bytes = [data bytes];
2305 int pointSize = (int)*((float*)bytes); bytes += sizeof(float);
2307 unsigned len = *((unsigned*)bytes); bytes += sizeof(unsigned);
2308 NSMutableString *name = [NSMutableString stringWithUTF8String:bytes];
2311 [name appendString:[NSString stringWithFormat:@":h%d", pointSize]];
2312 char_u *s = (char_u*)[name UTF8String];
2314 unsigned wlen = *((unsigned*)bytes); bytes += sizeof(unsigned);
2317 NSMutableString *wname = [NSMutableString stringWithUTF8String:bytes];
2320 [wname appendString:[NSString stringWithFormat:@":h%d", pointSize]];
2321 ws = (char_u*)[wname UTF8String];
2325 s = CONVERT_FROM_UTF8(s);
2327 ws = CONVERT_FROM_UTF8(ws);
2331 set_option_value((char_u*)"guifont", 0, s, 0);
2333 if (ws && gui.wide_font != NOFONT) {
2334 // NOTE: This message is sent on Cmd-+/Cmd-- and as such should only
2335 // change the wide font if 'gfw' is non-empty (the frontend always has
2336 // some wide font set, even if 'gfw' is empty).
2337 set_option_value((char_u*)"guifontwide", 0, ws, 0);
2342 CONVERT_FROM_UTF8_FREE(ws);
2344 CONVERT_FROM_UTF8_FREE(s);
2347 [self redrawScreen];
2350 - (void)handleDropFiles:(NSData *)data
2352 // TODO: Get rid of this method; instead use Vim script directly. At the
2353 // moment I know how to do this to open files in tabs, but I'm not sure how
2354 // to add the filenames to the command line when in command line mode.
2358 NSMutableDictionary *args = [NSMutableDictionary dictionaryWithData:data];
2361 id obj = [args objectForKey:@"forceOpen"];
2362 BOOL forceOpen = YES;
2364 forceOpen = [obj boolValue];
2366 NSArray *filenames = [args objectForKey:@"filenames"];
2367 if (!(filenames && [filenames count] > 0)) return;
2370 if (!forceOpen && (State & CMDLINE)) {
2371 // HACK! If Vim is in command line mode then the files names
2372 // should be added to the command line, instead of opening the
2373 // files in tabs (unless forceOpen is set). This is taken care of by
2374 // gui_handle_drop().
2375 int n = [filenames count];
2376 char_u **fnames = (char_u **)alloc(n * sizeof(char_u *));
2379 for (i = 0; i < n; ++i)
2380 fnames[i] = [[filenames objectAtIndex:i] vimStringSave];
2382 // NOTE! This function will free 'fnames'.
2383 // HACK! It is assumed that the 'x' and 'y' arguments are
2384 // unused when in command line mode.
2385 gui_handle_drop(0, 0, 0, fnames, n);
2390 [self handleOpenWithArguments:args];
2394 - (void)handleDropString:(NSData *)data
2399 char_u dropkey[3] = { CSI, KS_EXTRA, (char_u)KE_DROP };
2400 const void *bytes = [data bytes];
2401 int len = *((int*)bytes); bytes += sizeof(int);
2402 NSMutableString *string = [NSMutableString stringWithUTF8String:bytes];
2404 // Replace unrecognized end-of-line sequences with \x0a (line feed).
2405 NSRange range = { 0, [string length] };
2406 unsigned n = [string replaceOccurrencesOfString:@"\x0d\x0a"
2407 withString:@"\x0a" options:0
2410 n = [string replaceOccurrencesOfString:@"\x0d" withString:@"\x0a"
2411 options:0 range:range];
2414 len = [string lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
2415 char_u *s = (char_u*)[string UTF8String];
2417 if (input_conv.vc_type != CONV_NONE)
2418 s = string_convert(&input_conv, s, &len);
2420 dnd_yank_drag_data(s, len);
2422 if (input_conv.vc_type != CONV_NONE)
2425 add_to_input_buf(dropkey, sizeof(dropkey));
2429 - (void)startOdbEditWithArguments:(NSDictionary *)args
2431 #ifdef FEAT_ODB_EDITOR
2432 id obj = [args objectForKey:@"remoteID"];
2435 OSType serverID = [obj unsignedIntValue];
2436 NSString *remotePath = [args objectForKey:@"remotePath"];
2438 NSAppleEventDescriptor *token = nil;
2439 NSData *tokenData = [args objectForKey:@"remoteTokenData"];
2440 obj = [args objectForKey:@"remoteTokenDescType"];
2441 if (tokenData && obj) {
2442 DescType tokenType = [obj unsignedLongValue];
2443 token = [NSAppleEventDescriptor descriptorWithDescriptorType:tokenType
2447 NSArray *filenames = [args objectForKey:@"filenames"];
2448 unsigned i, numFiles = [filenames count];
2449 for (i = 0; i < numFiles; ++i) {
2450 NSString *filename = [filenames objectAtIndex:i];
2451 char_u *s = [filename vimStringSave];
2452 buf_T *buf = buflist_findname(s);
2456 if (buf->b_odb_token) {
2457 [(NSAppleEventDescriptor*)(buf->b_odb_token) release];
2458 buf->b_odb_token = NULL;
2461 if (buf->b_odb_fname) {
2462 vim_free(buf->b_odb_fname);
2463 buf->b_odb_fname = NULL;
2466 buf->b_odb_server_id = serverID;
2469 buf->b_odb_token = [token retain];
2471 buf->b_odb_fname = [remotePath vimStringSave];
2473 ASLogWarn(@"Could not find buffer '%@' for ODB editing.", filename);
2476 #endif // FEAT_ODB_EDITOR
2479 - (void)handleXcodeMod:(NSData *)data
2482 const void *bytes = [data bytes];
2483 DescType type = *((DescType*)bytes); bytes += sizeof(DescType);
2484 unsigned len = *((unsigned*)bytes); bytes += sizeof(unsigned);
2488 NSAppleEventDescriptor *replyEvent = [NSAppleEventDescriptor
2489 descriptorWithDescriptorType:type
2495 - (void)handleOpenWithArguments:(NSDictionary *)args
2497 // ARGUMENT: DESCRIPTION:
2498 // -------------------------------------------------------------
2499 // filenames list of filenames
2500 // dontOpen don't open files specified in above argument
2501 // layout which layout to use to open files
2502 // selectionRange range of lines to select
2503 // searchText string to search for
2504 // cursorLine line to position the cursor on
2505 // cursorColumn column to position the cursor on
2506 // (only valid when "cursorLine" is set)
2507 // remoteID ODB parameter
2508 // remotePath ODB parameter
2509 // remoteTokenDescType ODB parameter
2510 // remoteTokenData ODB parameter
2512 ASLogDebug(@"args=%@ (starting=%d)", args, starting);
2514 NSArray *filenames = [args objectForKey:@"filenames"];
2515 int i, numFiles = filenames ? [filenames count] : 0;
2516 BOOL openFiles = ![[args objectForKey:@"dontOpen"] boolValue];
2517 int layout = [[args objectForKey:@"layout"] intValue];
2519 // Change to directory of first file to open if this is an "unused" editor
2520 // (but do not do this if editing remotely).
2521 if (openFiles && numFiles > 0 && ![args objectForKey:@"remoteID"]
2522 && (starting || [self unusedEditor]) ) {
2523 char_u *s = [[filenames objectAtIndex:0] vimStringSave];
2525 mch_chdir((char*)s);
2533 // When Vim is starting we simply add the files to be opened to the
2534 // global arglist and Vim will take care of opening them for us.
2535 if (openFiles && numFiles > 0) {
2536 for (i = 0; i < numFiles; i++) {
2537 NSString *fname = [filenames objectAtIndex:i];
2540 if (ga_grow(&global_alist.al_ga, 1) == FAIL
2541 || (p = [fname vimStringSave]) == NULL)
2542 exit(2); // See comment in -[MMBackend exit]
2544 alist_add(&global_alist, p, 2);
2547 // Vim will take care of arranging the files added to the arglist
2548 // in windows or tabs; all we must do is to specify which layout to
2550 initialWindowLayout = layout;
2553 // When Vim is already open we resort to some trickery to open the
2554 // files with the specified layout.
2556 // TODO: Figure out a better way to handle this?
2557 if (openFiles && numFiles > 0) {
2558 BOOL oneWindowInTab = topframe ? YES
2559 : (topframe->fr_layout == FR_LEAF);
2560 BOOL bufChanged = NO;
2561 BOOL bufHasFilename = NO;
2563 bufChanged = curbufIsChanged();
2564 bufHasFilename = curbuf->b_ffname != NULL;
2567 // Temporarily disable flushing since the following code may
2568 // potentially cause multiple redraws.
2569 flushDisabled = YES;
2571 BOOL onlyOneTab = (first_tabpage->tp_next == NULL);
2572 if (WIN_TABS == layout && !onlyOneTab) {
2573 // By going to the last tabpage we ensure that the new tabs
2574 // will appear last (if this call is left out, the taborder
2579 // Make sure we're in normal mode first.
2580 [self addInput:@"<C-\\><C-N>"];
2583 // With "split layout" we open a new tab before opening
2584 // multiple files if the current tab has more than one window
2585 // or if there is exactly one window but whose buffer has a
2586 // filename. (The :drop command ensures modified buffers get
2587 // their own window.)
2588 if ((WIN_HOR == layout || WIN_VER == layout) &&
2589 (!oneWindowInTab || bufHasFilename))
2590 [self addInput:@":tabnew<CR>"];
2592 // The files are opened by constructing a ":drop ..." command
2593 // and executing it.
2594 NSMutableString *cmd = (WIN_TABS == layout)
2595 ? [NSMutableString stringWithString:@":tab drop"]
2596 : [NSMutableString stringWithString:@":drop"];
2598 for (i = 0; i < numFiles; ++i) {
2599 NSString *file = [filenames objectAtIndex:i];
2600 file = [file stringByEscapingSpecialFilenameCharacters];
2601 [cmd appendString:@" "];
2602 [cmd appendString:file];
2605 // Temporarily clear 'suffixes' so that the files are opened in
2606 // the same order as they appear in the "filenames" array.
2607 [self addInput:@":let mvim_oldsu=&su|set su=<CR>"];
2609 [self addInput:cmd];
2611 // Split the view into multiple windows if requested.
2612 if (WIN_HOR == layout)
2613 [self addInput:@"|sall"];
2614 else if (WIN_VER == layout)
2615 [self addInput:@"|vert sall"];
2617 // Restore the old value of 'suffixes'.
2618 [self addInput:@"|let &su=mvim_oldsu|unlet mvim_oldsu<CR>"];
2620 // When opening one file we try to reuse the current window,
2621 // but not if its buffer is modified or has a filename.
2622 // However, the 'arglist' layout always opens the file in the
2624 NSString *file = [[filenames lastObject]
2625 stringByEscapingSpecialFilenameCharacters];
2627 if (WIN_HOR == layout) {
2628 if (!(bufHasFilename || bufChanged))
2629 cmd = [NSString stringWithFormat:@":e %@", file];
2631 cmd = [NSString stringWithFormat:@":sp %@", file];
2632 } else if (WIN_VER == layout) {
2633 if (!(bufHasFilename || bufChanged))
2634 cmd = [NSString stringWithFormat:@":e %@", file];
2636 cmd = [NSString stringWithFormat:@":vsp %@", file];
2637 } else if (WIN_TABS == layout) {
2638 if (oneWindowInTab && !(bufHasFilename || bufChanged))
2639 cmd = [NSString stringWithFormat:@":e %@", file];
2641 cmd = [NSString stringWithFormat:@":tabe %@", file];
2643 // (The :drop command will split if there is a modified
2645 cmd = [NSString stringWithFormat:@":drop %@", file];
2648 [self addInput:cmd];
2649 [self addInput:@"<CR>"];
2652 // Force screen redraw (does it have to be this complicated?).
2653 // (This code was taken from the end of gui_handle_drop().)
2654 update_screen(NOT_VALID);
2657 gui_update_cursor(FALSE, FALSE);
2664 if ([args objectForKey:@"remoteID"]) {
2665 // NOTE: We have to delay processing any ODB related arguments since
2666 // the file(s) may not be opened until the input buffer is processed.
2667 [self performSelector:@selector(startOdbEditWithArguments:)
2672 NSString *lineString = [args objectForKey:@"cursorLine"];
2673 if (lineString && [lineString intValue] > 0) {
2674 NSString *columnString = [args objectForKey:@"cursorColumn"];
2675 if (!(columnString && [columnString intValue] > 0))
2676 columnString = @"1";
2678 NSString *cmd = [NSString stringWithFormat:@"<C-\\><C-N>:cal "
2679 "cursor(%@,%@)|norm! zz<CR>:f<CR>", lineString, columnString];
2680 [self addInput:cmd];
2683 NSString *rangeString = [args objectForKey:@"selectionRange"];
2685 // Build a command line string that will select the given range of
2686 // lines. If range.length == 0, then position the cursor on the given
2687 // line but do not select.
2688 NSRange range = NSRangeFromString(rangeString);
2690 if (range.length > 0) {
2691 cmd = [NSString stringWithFormat:@"<C-\\><C-N>%dGV%dGz.0",
2692 NSMaxRange(range), range.location];
2694 cmd = [NSString stringWithFormat:@"<C-\\><C-N>%dGz.0",
2698 [self addInput:cmd];
2701 NSString *searchText = [args objectForKey:@"searchText"];
2703 [self addInput:[NSString stringWithFormat:@"<C-\\><C-N>gg/\\c%@/e<CR>",
2708 - (BOOL)checkForModifiedBuffers
2711 for (buf = firstbuf; buf != NULL; buf = buf->b_next) {
2712 if (bufIsChanged(buf)) {
2720 - (void)addInput:(NSString *)input
2722 // NOTE: This code is essentially identical to server_to_input_buf(),
2723 // except the 'silent' flag is TRUE in the call to ins_typebuf() below.
2724 char_u *string = [input vimStringSave];
2725 if (!string) return;
2727 /* Set 'cpoptions' the way we want it.
2728 * B set - backslashes are *not* treated specially
2729 * k set - keycodes are *not* reverse-engineered
2730 * < unset - <Key> sequences *are* interpreted
2731 * The last but one parameter of replace_termcodes() is TRUE so that the
2732 * <lt> sequence is recognised - needed for a real backslash.
2735 char_u *cpo_save = p_cpo;
2736 p_cpo = (char_u *)"Bk";
2737 char_u *str = replace_termcodes((char_u *)string, &ptr, FALSE, TRUE, FALSE);
2740 if (*ptr != NUL) /* trailing CTRL-V results in nothing */
2743 * Add the string to the input stream.
2744 * Can't use add_to_input_buf() here, we now have K_SPECIAL bytes.
2746 * First clear typed characters from the typeahead buffer, there could
2747 * be half a mapping there. Then append to the existing string, so
2748 * that multiple commands from a client are concatenated.
2750 if (typebuf.tb_maplen < typebuf.tb_len)
2751 del_typebuf(typebuf.tb_len - typebuf.tb_maplen, typebuf.tb_maplen);
2752 (void)ins_typebuf(str, REMAP_NONE, typebuf.tb_len, TRUE, TRUE);
2754 /* Let input_available() know we inserted text in the typeahead
2756 typebuf_was_filled = TRUE;
2762 - (BOOL)unusedEditor
2764 BOOL oneWindowInTab = topframe ? YES
2765 : (topframe->fr_layout == FR_LEAF);
2766 BOOL bufChanged = NO;
2767 BOOL bufHasFilename = NO;
2769 bufChanged = curbufIsChanged();
2770 bufHasFilename = curbuf->b_ffname != NULL;
2773 BOOL onlyOneTab = (first_tabpage->tp_next == NULL);
2775 return onlyOneTab && oneWindowInTab && !bufChanged && !bufHasFilename;
2778 - (void)redrawScreen
2780 // Force screen redraw (does it have to be this complicated?).
2781 redraw_all_later(CLEAR);
2782 update_screen(NOT_VALID);
2785 gui_update_cursor(FALSE, FALSE);
2787 // HACK! The cursor is not put back at the command line by the above
2788 // "redraw commands". The following test seems to do the trick though.
2789 if (State & CMDLINE)
2793 - (void)handleFindReplace:(NSDictionary *)args
2797 NSString *findString = [args objectForKey:@"find"];
2798 if (!findString) return;
2800 char_u *find = [findString vimStringSave];
2801 char_u *replace = [[args objectForKey:@"replace"] vimStringSave];
2802 int flags = [[args objectForKey:@"flags"] intValue];
2804 // NOTE: The flag 0x100 is used to indicate a backward search.
2805 gui_do_findrepl(flags, find, replace, (flags & 0x100) == 0);
2812 - (void)handleMarkedText:(NSData *)data
2814 const void *bytes = [data bytes];
2815 unsigned textlen = *((unsigned*)bytes); bytes += sizeof(unsigned);
2816 int32_t pos = *((int32_t*)bytes); bytes += sizeof(int32_t);
2817 unsigned len = *((unsigned*)bytes); bytes += sizeof(unsigned);
2818 char *chars = (char *)bytes;
2820 ASLogDebug(@"textlen=%d pos=%d len=%d chars=%s", textlen, pos, len, chars);
2823 im_preedit_abandon_macvim();
2824 } else if (textllen == 0) {
2825 im_preedit_end_macvim();
2827 if (!preedit_get_status())
2828 im_preedit_start_macvim();
2830 im_preedit_changed_macvim(chars, pos + len);
2834 @end // MMBackend (Private)
2839 @implementation MMBackend (ClientServer)
2841 - (NSString *)connectionNameFromServerName:(NSString *)name
2843 NSString *bundlePath = [[NSBundle mainBundle] bundlePath];
2845 return [[NSString stringWithFormat:@"%@.%@", bundlePath, name]
2849 - (NSConnection *)connectionForServerName:(NSString *)name
2851 // TODO: Try 'name%d' if 'name' fails.
2852 NSString *connName = [self connectionNameFromServerName:name];
2853 NSConnection *svrConn = [connectionNameDict objectForKey:connName];
2856 svrConn = [NSConnection connectionWithRegisteredName:connName
2858 // Try alternate server...
2859 if (!svrConn && alternateServerName) {
2860 ASLogInfo(@" trying to connect to alternate server: %@",
2861 alternateServerName);
2862 connName = [self connectionNameFromServerName:alternateServerName];
2863 svrConn = [NSConnection connectionWithRegisteredName:connName
2867 // Try looking for alternate servers...
2869 ASLogInfo(@" looking for alternate servers...");
2870 NSString *alt = [self alternateServerNameForName:name];
2871 if (alt != alternateServerName) {
2872 ASLogInfo(@" found alternate server: %@", alt);
2873 [alternateServerName release];
2874 alternateServerName = [alt copy];
2878 // Try alternate server again...
2879 if (!svrConn && alternateServerName) {
2880 ASLogInfo(@" trying to connect to alternate server: %@",
2881 alternateServerName);
2882 connName = [self connectionNameFromServerName:alternateServerName];
2883 svrConn = [NSConnection connectionWithRegisteredName:connName
2888 [connectionNameDict setObject:svrConn forKey:connName];
2890 ASLogDebug(@"Adding %@ as connection observer for %@",
2892 [[NSNotificationCenter defaultCenter] addObserver:self
2893 selector:@selector(serverConnectionDidDie:)
2894 name:NSConnectionDidDieNotification object:svrConn];
2901 - (NSConnection *)connectionForServerPort:(int)port
2904 NSEnumerator *e = [connectionNameDict objectEnumerator];
2906 while ((conn = [e nextObject])) {
2907 // HACK! Assume connection uses mach ports.
2908 if (port == [(NSMachPort*)[conn sendPort] machPort])
2915 - (void)serverConnectionDidDie:(NSNotification *)notification
2917 ASLogDebug(@"notification=%@", notification);
2919 NSConnection *svrConn = [notification object];
2921 ASLogDebug(@"Removing %@ as connection observer from %@", self, svrConn);
2922 [[NSNotificationCenter defaultCenter]
2924 name:NSConnectionDidDieNotification
2927 [connectionNameDict removeObjectsForKeys:
2928 [connectionNameDict allKeysForObject:svrConn]];
2930 // HACK! Assume connection uses mach ports.
2931 int port = [(NSMachPort*)[svrConn sendPort] machPort];
2932 NSNumber *key = [NSNumber numberWithInt:port];
2934 [clientProxyDict removeObjectForKey:key];
2935 [serverReplyDict removeObjectForKey:key];
2938 - (void)addClient:(NSDistantObject *)client
2940 NSConnection *conn = [client connectionForProxy];
2941 // HACK! Assume connection uses mach ports.
2942 int port = [(NSMachPort*)[conn sendPort] machPort];
2943 NSNumber *key = [NSNumber numberWithInt:port];
2945 if (![clientProxyDict objectForKey:key]) {
2946 [client setProtocolForProxy:@protocol(MMVimClientProtocol)];
2947 [clientProxyDict setObject:client forKey:key];
2950 // NOTE: 'clientWindow' is a global variable which is used by <client>
2951 clientWindow = port;
2954 - (NSString *)alternateServerNameForName:(NSString *)name
2956 if (!(name && [name length] > 0))
2959 // Only look for alternates if 'name' doesn't end in a digit.
2960 unichar lastChar = [name characterAtIndex:[name length]-1];
2961 if (lastChar >= '0' && lastChar <= '9')
2964 // Look for alternates among all current servers.
2965 NSArray *list = [self serverList];
2966 if (!(list && [list count] > 0))
2969 // Filter out servers starting with 'name' and ending with a number. The
2970 // (?i) pattern ensures that the match is case insensitive.
2971 NSString *pat = [NSString stringWithFormat:@"(?i)%@[0-9]+\\z", name];
2972 NSPredicate *pred = [NSPredicate predicateWithFormat:
2973 @"SELF MATCHES %@", pat];
2974 list = [list filteredArrayUsingPredicate:pred];
2975 if ([list count] > 0) {
2976 list = [list sortedArrayUsingSelector:@selector(serverNameCompare:)];
2977 return [list objectAtIndex:0];
2983 @end // MMBackend (ClientServer)
2988 @implementation NSString (MMServerNameCompare)
2989 - (NSComparisonResult)serverNameCompare:(NSString *)string
2991 return [self compare:string
2992 options:NSCaseInsensitiveSearch|NSNumericSearch];
2999 static int eventModifierFlagsToVimModMask(int modifierFlags)
3003 if (modifierFlags & NSShiftKeyMask)
3004 modMask |= MOD_MASK_SHIFT;
3005 if (modifierFlags & NSControlKeyMask)
3006 modMask |= MOD_MASK_CTRL;
3007 if (modifierFlags & NSAlternateKeyMask)
3008 modMask |= MOD_MASK_ALT;
3009 if (modifierFlags & NSCommandKeyMask)
3010 modMask |= MOD_MASK_CMD;
3015 static int eventModifierFlagsToVimMouseModMask(int modifierFlags)
3019 if (modifierFlags & NSShiftKeyMask)
3020 modMask |= MOUSE_SHIFT;
3021 if (modifierFlags & NSControlKeyMask)
3022 modMask |= MOUSE_CTRL;
3023 if (modifierFlags & NSAlternateKeyMask)
3024 modMask |= MOUSE_ALT;
3029 static int eventButtonNumberToVimMouseButton(int buttonNumber)
3031 static int mouseButton[] = { MOUSE_LEFT, MOUSE_RIGHT, MOUSE_MIDDLE };
3033 return (buttonNumber >= 0 && buttonNumber < 3)
3034 ? mouseButton[buttonNumber] : -1;
3039 // This function is modeled after the VimToPython function found in if_python.c
3040 // NB This does a deep copy by value, it does not lookup references like the
3041 // VimToPython function does. This is because I didn't want to deal with the
3042 // retain cycles that this would create, and we can cover 99% of the use cases
3043 // by ignoring it. If we ever switch to using GC in MacVim then this
3044 // functionality can be implemented easily.
3045 static id vimToCocoa(typval_T * tv, int depth)
3051 // Avoid infinite recursion
3056 if (tv->v_type == VAR_STRING) {
3057 char_u * val = tv->vval.v_string;
3058 // val can be NULL if the string is empty
3060 result = [NSString string];
3063 val = CONVERT_TO_UTF8(val);
3065 result = [NSString stringWithUTF8String:(char*)val];
3067 CONVERT_TO_UTF8_FREE(val);
3070 } else if (tv->v_type == VAR_NUMBER) {
3071 // looks like sizeof(varnumber_T) is always <= sizeof(long)
3072 result = [NSNumber numberWithLong:(long)tv->vval.v_number];
3073 } else if (tv->v_type == VAR_LIST) {
3074 list_T * list = tv->vval.v_list;
3077 NSMutableArray * arr = result = [NSMutableArray array];
3080 for (curr = list->lv_first; curr != NULL; curr = curr->li_next) {
3081 newObj = vimToCocoa(&curr->li_tv, depth + 1);
3082 [arr addObject:newObj];
3085 } else if (tv->v_type == VAR_DICT) {
3086 NSMutableDictionary * dict = result = [NSMutableDictionary dictionary];
3088 if (tv->vval.v_dict != NULL) {
3089 hashtab_T * ht = &tv->vval.v_dict->dv_hashtab;
3090 int todo = ht->ht_used;
3094 for (hi = ht->ht_array; todo > 0; ++hi) {
3095 if (!HASHITEM_EMPTY(hi)) {
3098 di = dict_lookup(hi);
3099 newObj = vimToCocoa(&di->di_tv, depth + 1);
3101 char_u * keyval = hi->hi_key;
3103 keyval = CONVERT_TO_UTF8(keyval);
3105 NSString * key = [NSString stringWithUTF8String:(char*)keyval];
3107 CONVERT_TO_UTF8_FREE(keyval);
3109 [dict setObject:newObj forKey:key];
3113 } else { // only func refs should fall into this category?
3121 // This function is modeled after eval_client_expr_to_string found in main.c
3122 // Returns nil if there was an error evaluating the expression, and writes a
3123 // message to errorStr.
3124 // TODO Get the error that occurred while evaluating the expression in vim
3126 static id evalExprCocoa(NSString * expr, NSString ** errstr)
3129 char_u *s = (char_u*)[expr UTF8String];
3132 s = CONVERT_FROM_UTF8(s);
3135 int save_dbl = debug_break_level;
3136 int save_ro = redir_off;
3138 debug_break_level = -1;
3142 typval_T * tvres = eval_expr(s, NULL);
3144 debug_break_level = save_dbl;
3145 redir_off = save_ro;
3152 CONVERT_FROM_UTF8_FREE(s);
3157 gui_update_cursor(FALSE, FALSE);
3160 if (tvres == NULL) {
3162 *errstr = @"Expression evaluation failed.";
3165 id res = vimToCocoa(tvres, 1);
3170 *errstr = @"Conversion to cocoa values failed.";
3178 @implementation NSString (VimStrings)
3180 + (id)stringWithVimString:(char_u *)s
3182 // This method ensures a non-nil string is returned. If 's' cannot be
3183 // converted to a utf-8 string it is assumed to be latin-1. If conversion
3184 // still fails an empty NSString is returned.
3185 NSString *string = nil;
3188 s = CONVERT_TO_UTF8(s);
3190 string = [NSString stringWithUTF8String:(char*)s];
3192 // HACK! Apparently 's' is not a valid utf-8 string, maybe it is
3194 string = [NSString stringWithCString:(char*)s
3195 encoding:NSISOLatin1StringEncoding];
3198 CONVERT_TO_UTF8_FREE(s);
3202 return string != nil ? string : [NSString string];
3205 - (char_u *)vimStringSave
3207 char_u *s = (char_u*)[self UTF8String], *ret = NULL;
3210 s = CONVERT_FROM_UTF8(s);
3212 ret = vim_strsave(s);
3214 CONVERT_FROM_UTF8_FREE(s);
3220 @end // NSString (VimStrings)