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:mmta], @"p_mmta",
1717 [NSNumber numberWithInt:numTabs], @"numTabs",
1720 // Put the state before all other messages.
1721 int msgid = SetVimStateMsgID;
1722 [outputQueue insertObject:[vimState dictionaryAsData] atIndex:0];
1723 [outputQueue insertObject:[NSData dataWithBytes:&msgid length:sizeof(int)]
1727 - (void)processInputQueue
1729 if ([inputQueue count] == 0) return;
1731 // NOTE: One of the input events may cause this method to be called
1732 // recursively, so copy the input queue to a local variable and clear the
1733 // queue before starting to process input events (otherwise we could get
1734 // stuck in an endless loop).
1735 NSArray *q = [inputQueue copy];
1736 unsigned i, count = [q count];
1738 [inputQueue removeAllObjects];
1740 for (i = 1; i < count; i+=2) {
1741 int msgid = [[q objectAtIndex:i-1] intValue];
1742 id data = [q objectAtIndex:i];
1743 if ([data isEqual:[NSNull null]])
1746 ASLogDebug(@"(%d) %s", i, MessageStrings[msgid]);
1747 [self handleInputEvent:msgid data:data];
1754 - (void)handleInputEvent:(int)msgid data:(NSData *)data
1756 if (KeyDownMsgID == msgid) {
1758 const void *bytes = [data bytes];
1759 unsigned mods = *((unsigned*)bytes); bytes += sizeof(unsigned);
1760 unsigned code = *((unsigned*)bytes); bytes += sizeof(unsigned);
1761 unsigned len = *((unsigned*)bytes); bytes += sizeof(unsigned);
1762 NSString *key = [[NSString alloc] initWithBytes:bytes
1764 encoding:NSUTF8StringEncoding];
1765 mods = eventModifierFlagsToVimModMask(mods);
1767 [self doKeyDown:key keyCode:code modifiers:mods];
1769 } else if (ScrollWheelMsgID == msgid) {
1771 const void *bytes = [data bytes];
1773 int row = *((int*)bytes); bytes += sizeof(int);
1774 int col = *((int*)bytes); bytes += sizeof(int);
1775 int flags = *((int*)bytes); bytes += sizeof(int);
1776 float dy = *((float*)bytes); bytes += sizeof(float);
1778 int button = MOUSE_5;
1779 if (dy > 0) button = MOUSE_4;
1781 flags = eventModifierFlagsToVimMouseModMask(flags);
1783 int numLines = (int)round(dy);
1784 if (numLines < 0) numLines = -numLines;
1785 if (numLines == 0) numLines = 1;
1787 #ifdef FEAT_GUI_SCROLL_WHEEL_FORCE
1788 gui.scroll_wheel_force = numLines;
1791 gui_send_mouse_event(button, col, row, NO, flags);
1792 } else if (MouseDownMsgID == msgid) {
1794 const void *bytes = [data bytes];
1796 int row = *((int*)bytes); bytes += sizeof(int);
1797 int col = *((int*)bytes); bytes += sizeof(int);
1798 int button = *((int*)bytes); bytes += sizeof(int);
1799 int flags = *((int*)bytes); bytes += sizeof(int);
1800 int count = *((int*)bytes); bytes += sizeof(int);
1802 button = eventButtonNumberToVimMouseButton(button);
1804 flags = eventModifierFlagsToVimMouseModMask(flags);
1805 gui_send_mouse_event(button, col, row, count>1, flags);
1807 } else if (MouseUpMsgID == msgid) {
1809 const void *bytes = [data bytes];
1811 int row = *((int*)bytes); bytes += sizeof(int);
1812 int col = *((int*)bytes); bytes += sizeof(int);
1813 int flags = *((int*)bytes); bytes += sizeof(int);
1815 flags = eventModifierFlagsToVimMouseModMask(flags);
1817 gui_send_mouse_event(MOUSE_RELEASE, col, row, NO, flags);
1818 } else if (MouseDraggedMsgID == msgid) {
1820 const void *bytes = [data bytes];
1822 int row = *((int*)bytes); bytes += sizeof(int);
1823 int col = *((int*)bytes); bytes += sizeof(int);
1824 int flags = *((int*)bytes); bytes += sizeof(int);
1826 flags = eventModifierFlagsToVimMouseModMask(flags);
1828 gui_send_mouse_event(MOUSE_DRAG, col, row, NO, flags);
1829 } else if (MouseMovedMsgID == msgid) {
1830 const void *bytes = [data bytes];
1831 int row = *((int*)bytes); bytes += sizeof(int);
1832 int col = *((int*)bytes); bytes += sizeof(int);
1834 gui_mouse_moved(col, row);
1835 } else if (AddInputMsgID == msgid) {
1836 NSString *string = [[NSString alloc] initWithData:data
1837 encoding:NSUTF8StringEncoding];
1839 [self addInput:string];
1842 } else if (SelectTabMsgID == msgid) {
1844 const void *bytes = [data bytes];
1845 int idx = *((int*)bytes) + 1;
1846 send_tabline_event(idx);
1847 } else if (CloseTabMsgID == msgid) {
1849 const void *bytes = [data bytes];
1850 int idx = *((int*)bytes) + 1;
1851 send_tabline_menu_event(idx, TABLINE_MENU_CLOSE);
1852 } else if (AddNewTabMsgID == msgid) {
1853 send_tabline_menu_event(0, TABLINE_MENU_NEW);
1854 } else if (DraggedTabMsgID == msgid) {
1856 const void *bytes = [data bytes];
1857 // NOTE! The destination index is 0 based, so do not add 1 to make it 1
1859 int idx = *((int*)bytes);
1862 } else if (SetTextDimensionsMsgID == msgid || LiveResizeMsgID == msgid
1863 || SetTextRowsMsgID == msgid || SetTextColumnsMsgID == msgid) {
1865 const void *bytes = [data bytes];
1867 if (SetTextColumnsMsgID != msgid) {
1868 rows = *((int*)bytes); bytes += sizeof(int);
1871 if (SetTextRowsMsgID != msgid) {
1872 cols = *((int*)bytes); bytes += sizeof(int);
1876 if (SetTextRowsMsgID == msgid || SetTextColumnsMsgID == msgid) {
1877 int dim[2] = { rows, cols };
1878 d = [NSData dataWithBytes:dim length:2*sizeof(int)];
1879 msgid = SetTextDimensionsReplyMsgID;
1882 if (SetTextDimensionsMsgID == msgid)
1883 msgid = SetTextDimensionsReplyMsgID;
1885 // NOTE! Vim doesn't call gui_mch_set_shellsize() after
1886 // gui_resize_shell(), so we have to manually set the rows and columns
1887 // here since MacVim doesn't change the rows and columns to avoid
1888 // inconsistent states between Vim and MacVim. The message sent back
1889 // indicates that it is a reply to a message that originated in MacVim
1890 // since we need to be able to determine where a message originated.
1891 [self queueMessage:msgid data:d];
1893 gui_resize_shell(cols, rows);
1894 } else if (ExecuteMenuMsgID == msgid) {
1895 NSDictionary *attrs = [NSDictionary dictionaryWithData:data];
1897 NSArray *desc = [attrs objectForKey:@"descriptor"];
1898 vimmenu_T *menu = menu_for_descriptor(desc);
1902 } else if (ToggleToolbarMsgID == msgid) {
1903 [self handleToggleToolbar];
1904 } else if (ScrollbarEventMsgID == msgid) {
1905 [self handleScrollbarEvent:data];
1906 } else if (SetFontMsgID == msgid) {
1907 [self handleSetFont:data];
1908 } else if (VimShouldCloseMsgID == msgid) {
1910 } else if (DropFilesMsgID == msgid) {
1911 [self handleDropFiles:data];
1912 } else if (DropStringMsgID == msgid) {
1913 [self handleDropString:data];
1914 } else if (GotFocusMsgID == msgid) {
1916 [self focusChange:YES];
1917 } else if (LostFocusMsgID == msgid) {
1919 [self focusChange:NO];
1920 } else if (SetMouseShapeMsgID == msgid) {
1921 const void *bytes = [data bytes];
1922 int shape = *((int*)bytes); bytes += sizeof(int);
1923 update_mouseshape(shape);
1924 } else if (XcodeModMsgID == msgid) {
1925 [self handleXcodeMod:data];
1926 } else if (OpenWithArgumentsMsgID == msgid) {
1927 [self handleOpenWithArguments:[NSDictionary dictionaryWithData:data]];
1928 } else if (FindReplaceMsgID == msgid) {
1929 [self handleFindReplace:[NSDictionary dictionaryWithData:data]];
1930 } else if (ActivatedImMsgID == msgid) {
1931 [self setImState:YES];
1932 } else if (DeactivatedImMsgID == msgid) {
1933 [self setImState:NO];
1934 } else if (NetBeansMsgID == msgid) {
1935 #ifdef FEAT_NETBEANS_INTG
1936 messageFromNetbeansMacVim();
1938 } else if (SetMarkedTextMsgID == msgid) {
1939 [self handleMarkedText:data];
1940 } else if (ZoomMsgID == msgid) {
1942 const void *bytes = [data bytes];
1943 int rows = *((int*)bytes); bytes += sizeof(int);
1944 int cols = *((int*)bytes); bytes += sizeof(int);
1945 //int zoom = *((int*)bytes); bytes += sizeof(int);
1947 // NOTE: The frontend sends zoom messages here causing us to
1948 // immediately resize the shell and mirror the message back to the
1949 // frontend. This is done to ensure that the draw commands reach the
1950 // frontend before the window actually changes size in order to avoid
1951 // flickering. (Also see comment in SetTextDimensionsReplyMsgID
1952 // regarding resizing.)
1953 [self queueMessage:ZoomMsgID data:data];
1954 gui_resize_shell(cols, rows);
1956 ASLogWarn(@"Unknown message received (msgid=%d)", msgid);
1960 - (void)doKeyDown:(NSString *)key
1961 keyCode:(unsigned)code
1964 ASLogDebug(@"key='%@' code=%#x mods=%#x length=%d", key, code, mods,
1968 char_u *str = (char_u*)[key UTF8String];
1969 int i, len = [key lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
1971 if ([self handleSpecialKey:key keyCode:code modifiers:mods])
1975 char_u *conv_str = NULL;
1976 if (input_conv.vc_type != CONV_NONE) {
1977 conv_str = string_convert(&input_conv, str, &len);
1983 if (mods & MOD_MASK_CMD) {
1984 // NOTE: For normal input (non-special, 'macmeta' off) the modifier
1985 // flags are already included in the key event. However, the Cmd key
1986 // flag is special and must always be added manually.
1987 // The Shift flag is already included in the key when the Command
1988 // key is held. The same goes for Alt, unless Ctrl is held or
1989 // 'macmeta' is set. It is important that these flags are cleared
1990 // _after_ special keys have been handled, since they should never be
1991 // cleared for special keys.
1992 mods &= ~MOD_MASK_SHIFT;
1993 if (!(mods & MOD_MASK_CTRL)) {
1994 BOOL mmta = curbuf ? curbuf->b_p_mmta : YES;
1996 mods &= ~MOD_MASK_ALT;
1999 ASLogDebug(@"add mods=%#x", mods);
2000 char_u modChars[3] = { CSI, KS_MODIFIER, mods };
2001 add_to_input_buf(modChars, 3);
2002 } else if (mods & MOD_MASK_ALT && 1 == len && str[0] < 0x80
2003 && curbuf && curbuf->b_p_mmta) {
2004 // HACK! The 'macmeta' is set so we have to handle Alt key presses
2005 // separately. Normally Alt key presses are interpreted by the
2006 // frontend but now we have to manually set the 8th bit and deal with
2007 // UTF-8 conversion.
2008 if ([self handleMacMetaKey:str[0] modifiers:mods])
2013 for (i = 0; i < len; ++i) {
2014 ASLogDebug(@"add byte [%d/%d]: %#x", i, len, str[i]);
2015 add_to_input_buf(str+i, 1);
2016 if (CSI == str[i]) {
2017 // NOTE: If the converted string contains the byte CSI, then it
2018 // must be followed by the bytes KS_EXTRA, KE_CSI or things
2020 static char_u extra[2] = { KS_EXTRA, KE_CSI };
2021 ASLogDebug(@"add KS_EXTRA, KE_CSI");
2022 add_to_input_buf(extra, 2);
2032 - (BOOL)handleSpecialKey:(NSString *)key
2033 keyCode:(unsigned)code
2037 for (i = 0; special_keys[i].key_sym != 0; i++) {
2038 if (special_keys[i].key_sym == code) {
2039 ASLogDebug(@"Special key: %#x", code);
2043 if (special_keys[i].key_sym == 0)
2046 int ikey = special_keys[i].vim_code1 == NUL ? special_keys[i].vim_code0 :
2047 TO_SPECIAL(special_keys[i].vim_code0, special_keys[i].vim_code1);
2048 ikey = simplify_key(ikey, &mods);
2055 if (IS_SPECIAL(ikey)) {
2057 chars[1] = K_SECOND(ikey);
2058 chars[2] = K_THIRD(ikey);
2060 } else if (mods & MOD_MASK_ALT && special_keys[i].vim_code1 == 0
2062 && !enc_dbcs // TODO: ? (taken from gui_gtk_x11.c)
2065 ASLogDebug(@"Alt special=%d", ikey);
2067 // NOTE: The last entries in the special_keys struct when pressed
2068 // together with Alt need to be handled separately or they will not
2070 // The following code was gleaned from gui_gtk_x11.c.
2071 mods &= ~MOD_MASK_ALT;
2072 int mkey = 0x80 | ikey;
2074 if (enc_utf8) { // TODO: What about other encodings?
2076 chars[0] = (mkey >> 6) + 0xc0;
2077 chars[1] = mkey & 0xbf;
2078 if (chars[1] == CSI) {
2079 // We end up here when ikey == ESC
2080 chars[2] = KS_EXTRA;
2093 ASLogDebug(@"Just ikey=%d", ikey);
2100 ASLogDebug(@"Adding mods to special: %d", mods);
2101 char_u modChars[3] = { CSI, KS_MODIFIER, (char_u)mods };
2102 add_to_input_buf(modChars, 3);
2105 ASLogDebug(@"Adding special (%d): %x,%x,%x", len,
2106 chars[0], chars[1], chars[2]);
2107 add_to_input_buf(chars, len);
2113 - (BOOL)handleMacMetaKey:(int)ikey modifiers:(int)mods
2115 ASLogDebug(@"ikey=%d mods=%d", ikey, mods);
2117 // This code was taken from gui_w48.c and gui_gtk_x11.c.
2119 int ch = simplify_key(ikey, &mods);
2121 // Remove the SHIFT modifier for keys where it's already included,
2122 // e.g., '(' and '*'
2123 if (ch < 0x100 && !isalpha(ch) && isprint(ch))
2124 mods &= ~MOD_MASK_SHIFT;
2126 // Interpret the ALT key as making the key META, include SHIFT, etc.
2127 ch = extract_modifiers(ch, &mods);
2133 string[len++] = CSI;
2134 string[len++] = KS_MODIFIER;
2135 string[len++] = mods;
2140 // TODO: What if 'enc' is not "utf-8"?
2141 if (enc_utf8 && (ch & 0x80)) { // convert to utf-8
2142 string[len++] = ch & 0xbf;
2143 string[len-2] = ((unsigned)ch >> 6) + 0xc0;
2144 if (string[len-1] == CSI) {
2145 string[len++] = KS_EXTRA;
2146 string[len++] = (int)KE_CSI;
2151 add_to_input_buf(string, len);
2155 - (void)queueMessage:(int)msgid data:(NSData *)data
2157 [outputQueue addObject:[NSData dataWithBytes:&msgid length:sizeof(int)]];
2159 [outputQueue addObject:data];
2161 [outputQueue addObject:[NSData data]];
2164 - (void)connectionDidDie:(NSNotification *)notification
2166 // If the main connection to MacVim is lost this means that either MacVim
2167 // has crashed or this process did not receive its termination message
2168 // properly (e.g. if the TerminateNowMsgID was dropped).
2170 // NOTE: This is not called if a Vim controller invalidates its connection.
2172 ASLogNotice(@"Main connection was lost before process had a chance "
2173 "to terminate; preserving swap files.");
2174 getout_preserve_modified(1);
2177 - (void)blinkTimerFired:(NSTimer *)timer
2179 NSTimeInterval timeInterval = 0;
2181 [blinkTimer release];
2184 if (MMBlinkStateOn == blinkState) {
2185 gui_undraw_cursor();
2186 blinkState = MMBlinkStateOff;
2187 timeInterval = blinkOffInterval;
2188 } else if (MMBlinkStateOff == blinkState) {
2189 gui_update_cursor(TRUE, FALSE);
2190 blinkState = MMBlinkStateOn;
2191 timeInterval = blinkOnInterval;
2194 if (timeInterval > 0) {
2196 [[NSTimer scheduledTimerWithTimeInterval:timeInterval target:self
2197 selector:@selector(blinkTimerFired:)
2198 userInfo:nil repeats:NO] retain];
2199 [self flushQueue:YES];
2203 - (void)focusChange:(BOOL)on
2205 gui_focus_change(on);
2208 - (void)handleToggleToolbar
2210 // If 'go' contains 'T', then remove it, else add it.
2212 char_u go[sizeof(GO_ALL)+2];
2217 p = vim_strchr(go, GO_TOOLBAR);
2221 char_u *end = go + len;
2227 go[len] = GO_TOOLBAR;
2231 set_option_value((char_u*)"guioptions", 0, go, 0);
2234 - (void)handleScrollbarEvent:(NSData *)data
2238 const void *bytes = [data bytes];
2239 int32_t ident = *((int32_t*)bytes); bytes += sizeof(int32_t);
2240 int hitPart = *((int*)bytes); bytes += sizeof(int);
2241 float fval = *((float*)bytes); bytes += sizeof(float);
2242 scrollbar_T *sb = gui_find_scrollbar(ident);
2245 scrollbar_T *sb_info = sb->wp ? &sb->wp->w_scrollbars[0] : sb;
2246 long value = sb_info->value;
2247 long size = sb_info->size;
2248 long max = sb_info->max;
2249 BOOL isStillDragging = NO;
2250 BOOL updateKnob = YES;
2253 case NSScrollerDecrementPage:
2254 value -= (size > 2 ? size - 2 : 1);
2256 case NSScrollerIncrementPage:
2257 value += (size > 2 ? size - 2 : 1);
2259 case NSScrollerDecrementLine:
2262 case NSScrollerIncrementLine:
2265 case NSScrollerKnob:
2266 isStillDragging = YES;
2268 case NSScrollerKnobSlot:
2269 value = (long)(fval * (max - size + 1));
2276 gui_drag_scrollbar(sb, value, isStillDragging);
2279 // Dragging the knob or option+clicking automatically updates
2280 // the knob position (on the actual NSScroller), so we only
2281 // need to set the knob position in the other cases.
2283 // Update both the left&right vertical scrollbars.
2284 int32_t idL = (int32_t)sb->wp->w_scrollbars[SBAR_LEFT].ident;
2285 int32_t idR = (int32_t)sb->wp->w_scrollbars[SBAR_RIGHT].ident;
2286 [self setScrollbarThumbValue:value size:size max:max
2288 [self setScrollbarThumbValue:value size:size max:max
2291 // Update the horizontal scrollbar.
2292 [self setScrollbarThumbValue:value size:size max:max
2299 - (void)handleSetFont:(NSData *)data
2303 const void *bytes = [data bytes];
2304 int pointSize = (int)*((float*)bytes); bytes += sizeof(float);
2306 unsigned len = *((unsigned*)bytes); bytes += sizeof(unsigned);
2307 NSMutableString *name = [NSMutableString stringWithUTF8String:bytes];
2310 [name appendString:[NSString stringWithFormat:@":h%d", pointSize]];
2311 char_u *s = (char_u*)[name UTF8String];
2313 unsigned wlen = *((unsigned*)bytes); bytes += sizeof(unsigned);
2316 NSMutableString *wname = [NSMutableString stringWithUTF8String:bytes];
2319 [wname appendString:[NSString stringWithFormat:@":h%d", pointSize]];
2320 ws = (char_u*)[wname UTF8String];
2324 s = CONVERT_FROM_UTF8(s);
2326 ws = CONVERT_FROM_UTF8(ws);
2330 set_option_value((char_u*)"guifont", 0, s, 0);
2332 if (ws && gui.wide_font != NOFONT) {
2333 // NOTE: This message is sent on Cmd-+/Cmd-- and as such should only
2334 // change the wide font if 'gfw' is non-empty (the frontend always has
2335 // some wide font set, even if 'gfw' is empty).
2336 set_option_value((char_u*)"guifontwide", 0, ws, 0);
2341 CONVERT_FROM_UTF8_FREE(ws);
2343 CONVERT_FROM_UTF8_FREE(s);
2346 [self redrawScreen];
2349 - (void)handleDropFiles:(NSData *)data
2351 // TODO: Get rid of this method; instead use Vim script directly. At the
2352 // moment I know how to do this to open files in tabs, but I'm not sure how
2353 // to add the filenames to the command line when in command line mode.
2357 NSMutableDictionary *args = [NSMutableDictionary dictionaryWithData:data];
2360 id obj = [args objectForKey:@"forceOpen"];
2361 BOOL forceOpen = YES;
2363 forceOpen = [obj boolValue];
2365 NSArray *filenames = [args objectForKey:@"filenames"];
2366 if (!(filenames && [filenames count] > 0)) return;
2369 if (!forceOpen && (State & CMDLINE)) {
2370 // HACK! If Vim is in command line mode then the files names
2371 // should be added to the command line, instead of opening the
2372 // files in tabs (unless forceOpen is set). This is taken care of by
2373 // gui_handle_drop().
2374 int n = [filenames count];
2375 char_u **fnames = (char_u **)alloc(n * sizeof(char_u *));
2378 for (i = 0; i < n; ++i)
2379 fnames[i] = [[filenames objectAtIndex:i] vimStringSave];
2381 // NOTE! This function will free 'fnames'.
2382 // HACK! It is assumed that the 'x' and 'y' arguments are
2383 // unused when in command line mode.
2384 gui_handle_drop(0, 0, 0, fnames, n);
2389 [self handleOpenWithArguments:args];
2393 - (void)handleDropString:(NSData *)data
2398 char_u dropkey[3] = { CSI, KS_EXTRA, (char_u)KE_DROP };
2399 const void *bytes = [data bytes];
2400 int len = *((int*)bytes); bytes += sizeof(int);
2401 NSMutableString *string = [NSMutableString stringWithUTF8String:bytes];
2403 // Replace unrecognized end-of-line sequences with \x0a (line feed).
2404 NSRange range = { 0, [string length] };
2405 unsigned n = [string replaceOccurrencesOfString:@"\x0d\x0a"
2406 withString:@"\x0a" options:0
2409 n = [string replaceOccurrencesOfString:@"\x0d" withString:@"\x0a"
2410 options:0 range:range];
2413 len = [string lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
2414 char_u *s = (char_u*)[string UTF8String];
2416 if (input_conv.vc_type != CONV_NONE)
2417 s = string_convert(&input_conv, s, &len);
2419 dnd_yank_drag_data(s, len);
2421 if (input_conv.vc_type != CONV_NONE)
2424 add_to_input_buf(dropkey, sizeof(dropkey));
2428 - (void)startOdbEditWithArguments:(NSDictionary *)args
2430 #ifdef FEAT_ODB_EDITOR
2431 id obj = [args objectForKey:@"remoteID"];
2434 OSType serverID = [obj unsignedIntValue];
2435 NSString *remotePath = [args objectForKey:@"remotePath"];
2437 NSAppleEventDescriptor *token = nil;
2438 NSData *tokenData = [args objectForKey:@"remoteTokenData"];
2439 obj = [args objectForKey:@"remoteTokenDescType"];
2440 if (tokenData && obj) {
2441 DescType tokenType = [obj unsignedLongValue];
2442 token = [NSAppleEventDescriptor descriptorWithDescriptorType:tokenType
2446 NSArray *filenames = [args objectForKey:@"filenames"];
2447 unsigned i, numFiles = [filenames count];
2448 for (i = 0; i < numFiles; ++i) {
2449 NSString *filename = [filenames objectAtIndex:i];
2450 char_u *s = [filename vimStringSave];
2451 buf_T *buf = buflist_findname(s);
2455 if (buf->b_odb_token) {
2456 [(NSAppleEventDescriptor*)(buf->b_odb_token) release];
2457 buf->b_odb_token = NULL;
2460 if (buf->b_odb_fname) {
2461 vim_free(buf->b_odb_fname);
2462 buf->b_odb_fname = NULL;
2465 buf->b_odb_server_id = serverID;
2468 buf->b_odb_token = [token retain];
2470 buf->b_odb_fname = [remotePath vimStringSave];
2472 ASLogWarn(@"Could not find buffer '%@' for ODB editing.", filename);
2475 #endif // FEAT_ODB_EDITOR
2478 - (void)handleXcodeMod:(NSData *)data
2481 const void *bytes = [data bytes];
2482 DescType type = *((DescType*)bytes); bytes += sizeof(DescType);
2483 unsigned len = *((unsigned*)bytes); bytes += sizeof(unsigned);
2487 NSAppleEventDescriptor *replyEvent = [NSAppleEventDescriptor
2488 descriptorWithDescriptorType:type
2494 - (void)handleOpenWithArguments:(NSDictionary *)args
2496 // ARGUMENT: DESCRIPTION:
2497 // -------------------------------------------------------------
2498 // filenames list of filenames
2499 // dontOpen don't open files specified in above argument
2500 // layout which layout to use to open files
2501 // selectionRange range of lines to select
2502 // searchText string to search for
2503 // cursorLine line to position the cursor on
2504 // cursorColumn column to position the cursor on
2505 // (only valid when "cursorLine" is set)
2506 // remoteID ODB parameter
2507 // remotePath ODB parameter
2508 // remoteTokenDescType ODB parameter
2509 // remoteTokenData ODB parameter
2511 ASLogDebug(@"args=%@ (starting=%d)", args, starting);
2513 NSArray *filenames = [args objectForKey:@"filenames"];
2514 int i, numFiles = filenames ? [filenames count] : 0;
2515 BOOL openFiles = ![[args objectForKey:@"dontOpen"] boolValue];
2516 int layout = [[args objectForKey:@"layout"] intValue];
2518 // Change to directory of first file to open if this is an "unused" editor
2519 // (but do not do this if editing remotely).
2520 if (openFiles && numFiles > 0 && ![args objectForKey:@"remoteID"]
2521 && (starting || [self unusedEditor]) ) {
2522 char_u *s = [[filenames objectAtIndex:0] vimStringSave];
2524 mch_chdir((char*)s);
2532 // When Vim is starting we simply add the files to be opened to the
2533 // global arglist and Vim will take care of opening them for us.
2534 if (openFiles && numFiles > 0) {
2535 for (i = 0; i < numFiles; i++) {
2536 NSString *fname = [filenames objectAtIndex:i];
2539 if (ga_grow(&global_alist.al_ga, 1) == FAIL
2540 || (p = [fname vimStringSave]) == NULL)
2541 exit(2); // See comment in -[MMBackend exit]
2543 alist_add(&global_alist, p, 2);
2546 // Vim will take care of arranging the files added to the arglist
2547 // in windows or tabs; all we must do is to specify which layout to
2549 initialWindowLayout = layout;
2552 // When Vim is already open we resort to some trickery to open the
2553 // files with the specified layout.
2555 // TODO: Figure out a better way to handle this?
2556 if (openFiles && numFiles > 0) {
2557 BOOL oneWindowInTab = topframe ? YES
2558 : (topframe->fr_layout == FR_LEAF);
2559 BOOL bufChanged = NO;
2560 BOOL bufHasFilename = NO;
2562 bufChanged = curbufIsChanged();
2563 bufHasFilename = curbuf->b_ffname != NULL;
2566 // Temporarily disable flushing since the following code may
2567 // potentially cause multiple redraws.
2568 flushDisabled = YES;
2570 BOOL onlyOneTab = (first_tabpage->tp_next == NULL);
2571 if (WIN_TABS == layout && !onlyOneTab) {
2572 // By going to the last tabpage we ensure that the new tabs
2573 // will appear last (if this call is left out, the taborder
2578 // Make sure we're in normal mode first.
2579 [self addInput:@"<C-\\><C-N>"];
2582 // With "split layout" we open a new tab before opening
2583 // multiple files if the current tab has more than one window
2584 // or if there is exactly one window but whose buffer has a
2585 // filename. (The :drop command ensures modified buffers get
2586 // their own window.)
2587 if ((WIN_HOR == layout || WIN_VER == layout) &&
2588 (!oneWindowInTab || bufHasFilename))
2589 [self addInput:@":tabnew<CR>"];
2591 // The files are opened by constructing a ":drop ..." command
2592 // and executing it.
2593 NSMutableString *cmd = (WIN_TABS == layout)
2594 ? [NSMutableString stringWithString:@":tab drop"]
2595 : [NSMutableString stringWithString:@":drop"];
2597 for (i = 0; i < numFiles; ++i) {
2598 NSString *file = [filenames objectAtIndex:i];
2599 file = [file stringByEscapingSpecialFilenameCharacters];
2600 [cmd appendString:@" "];
2601 [cmd appendString:file];
2604 // Temporarily clear 'suffixes' so that the files are opened in
2605 // the same order as they appear in the "filenames" array.
2606 [self addInput:@":let mvim_oldsu=&su|set su=<CR>"];
2608 [self addInput:cmd];
2610 // Split the view into multiple windows if requested.
2611 if (WIN_HOR == layout)
2612 [self addInput:@"|sall"];
2613 else if (WIN_VER == layout)
2614 [self addInput:@"|vert sall"];
2616 // Restore the old value of 'suffixes'.
2617 [self addInput:@"|let &su=mvim_oldsu|unlet mvim_oldsu<CR>"];
2619 // When opening one file we try to reuse the current window,
2620 // but not if its buffer is modified or has a filename.
2621 // However, the 'arglist' layout always opens the file in the
2623 NSString *file = [[filenames lastObject]
2624 stringByEscapingSpecialFilenameCharacters];
2626 if (WIN_HOR == layout) {
2627 if (!(bufHasFilename || bufChanged))
2628 cmd = [NSString stringWithFormat:@":e %@", file];
2630 cmd = [NSString stringWithFormat:@":sp %@", file];
2631 } else if (WIN_VER == layout) {
2632 if (!(bufHasFilename || bufChanged))
2633 cmd = [NSString stringWithFormat:@":e %@", file];
2635 cmd = [NSString stringWithFormat:@":vsp %@", file];
2636 } else if (WIN_TABS == layout) {
2637 if (oneWindowInTab && !(bufHasFilename || bufChanged))
2638 cmd = [NSString stringWithFormat:@":e %@", file];
2640 cmd = [NSString stringWithFormat:@":tabe %@", file];
2642 // (The :drop command will split if there is a modified
2644 cmd = [NSString stringWithFormat:@":drop %@", file];
2647 [self addInput:cmd];
2648 [self addInput:@"<CR>"];
2651 // Force screen redraw (does it have to be this complicated?).
2652 // (This code was taken from the end of gui_handle_drop().)
2653 update_screen(NOT_VALID);
2656 gui_update_cursor(FALSE, FALSE);
2663 if ([args objectForKey:@"remoteID"]) {
2664 // NOTE: We have to delay processing any ODB related arguments since
2665 // the file(s) may not be opened until the input buffer is processed.
2666 [self performSelector:@selector(startOdbEditWithArguments:)
2671 NSString *lineString = [args objectForKey:@"cursorLine"];
2672 if (lineString && [lineString intValue] > 0) {
2673 NSString *columnString = [args objectForKey:@"cursorColumn"];
2674 if (!(columnString && [columnString intValue] > 0))
2675 columnString = @"1";
2677 NSString *cmd = [NSString stringWithFormat:@"<C-\\><C-N>:cal "
2678 "cursor(%@,%@)|norm! zz<CR>:f<CR>", lineString, columnString];
2679 [self addInput:cmd];
2682 NSString *rangeString = [args objectForKey:@"selectionRange"];
2684 // Build a command line string that will select the given range of
2685 // lines. If range.length == 0, then position the cursor on the given
2686 // line but do not select.
2687 NSRange range = NSRangeFromString(rangeString);
2689 if (range.length > 0) {
2690 cmd = [NSString stringWithFormat:@"<C-\\><C-N>%dGV%dGz.0",
2691 NSMaxRange(range), range.location];
2693 cmd = [NSString stringWithFormat:@"<C-\\><C-N>%dGz.0",
2697 [self addInput:cmd];
2700 NSString *searchText = [args objectForKey:@"searchText"];
2702 [self addInput:[NSString stringWithFormat:@"<C-\\><C-N>gg/\\c%@/e<CR>",
2707 - (BOOL)checkForModifiedBuffers
2710 for (buf = firstbuf; buf != NULL; buf = buf->b_next) {
2711 if (bufIsChanged(buf)) {
2719 - (void)addInput:(NSString *)input
2721 // NOTE: This code is essentially identical to server_to_input_buf(),
2722 // except the 'silent' flag is TRUE in the call to ins_typebuf() below.
2723 char_u *string = [input vimStringSave];
2724 if (!string) return;
2726 /* Set 'cpoptions' the way we want it.
2727 * B set - backslashes are *not* treated specially
2728 * k set - keycodes are *not* reverse-engineered
2729 * < unset - <Key> sequences *are* interpreted
2730 * The last but one parameter of replace_termcodes() is TRUE so that the
2731 * <lt> sequence is recognised - needed for a real backslash.
2734 char_u *cpo_save = p_cpo;
2735 p_cpo = (char_u *)"Bk";
2736 char_u *str = replace_termcodes((char_u *)string, &ptr, FALSE, TRUE, FALSE);
2739 if (*ptr != NUL) /* trailing CTRL-V results in nothing */
2742 * Add the string to the input stream.
2743 * Can't use add_to_input_buf() here, we now have K_SPECIAL bytes.
2745 * First clear typed characters from the typeahead buffer, there could
2746 * be half a mapping there. Then append to the existing string, so
2747 * that multiple commands from a client are concatenated.
2749 if (typebuf.tb_maplen < typebuf.tb_len)
2750 del_typebuf(typebuf.tb_len - typebuf.tb_maplen, typebuf.tb_maplen);
2751 (void)ins_typebuf(str, REMAP_NONE, typebuf.tb_len, TRUE, TRUE);
2753 /* Let input_available() know we inserted text in the typeahead
2755 typebuf_was_filled = TRUE;
2761 - (BOOL)unusedEditor
2763 BOOL oneWindowInTab = topframe ? YES
2764 : (topframe->fr_layout == FR_LEAF);
2765 BOOL bufChanged = NO;
2766 BOOL bufHasFilename = NO;
2768 bufChanged = curbufIsChanged();
2769 bufHasFilename = curbuf->b_ffname != NULL;
2772 BOOL onlyOneTab = (first_tabpage->tp_next == NULL);
2774 return onlyOneTab && oneWindowInTab && !bufChanged && !bufHasFilename;
2777 - (void)redrawScreen
2779 // Force screen redraw (does it have to be this complicated?).
2780 redraw_all_later(CLEAR);
2781 update_screen(NOT_VALID);
2784 gui_update_cursor(FALSE, FALSE);
2786 // HACK! The cursor is not put back at the command line by the above
2787 // "redraw commands". The following test seems to do the trick though.
2788 if (State & CMDLINE)
2792 - (void)handleFindReplace:(NSDictionary *)args
2796 NSString *findString = [args objectForKey:@"find"];
2797 if (!findString) return;
2799 char_u *find = [findString vimStringSave];
2800 char_u *replace = [[args objectForKey:@"replace"] vimStringSave];
2801 int flags = [[args objectForKey:@"flags"] intValue];
2803 // NOTE: The flag 0x100 is used to indicate a backward search.
2804 gui_do_findrepl(flags, find, replace, (flags & 0x100) == 0);
2811 - (void)handleMarkedText:(NSData *)data
2813 const void *bytes = [data bytes];
2814 int32_t pos = *((int32_t*)bytes); bytes += sizeof(int32_t);
2815 unsigned len = *((unsigned*)bytes); bytes += sizeof(unsigned);
2816 char *chars = (char *)bytes;
2818 ASLogDebug(@"pos=%d len=%d chars=%s", pos, len, chars);
2821 im_preedit_abandon_macvim();
2822 } else if (len == 0) {
2823 im_preedit_end_macvim();
2825 if (!preedit_get_status())
2826 im_preedit_start_macvim();
2828 im_preedit_changed_macvim(chars, pos);
2832 @end // MMBackend (Private)
2837 @implementation MMBackend (ClientServer)
2839 - (NSString *)connectionNameFromServerName:(NSString *)name
2841 NSString *bundlePath = [[NSBundle mainBundle] bundlePath];
2843 return [[NSString stringWithFormat:@"%@.%@", bundlePath, name]
2847 - (NSConnection *)connectionForServerName:(NSString *)name
2849 // TODO: Try 'name%d' if 'name' fails.
2850 NSString *connName = [self connectionNameFromServerName:name];
2851 NSConnection *svrConn = [connectionNameDict objectForKey:connName];
2854 svrConn = [NSConnection connectionWithRegisteredName:connName
2856 // Try alternate server...
2857 if (!svrConn && alternateServerName) {
2858 ASLogInfo(@" trying to connect to alternate server: %@",
2859 alternateServerName);
2860 connName = [self connectionNameFromServerName:alternateServerName];
2861 svrConn = [NSConnection connectionWithRegisteredName:connName
2865 // Try looking for alternate servers...
2867 ASLogInfo(@" looking for alternate servers...");
2868 NSString *alt = [self alternateServerNameForName:name];
2869 if (alt != alternateServerName) {
2870 ASLogInfo(@" found alternate server: %@", alt);
2871 [alternateServerName release];
2872 alternateServerName = [alt copy];
2876 // Try alternate server again...
2877 if (!svrConn && alternateServerName) {
2878 ASLogInfo(@" trying to connect to alternate server: %@",
2879 alternateServerName);
2880 connName = [self connectionNameFromServerName:alternateServerName];
2881 svrConn = [NSConnection connectionWithRegisteredName:connName
2886 [connectionNameDict setObject:svrConn forKey:connName];
2888 ASLogDebug(@"Adding %@ as connection observer for %@",
2890 [[NSNotificationCenter defaultCenter] addObserver:self
2891 selector:@selector(serverConnectionDidDie:)
2892 name:NSConnectionDidDieNotification object:svrConn];
2899 - (NSConnection *)connectionForServerPort:(int)port
2902 NSEnumerator *e = [connectionNameDict objectEnumerator];
2904 while ((conn = [e nextObject])) {
2905 // HACK! Assume connection uses mach ports.
2906 if (port == [(NSMachPort*)[conn sendPort] machPort])
2913 - (void)serverConnectionDidDie:(NSNotification *)notification
2915 ASLogDebug(@"notification=%@", notification);
2917 NSConnection *svrConn = [notification object];
2919 ASLogDebug(@"Removing %@ as connection observer from %@", self, svrConn);
2920 [[NSNotificationCenter defaultCenter]
2922 name:NSConnectionDidDieNotification
2925 [connectionNameDict removeObjectsForKeys:
2926 [connectionNameDict allKeysForObject:svrConn]];
2928 // HACK! Assume connection uses mach ports.
2929 int port = [(NSMachPort*)[svrConn sendPort] machPort];
2930 NSNumber *key = [NSNumber numberWithInt:port];
2932 [clientProxyDict removeObjectForKey:key];
2933 [serverReplyDict removeObjectForKey:key];
2936 - (void)addClient:(NSDistantObject *)client
2938 NSConnection *conn = [client connectionForProxy];
2939 // HACK! Assume connection uses mach ports.
2940 int port = [(NSMachPort*)[conn sendPort] machPort];
2941 NSNumber *key = [NSNumber numberWithInt:port];
2943 if (![clientProxyDict objectForKey:key]) {
2944 [client setProtocolForProxy:@protocol(MMVimClientProtocol)];
2945 [clientProxyDict setObject:client forKey:key];
2948 // NOTE: 'clientWindow' is a global variable which is used by <client>
2949 clientWindow = port;
2952 - (NSString *)alternateServerNameForName:(NSString *)name
2954 if (!(name && [name length] > 0))
2957 // Only look for alternates if 'name' doesn't end in a digit.
2958 unichar lastChar = [name characterAtIndex:[name length]-1];
2959 if (lastChar >= '0' && lastChar <= '9')
2962 // Look for alternates among all current servers.
2963 NSArray *list = [self serverList];
2964 if (!(list && [list count] > 0))
2967 // Filter out servers starting with 'name' and ending with a number. The
2968 // (?i) pattern ensures that the match is case insensitive.
2969 NSString *pat = [NSString stringWithFormat:@"(?i)%@[0-9]+\\z", name];
2970 NSPredicate *pred = [NSPredicate predicateWithFormat:
2971 @"SELF MATCHES %@", pat];
2972 list = [list filteredArrayUsingPredicate:pred];
2973 if ([list count] > 0) {
2974 list = [list sortedArrayUsingSelector:@selector(serverNameCompare:)];
2975 return [list objectAtIndex:0];
2981 @end // MMBackend (ClientServer)
2986 @implementation NSString (MMServerNameCompare)
2987 - (NSComparisonResult)serverNameCompare:(NSString *)string
2989 return [self compare:string
2990 options:NSCaseInsensitiveSearch|NSNumericSearch];
2997 static int eventModifierFlagsToVimModMask(int modifierFlags)
3001 if (modifierFlags & NSShiftKeyMask)
3002 modMask |= MOD_MASK_SHIFT;
3003 if (modifierFlags & NSControlKeyMask)
3004 modMask |= MOD_MASK_CTRL;
3005 if (modifierFlags & NSAlternateKeyMask)
3006 modMask |= MOD_MASK_ALT;
3007 if (modifierFlags & NSCommandKeyMask)
3008 modMask |= MOD_MASK_CMD;
3013 static int eventModifierFlagsToVimMouseModMask(int modifierFlags)
3017 if (modifierFlags & NSShiftKeyMask)
3018 modMask |= MOUSE_SHIFT;
3019 if (modifierFlags & NSControlKeyMask)
3020 modMask |= MOUSE_CTRL;
3021 if (modifierFlags & NSAlternateKeyMask)
3022 modMask |= MOUSE_ALT;
3027 static int eventButtonNumberToVimMouseButton(int buttonNumber)
3029 static int mouseButton[] = { MOUSE_LEFT, MOUSE_RIGHT, MOUSE_MIDDLE };
3031 return (buttonNumber >= 0 && buttonNumber < 3)
3032 ? mouseButton[buttonNumber] : -1;
3037 // This function is modeled after the VimToPython function found in if_python.c
3038 // NB This does a deep copy by value, it does not lookup references like the
3039 // VimToPython function does. This is because I didn't want to deal with the
3040 // retain cycles that this would create, and we can cover 99% of the use cases
3041 // by ignoring it. If we ever switch to using GC in MacVim then this
3042 // functionality can be implemented easily.
3043 static id vimToCocoa(typval_T * tv, int depth)
3049 // Avoid infinite recursion
3054 if (tv->v_type == VAR_STRING) {
3055 char_u * val = tv->vval.v_string;
3056 // val can be NULL if the string is empty
3058 result = [NSString string];
3061 val = CONVERT_TO_UTF8(val);
3063 result = [NSString stringWithUTF8String:(char*)val];
3065 CONVERT_TO_UTF8_FREE(val);
3068 } else if (tv->v_type == VAR_NUMBER) {
3069 // looks like sizeof(varnumber_T) is always <= sizeof(long)
3070 result = [NSNumber numberWithLong:(long)tv->vval.v_number];
3071 } else if (tv->v_type == VAR_LIST) {
3072 list_T * list = tv->vval.v_list;
3075 NSMutableArray * arr = result = [NSMutableArray array];
3078 for (curr = list->lv_first; curr != NULL; curr = curr->li_next) {
3079 newObj = vimToCocoa(&curr->li_tv, depth + 1);
3080 [arr addObject:newObj];
3083 } else if (tv->v_type == VAR_DICT) {
3084 NSMutableDictionary * dict = result = [NSMutableDictionary dictionary];
3086 if (tv->vval.v_dict != NULL) {
3087 hashtab_T * ht = &tv->vval.v_dict->dv_hashtab;
3088 int todo = ht->ht_used;
3092 for (hi = ht->ht_array; todo > 0; ++hi) {
3093 if (!HASHITEM_EMPTY(hi)) {
3096 di = dict_lookup(hi);
3097 newObj = vimToCocoa(&di->di_tv, depth + 1);
3099 char_u * keyval = hi->hi_key;
3101 keyval = CONVERT_TO_UTF8(keyval);
3103 NSString * key = [NSString stringWithUTF8String:(char*)keyval];
3105 CONVERT_TO_UTF8_FREE(keyval);
3107 [dict setObject:newObj forKey:key];
3111 } else { // only func refs should fall into this category?
3119 // This function is modeled after eval_client_expr_to_string found in main.c
3120 // Returns nil if there was an error evaluating the expression, and writes a
3121 // message to errorStr.
3122 // TODO Get the error that occurred while evaluating the expression in vim
3124 static id evalExprCocoa(NSString * expr, NSString ** errstr)
3127 char_u *s = (char_u*)[expr UTF8String];
3130 s = CONVERT_FROM_UTF8(s);
3133 int save_dbl = debug_break_level;
3134 int save_ro = redir_off;
3136 debug_break_level = -1;
3140 typval_T * tvres = eval_expr(s, NULL);
3142 debug_break_level = save_dbl;
3143 redir_off = save_ro;
3150 CONVERT_FROM_UTF8_FREE(s);
3155 gui_update_cursor(FALSE, FALSE);
3158 if (tvres == NULL) {
3160 *errstr = @"Expression evaluation failed.";
3163 id res = vimToCocoa(tvres, 1);
3168 *errstr = @"Conversion to cocoa values failed.";
3176 @implementation NSString (VimStrings)
3178 + (id)stringWithVimString:(char_u *)s
3180 // This method ensures a non-nil string is returned. If 's' cannot be
3181 // converted to a utf-8 string it is assumed to be latin-1. If conversion
3182 // still fails an empty NSString is returned.
3183 NSString *string = nil;
3186 s = CONVERT_TO_UTF8(s);
3188 string = [NSString stringWithUTF8String:(char*)s];
3190 // HACK! Apparently 's' is not a valid utf-8 string, maybe it is
3192 string = [NSString stringWithCString:(char*)s
3193 encoding:NSISOLatin1StringEncoding];
3196 CONVERT_TO_UTF8_FREE(s);
3200 return string != nil ? string : [NSString string];
3203 - (char_u *)vimStringSave
3205 char_u *s = (char_u*)[self UTF8String], *ret = NULL;
3208 s = CONVERT_FROM_UTF8(s);
3210 ret = vim_strsave(s);
3212 CONVERT_FROM_UTF8_FREE(s);
3218 @end // NSString (VimStrings)