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_changed_macvim(char *preedit_string, int cursor_index);
69 static NSString *MMSymlinkWarningString =
70 @"\n\n\tMost likely this is because you have symlinked directly to\n"
71 "\tthe Vim binary, which Cocoa does not allow. Please use an\n"
72 "\talias or the mvim shell script instead. If you have not used\n"
73 "\ta symlink, then your MacVim.app bundle is incomplete.\n\n";
76 // Keycodes recognized by Vim (struct taken from gui_x11.c and gui_w48.c)
77 // (The key codes were taken from Carbon/HIToolbox/Events.)
78 static struct specialkey
85 {0x7e /*kVK_UpArrow*/, 'k', 'u'},
86 {0x7d /*kVK_DownArrow*/, 'k', 'd'},
87 {0x7b /*kVK_LeftArrow*/, 'k', 'l'},
88 {0x7c /*kVK_RightArrow*/, 'k', 'r'},
90 {0x7a /*kVK_F1*/, 'k', '1'},
91 {0x78 /*kVK_F2*/, 'k', '2'},
92 {0x63 /*kVK_F3*/, 'k', '3'},
93 {0x76 /*kVK_F4*/, 'k', '4'},
94 {0x60 /*kVK_F5*/, 'k', '5'},
95 {0x61 /*kVK_F6*/, 'k', '6'},
96 {0x62 /*kVK_F7*/, 'k', '7'},
97 {0x64 /*kVK_F8*/, 'k', '8'},
98 {0x65 /*kVK_F9*/, 'k', '9'},
99 {0x6d /*kVK_F10*/, 'k', ';'},
101 {0x67 /*kVK_F11*/, 'F', '1'},
102 {0x6f /*kVK_F12*/, 'F', '2'},
103 {0x69 /*kVK_F13*/, 'F', '3'},
104 {0x6b /*kVK_F14*/, 'F', '4'},
105 {0x71 /*kVK_F15*/, 'F', '5'},
106 {0x6a /*kVK_F16*/, 'F', '6'},
107 {0x40 /*kVK_F17*/, 'F', '7'},
108 {0x4f /*kVK_F18*/, 'F', '8'},
109 {0x50 /*kVK_F19*/, 'F', '9'},
110 {0x5a /*kVK_F20*/, 'F', 'A'},
112 {0x72 /*kVK_Help*/, '%', '1'},
113 {0x33 /*kVK_Delete*/, 'k', 'b'},
114 {0x75 /*kVK_ForwardDelete*/, 'k', 'D'},
115 {0x73 /*kVK_Home*/, 'k', 'h'},
116 {0x77 /*kVK_End*/, '@', '7'},
117 {0x74 /*kVK_PageUp*/, 'k', 'P'},
118 {0x79 /*kVK_PageDown*/, 'k', 'N'},
121 {0x45 /*kVK_ANSI_KeypadPlus*/, 'K', '6'},
122 {0x4e /*kVK_ANSI_KeypadMinus*/, 'K', '7'},
123 {0x4b /*kVK_ANSI_KeypadDivide*/, 'K', '8'},
124 {0x43 /*kVK_ANSI_KeypadMultiply*/, 'K', '9'},
125 {0x4c /*kVK_ANSI_KeypadEnter*/, 'K', 'A'},
126 {0x41 /*kVK_ANSI_KeypadDecimal*/, 'K', 'B'},
127 {0x47 /*kVK_ANSI_KeypadClear*/, KS_EXTRA, (char_u)KE_KDEL},
129 {0x52 /*kVK_ANSI_Keypad0*/, 'K', 'C'},
130 {0x53 /*kVK_ANSI_Keypad1*/, 'K', 'D'},
131 {0x54 /*kVK_ANSI_Keypad2*/, 'K', 'E'},
132 {0x55 /*kVK_ANSI_Keypad3*/, 'K', 'F'},
133 {0x56 /*kVK_ANSI_Keypad4*/, 'K', 'G'},
134 {0x57 /*kVK_ANSI_Keypad5*/, 'K', 'H'},
135 {0x58 /*kVK_ANSI_Keypad6*/, 'K', 'I'},
136 {0x59 /*kVK_ANSI_Keypad7*/, 'K', 'J'},
137 {0x5b /*kVK_ANSI_Keypad8*/, 'K', 'K'},
138 {0x5c /*kVK_ANSI_Keypad9*/, 'K', 'L'},
140 /* Keys that we want to be able to use any modifier with: */
141 {0x31 /*kVK_Space*/, ' ', NUL},
142 {0x30 /*kVK_Tab*/, TAB, NUL},
143 {0x35 /*kVK_Escape*/, ESC, NUL},
144 {0x24 /*kVK_Return*/, CAR, NUL},
146 /* End of list marker: */
151 extern GuiFont gui_mch_retain_font(GuiFont font);
155 @interface NSString (MMServerNameCompare)
156 - (NSComparisonResult)serverNameCompare:(NSString *)string;
162 @interface MMBackend (Private)
163 - (void)clearDrawData;
164 - (void)didChangeWholeLine;
165 - (void)waitForDialogReturn;
166 - (void)insertVimStateMessage;
167 - (void)processInputQueue;
168 - (void)handleInputEvent:(int)msgid data:(NSData *)data;
169 - (void)doKeyDown:(NSString *)key
170 keyCode:(unsigned)code
172 - (BOOL)handleSpecialKey:(NSString *)key
173 keyCode:(unsigned)code
175 - (BOOL)handleMacMetaKey:(int)ikey modifiers:(int)mods;
176 - (void)queueMessage:(int)msgid data:(NSData *)data;
177 - (void)connectionDidDie:(NSNotification *)notification;
178 - (void)blinkTimerFired:(NSTimer *)timer;
179 - (void)focusChange:(BOOL)on;
180 - (void)handleToggleToolbar;
181 - (void)handleScrollbarEvent:(NSData *)data;
182 - (void)handleSetFont:(NSData *)data;
183 - (void)handleDropFiles:(NSData *)data;
184 - (void)handleDropString:(NSData *)data;
185 - (void)startOdbEditWithArguments:(NSDictionary *)args;
186 - (void)handleXcodeMod:(NSData *)data;
187 - (void)handleOpenWithArguments:(NSDictionary *)args;
188 - (BOOL)checkForModifiedBuffers;
189 - (void)addInput:(NSString *)input;
190 - (BOOL)unusedEditor;
191 - (void)redrawScreen;
192 - (void)handleFindReplace:(NSDictionary *)args;
193 - (void)handleMarkedText:(NSData *)data;
198 @interface MMBackend (ClientServer)
199 - (NSString *)connectionNameFromServerName:(NSString *)name;
200 - (NSConnection *)connectionForServerName:(NSString *)name;
201 - (NSConnection *)connectionForServerPort:(int)port;
202 - (void)serverConnectionDidDie:(NSNotification *)notification;
203 - (void)addClient:(NSDistantObject *)client;
204 - (NSString *)alternateServerNameForName:(NSString *)name;
209 @implementation MMBackend
211 + (MMBackend *)sharedInstance
213 static MMBackend *singleton = nil;
214 return singleton ? singleton : (singleton = [MMBackend new]);
220 if (!self) return nil;
222 outputQueue = [[NSMutableArray alloc] init];
223 inputQueue = [[NSMutableArray alloc] init];
224 drawData = [[NSMutableData alloc] initWithCapacity:1024];
225 connectionNameDict = [[NSMutableDictionary alloc] init];
226 clientProxyDict = [[NSMutableDictionary alloc] init];
227 serverReplyDict = [[NSMutableDictionary alloc] init];
229 NSBundle *mainBundle = [NSBundle mainBundle];
230 NSString *path = [mainBundle pathForResource:@"Colors" ofType:@"plist"];
232 colorDict = [[NSDictionary dictionaryWithContentsOfFile:path] retain];
234 path = [mainBundle pathForResource:@"SystemColors" ofType:@"plist"];
236 sysColorDict = [[NSDictionary dictionaryWithContentsOfFile:path]
239 path = [mainBundle pathForResource:@"Actions" ofType:@"plist"];
241 actionDict = [[NSDictionary dictionaryWithContentsOfFile:path] retain];
243 if (!(colorDict && sysColorDict && actionDict)) {
244 ASLogNotice(@"Failed to load dictionaries.%@", MMSymlinkWarningString);
254 [[NSNotificationCenter defaultCenter] removeObserver:self];
256 gui_mch_free_font(oldWideFont); oldWideFont = NOFONT;
257 [blinkTimer release]; blinkTimer = nil;
258 [alternateServerName release]; alternateServerName = nil;
259 [serverReplyDict release]; serverReplyDict = nil;
260 [clientProxyDict release]; clientProxyDict = nil;
261 [connectionNameDict release]; connectionNameDict = nil;
262 [inputQueue release]; inputQueue = nil;
263 [outputQueue release]; outputQueue = nil;
264 [drawData release]; drawData = nil;
265 [connection release]; connection = nil;
266 [actionDict release]; actionDict = nil;
267 [sysColorDict release]; sysColorDict = nil;
268 [colorDict release]; colorDict = nil;
269 [vimServerConnection release]; vimServerConnection = nil;
274 - (void)setBackgroundColor:(int)color
276 backgroundColor = MM_COLOR_WITH_TRANSP(color,p_transp);
279 - (void)setForegroundColor:(int)color
281 foregroundColor = MM_COLOR(color);
284 - (void)setSpecialColor:(int)color
286 specialColor = MM_COLOR(color);
289 - (void)setDefaultColorsBackground:(int)bg foreground:(int)fg
291 defaultBackgroundColor = MM_COLOR_WITH_TRANSP(bg,p_transp);
292 defaultForegroundColor = MM_COLOR(fg);
294 NSMutableData *data = [NSMutableData data];
296 [data appendBytes:&defaultBackgroundColor length:sizeof(unsigned)];
297 [data appendBytes:&defaultForegroundColor length:sizeof(unsigned)];
299 [self queueMessage:SetDefaultColorsMsgID data:data];
302 - (NSConnection *)connection
305 // NOTE! If the name of the connection changes here it must also be
306 // updated in MMAppController.m.
307 NSString *name = [NSString stringWithFormat:@"%@-connection",
308 [[NSBundle mainBundle] bundlePath]];
310 connection = [NSConnection connectionWithRegisteredName:name host:nil];
314 // NOTE: 'connection' may be nil here.
318 - (NSDictionary *)actionDict
323 - (int)initialWindowLayout
325 return initialWindowLayout;
328 - (void)queueMessage:(int)msgid properties:(NSDictionary *)props
330 [self queueMessage:msgid data:[props dictionaryAsData]];
335 if (![self connection]) {
337 // This is a preloaded process and as such should not cause the
338 // MacVim to be opened. We probably got here as a result of the
339 // user quitting MacVim while the process was preloading, so exit
341 // (Don't use mch_exit() since it assumes the process has properly
346 NSBundle *mainBundle = [NSBundle mainBundle];
351 // Launch MacVim using Launch Services (NSWorkspace would be nicer, but
352 // the API to pass Apple Event parameters is broken on 10.4).
353 NSString *path = [mainBundle bundlePath];
354 status = FSPathMakeRef((const UInt8 *)[path UTF8String], &ref, NULL);
355 if (noErr == status) {
356 // Pass parameter to the 'Open' Apple Event that tells MacVim not
357 // to open an untitled window.
358 NSAppleEventDescriptor *desc =
359 [NSAppleEventDescriptor recordDescriptor];
360 [desc setParamDescriptor:
361 [NSAppleEventDescriptor descriptorWithBoolean:NO]
362 forKeyword:keyMMUntitledWindow];
364 LSLaunchFSRefSpec spec = { &ref, 0, NULL, [desc aeDesc],
365 kLSLaunchDefaults, NULL };
366 status = LSOpenFromRefSpec(&spec, NULL);
369 if (noErr != status) {
370 ASLogCrit(@"Failed to launch MacVim (path=%@).%@",
371 path, MMSymlinkWarningString);
375 // Launch MacVim using NSTask. For some reason the above code using
376 // Launch Services sometimes fails on LSOpenFromRefSpec() (when it
377 // fails, the dock icon starts bouncing and never stops). It seems
378 // like rebuilding the Launch Services database takes care of this
379 // problem, but the NSTask way seems more stable so stick with it.
381 // NOTE! Using NSTask to launch the GUI has the negative side-effect
382 // that the GUI won't be activated (or raised) so there is a hack in
383 // MMAppController which raises the app when a new window is opened.
384 NSMutableArray *args = [NSMutableArray arrayWithObjects:
385 [NSString stringWithFormat:@"-%@", MMNoWindowKey], @"yes", nil];
386 NSString *exeName = [[mainBundle infoDictionary]
387 objectForKey:@"CFBundleExecutable"];
388 NSString *path = [mainBundle pathForAuxiliaryExecutable:exeName];
390 ASLogCrit(@"Could not find MacVim executable in bundle.%@",
391 MMSymlinkWarningString);
395 [NSTask launchedTaskWithLaunchPath:path arguments:args];
398 // HACK! Poll the mach bootstrap server until it returns a valid
399 // connection to detect that MacVim has finished launching. Also set a
400 // time-out date so that we don't get stuck doing this forever.
401 NSDate *timeOutDate = [NSDate dateWithTimeIntervalSinceNow:10];
402 while (![self connection] &&
403 NSOrderedDescending == [timeOutDate compare:[NSDate date]])
404 [[NSRunLoop currentRunLoop]
405 runMode:NSDefaultRunLoopMode
406 beforeDate:[NSDate dateWithTimeIntervalSinceNow:.1]];
408 // NOTE: [self connection] will set 'connection' as a side-effect.
410 ASLogCrit(@"Timed-out waiting for GUI to launch.");
416 [[NSNotificationCenter defaultCenter] addObserver:self
417 selector:@selector(connectionDidDie:)
418 name:NSConnectionDidDieNotification object:connection];
420 appProxy = [[connection rootProxy] retain];
421 [appProxy setProtocolForProxy:@protocol(MMAppProtocol)];
423 // NOTE: We do not set any new timeout values for the connection to the
424 // frontend. This means that if the frontend is "stuck" (e.g. in a
425 // modal loop) then any calls to the frontend will block indefinitely
426 // (the default timeouts are huge).
428 int pid = [[NSProcessInfo processInfo] processIdentifier];
430 identifier = [appProxy connectBackend:self pid:pid];
433 @catch (NSException *ex) {
434 ASLogDebug(@"Connect backend failed: reason=%@", ex);
440 - (BOOL)openGUIWindow
442 [self queueMessage:OpenWindowMsgID data:nil];
448 int type = ClearAllDrawType;
450 // Any draw commands in queue are effectively obsolete since this clearAll
451 // will negate any effect they have, therefore we may as well clear the
453 [self clearDrawData];
455 [drawData appendBytes:&type length:sizeof(int)];
458 - (void)clearBlockFromRow:(int)row1 column:(int)col1
459 toRow:(int)row2 column:(int)col2
461 int type = ClearBlockDrawType;
463 [drawData appendBytes:&type length:sizeof(int)];
465 [drawData appendBytes:&defaultBackgroundColor length:sizeof(unsigned)];
466 [drawData appendBytes:&row1 length:sizeof(int)];
467 [drawData appendBytes:&col1 length:sizeof(int)];
468 [drawData appendBytes:&row2 length:sizeof(int)];
469 [drawData appendBytes:&col2 length:sizeof(int)];
472 - (void)deleteLinesFromRow:(int)row count:(int)count
473 scrollBottom:(int)bottom left:(int)left right:(int)right
475 int type = DeleteLinesDrawType;
477 [drawData appendBytes:&type length:sizeof(int)];
479 [drawData appendBytes:&defaultBackgroundColor length:sizeof(unsigned)];
480 [drawData appendBytes:&row length:sizeof(int)];
481 [drawData appendBytes:&count length:sizeof(int)];
482 [drawData appendBytes:&bottom length:sizeof(int)];
483 [drawData appendBytes:&left length:sizeof(int)];
484 [drawData appendBytes:&right length:sizeof(int)];
486 if (left == 0 && right == gui.num_cols-1)
487 [self didChangeWholeLine];
490 - (void)drawString:(char_u*)s length:(int)len row:(int)row
491 column:(int)col cells:(int)cells flags:(int)flags
493 if (len <= 0) return;
495 int type = DrawStringDrawType;
497 [drawData appendBytes:&type length:sizeof(int)];
499 [drawData appendBytes:&backgroundColor length:sizeof(unsigned)];
500 [drawData appendBytes:&foregroundColor length:sizeof(unsigned)];
501 [drawData appendBytes:&specialColor length:sizeof(unsigned)];
502 [drawData appendBytes:&row length:sizeof(int)];
503 [drawData appendBytes:&col length:sizeof(int)];
504 [drawData appendBytes:&cells length:sizeof(int)];
505 [drawData appendBytes:&flags length:sizeof(int)];
506 [drawData appendBytes:&len length:sizeof(int)];
507 [drawData appendBytes:s length:len];
510 - (void)insertLinesFromRow:(int)row count:(int)count
511 scrollBottom:(int)bottom left:(int)left right:(int)right
513 int type = InsertLinesDrawType;
515 [drawData appendBytes:&type length:sizeof(int)];
517 [drawData appendBytes:&defaultBackgroundColor length:sizeof(unsigned)];
518 [drawData appendBytes:&row length:sizeof(int)];
519 [drawData appendBytes:&count length:sizeof(int)];
520 [drawData appendBytes:&bottom length:sizeof(int)];
521 [drawData appendBytes:&left length:sizeof(int)];
522 [drawData appendBytes:&right length:sizeof(int)];
524 if (left == 0 && right == gui.num_cols-1)
525 [self didChangeWholeLine];
528 - (void)drawCursorAtRow:(int)row column:(int)col shape:(int)shape
529 fraction:(int)percent color:(int)color
531 int type = DrawCursorDrawType;
532 unsigned uc = MM_COLOR(color);
534 [drawData appendBytes:&type length:sizeof(int)];
536 [drawData appendBytes:&uc length:sizeof(unsigned)];
537 [drawData appendBytes:&row length:sizeof(int)];
538 [drawData appendBytes:&col length:sizeof(int)];
539 [drawData appendBytes:&shape length:sizeof(int)];
540 [drawData appendBytes:&percent length:sizeof(int)];
543 - (void)drawInvertedRectAtRow:(int)row column:(int)col numRows:(int)nr
544 numColumns:(int)nc invert:(int)invert
546 int type = DrawInvertedRectDrawType;
547 [drawData appendBytes:&type length:sizeof(int)];
549 [drawData appendBytes:&row length:sizeof(int)];
550 [drawData appendBytes:&col length:sizeof(int)];
551 [drawData appendBytes:&nr length:sizeof(int)];
552 [drawData appendBytes:&nc length:sizeof(int)];
553 [drawData appendBytes:&invert length:sizeof(int)];
558 // Keep running the run-loop until there is no more input to process.
559 while (CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0, true)
560 == kCFRunLoopRunHandledSource)
564 - (void)flushQueue:(BOOL)force
566 // NOTE: This variable allows for better control over when the queue is
567 // flushed. It can be set to YES at the beginning of a sequence of calls
568 // that may potentially add items to the queue, and then restored back to
570 if (flushDisabled) return;
572 if ([drawData length] > 0) {
573 // HACK! Detect changes to 'guifontwide'.
574 if (gui.wide_font != oldWideFont) {
575 gui_mch_free_font(oldWideFont);
576 oldWideFont = gui_mch_retain_font(gui.wide_font);
577 [self setFont:oldWideFont wide:YES];
580 int type = SetCursorPosDrawType;
581 [drawData appendBytes:&type length:sizeof(type)];
582 [drawData appendBytes:&gui.row length:sizeof(gui.row)];
583 [drawData appendBytes:&gui.col length:sizeof(gui.col)];
585 [self queueMessage:BatchDrawMsgID data:[drawData copy]];
586 [self clearDrawData];
589 if ([outputQueue count] > 0) {
590 [self insertVimStateMessage];
593 ASLogDebug(@"Flushing queue: %@",
594 debugStringForMessageQueue(outputQueue));
595 [appProxy processInput:outputQueue forIdentifier:identifier];
597 @catch (NSException *ex) {
598 ASLogDebug(@"processInput:forIdentifer failed: reason=%@", ex);
599 if (![connection isValid]) {
600 ASLogDebug(@"Connection is invalid, exit now!");
601 ASLogDebug(@"waitForAck=%d got_int=%d", waitForAck, got_int);
606 [outputQueue removeAllObjects];
610 - (BOOL)waitForInput:(int)milliseconds
612 // Return NO if we timed out waiting for input, otherwise return YES.
613 BOOL inputReceived = NO;
615 // Only start the run loop if the input queue is empty, otherwise process
616 // the input first so that the input on queue isn't delayed.
617 if ([inputQueue count]) {
620 // Wait for the specified amount of time, unless 'milliseconds' is
621 // negative in which case we wait "forever" (1e6 seconds translates to
622 // approximately 11 days).
623 CFTimeInterval dt = (milliseconds >= 0 ? .001*milliseconds : 1e6);
625 while (CFRunLoopRunInMode(kCFRunLoopDefaultMode, dt, true)
626 == kCFRunLoopRunHandledSource) {
627 // In order to ensure that all input on the run-loop has been
628 // processed we set the timeout to 0 and keep processing until the
629 // run-loop times out.
635 // The above calls may have placed messages on the input queue so process
636 // it now. This call may enter a blocking loop.
637 if ([inputQueue count] > 0)
638 [self processInputQueue];
640 return inputReceived;
645 // NOTE: This is called if mch_exit() is called. Since we assume here that
646 // the process has started properly, be sure to use exit() instead of
647 // mch_exit() to prematurely terminate a process (or set 'isTerminating'
650 // Make sure no connectionDidDie: notification is received now that we are
652 [[NSNotificationCenter defaultCenter] removeObserver:self];
654 // The 'isTerminating' flag indicates that the frontend is also exiting so
655 // there is no need to flush any more output since the frontend won't look
657 if (!isTerminating && [connection isValid]) {
659 // Flush the entire queue in case a VimLeave autocommand added
660 // something to the queue.
661 [self queueMessage:CloseWindowMsgID data:nil];
662 ASLogDebug(@"Flush output queue before exit: %@",
663 debugStringForMessageQueue(outputQueue));
664 [appProxy processInput:outputQueue forIdentifier:identifier];
666 @catch (NSException *ex) {
667 ASLogDebug(@"CloseWindowMsgID send failed: reason=%@", ex);
670 // NOTE: If Cmd-w was pressed to close the window the menu is briefly
671 // highlighted and during this pause the frontend won't receive any DO
672 // messages. If the Vim process exits before this highlighting has
673 // finished Cocoa will emit the following error message:
674 // *** -[NSMachPort handlePortMessage:]: dropping incoming DO message
675 // because the connection or ports are invalid
676 // To avoid this warning we delay here. If the warning still appears
677 // this delay may need to be increased.
681 #ifdef MAC_CLIENTSERVER
682 // The default connection is used for the client/server code.
683 if (vimServerConnection) {
684 [vimServerConnection setRootObject:nil];
685 [vimServerConnection invalidate];
690 - (void)selectTab:(int)index
693 NSData *data = [NSData dataWithBytes:&index length:sizeof(int)];
694 [self queueMessage:SelectTabMsgID data:data];
699 NSMutableData *data = [NSMutableData data];
701 int idx = tabpage_index(curtab) - 1;
702 [data appendBytes:&idx length:sizeof(int)];
705 for (tp = first_tabpage; tp != NULL; tp = tp->tp_next) {
706 // Count the number of windows in the tabpage.
707 //win_T *wp = tp->tp_firstwin;
709 //for (wincount = 0; wp != NULL; wp = wp->w_next, ++wincount);
710 //[data appendBytes:&wincount length:sizeof(int)];
712 int tabProp = MMTabInfoCount;
713 [data appendBytes:&tabProp length:sizeof(int)];
714 for (tabProp = MMTabLabel; tabProp < MMTabInfoCount; ++tabProp) {
715 // This function puts the label of the tab in the global 'NameBuff'.
716 get_tabline_label(tp, (tabProp == MMTabToolTip));
717 NSString *s = [NSString stringWithVimString:NameBuff];
718 int len = [s lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
722 [data appendBytes:&len length:sizeof(int)];
724 [data appendBytes:[s UTF8String] length:len];
728 [self queueMessage:UpdateTabBarMsgID data:data];
731 - (BOOL)tabBarVisible
733 return tabBarVisible;
736 - (void)showTabBar:(BOOL)enable
738 tabBarVisible = enable;
740 int msgid = enable ? ShowTabBarMsgID : HideTabBarMsgID;
741 [self queueMessage:msgid data:nil];
744 - (void)setRows:(int)rows columns:(int)cols
746 int dim[] = { rows, cols };
747 NSData *data = [NSData dataWithBytes:&dim length:2*sizeof(int)];
749 [self queueMessage:SetTextDimensionsMsgID data:data];
752 - (void)setWindowTitle:(char *)title
754 NSMutableData *data = [NSMutableData data];
755 int len = strlen(title);
756 if (len <= 0) return;
758 [data appendBytes:&len length:sizeof(int)];
759 [data appendBytes:title length:len];
761 [self queueMessage:SetWindowTitleMsgID data:data];
764 - (void)setDocumentFilename:(char *)filename
766 NSMutableData *data = [NSMutableData data];
767 int len = filename ? strlen(filename) : 0;
769 [data appendBytes:&len length:sizeof(int)];
771 [data appendBytes:filename length:len];
773 [self queueMessage:SetDocumentFilenameMsgID data:data];
776 - (char *)browseForFileWithAttributes:(NSDictionary *)attr
780 [self queueMessage:BrowseForFileMsgID properties:attr];
781 [self flushQueue:YES];
784 [self waitForDialogReturn];
786 if (dialogReturn && [dialogReturn isKindOfClass:[NSString class]])
787 s = [dialogReturn vimStringSave];
789 [dialogReturn release]; dialogReturn = nil;
791 @catch (NSException *ex) {
792 ASLogDebug(@"Exception: reason=%@", ex);
798 - (oneway void)setDialogReturn:(in bycopy id)obj
800 ASLogDebug(@"%@", obj);
802 // NOTE: This is called by
803 // - [MMVimController panelDidEnd:::], and
804 // - [MMVimController alertDidEnd:::],
805 // to indicate that a save/open panel or alert has finished.
807 // We want to distinguish between "no dialog return yet" and "dialog
808 // returned nothing". The former can be tested with dialogReturn == nil,
809 // the latter with dialogReturn == [NSNull null].
810 if (!obj) obj = [NSNull null];
812 if (obj != dialogReturn) {
813 [dialogReturn release];
814 dialogReturn = [obj retain];
818 - (int)showDialogWithAttributes:(NSDictionary *)attr textField:(char *)txtfield
822 [self queueMessage:ShowDialogMsgID properties:attr];
823 [self flushQueue:YES];
826 [self waitForDialogReturn];
828 if (dialogReturn && [dialogReturn isKindOfClass:[NSArray class]]
829 && [dialogReturn count]) {
830 retval = [[dialogReturn objectAtIndex:0] intValue];
831 if (txtfield && [dialogReturn count] > 1) {
832 NSString *retString = [dialogReturn objectAtIndex:1];
833 char_u *ret = (char_u*)[retString UTF8String];
835 ret = CONVERT_FROM_UTF8(ret);
837 vim_strncpy((char_u*)txtfield, ret, IOSIZE - 1);
839 CONVERT_FROM_UTF8_FREE(ret);
844 [dialogReturn release]; dialogReturn = nil;
846 @catch (NSException *ex) {
847 ASLogDebug(@"Exception: reason=%@", ex);
853 - (void)showToolbar:(int)enable flags:(int)flags
855 NSMutableData *data = [NSMutableData data];
857 [data appendBytes:&enable length:sizeof(int)];
858 [data appendBytes:&flags length:sizeof(int)];
860 [self queueMessage:ShowToolbarMsgID data:data];
863 - (void)createScrollbarWithIdentifier:(int32_t)ident type:(int)type
865 NSMutableData *data = [NSMutableData data];
867 [data appendBytes:&ident length:sizeof(int32_t)];
868 [data appendBytes:&type length:sizeof(int)];
870 [self queueMessage:CreateScrollbarMsgID data:data];
873 - (void)destroyScrollbarWithIdentifier:(int32_t)ident
875 NSMutableData *data = [NSMutableData data];
876 [data appendBytes:&ident length:sizeof(int32_t)];
878 [self queueMessage:DestroyScrollbarMsgID data:data];
881 - (void)showScrollbarWithIdentifier:(int32_t)ident state:(int)visible
883 NSMutableData *data = [NSMutableData data];
885 [data appendBytes:&ident length:sizeof(int32_t)];
886 [data appendBytes:&visible length:sizeof(int)];
888 [self queueMessage:ShowScrollbarMsgID data:data];
891 - (void)setScrollbarPosition:(int)pos length:(int)len identifier:(int32_t)ident
893 NSMutableData *data = [NSMutableData data];
895 [data appendBytes:&ident length:sizeof(int32_t)];
896 [data appendBytes:&pos length:sizeof(int)];
897 [data appendBytes:&len length:sizeof(int)];
899 [self queueMessage:SetScrollbarPositionMsgID data:data];
902 - (void)setScrollbarThumbValue:(long)val size:(long)size max:(long)max
903 identifier:(int32_t)ident
905 float fval = max-size+1 > 0 ? (float)val/(max-size+1) : 0;
906 float prop = (float)size/(max+1);
907 if (fval < 0) fval = 0;
908 else if (fval > 1.0f) fval = 1.0f;
909 if (prop < 0) prop = 0;
910 else if (prop > 1.0f) prop = 1.0f;
912 NSMutableData *data = [NSMutableData data];
914 [data appendBytes:&ident length:sizeof(int32_t)];
915 [data appendBytes:&fval length:sizeof(float)];
916 [data appendBytes:&prop length:sizeof(float)];
918 [self queueMessage:SetScrollbarThumbMsgID data:data];
921 - (void)setFont:(GuiFont)font wide:(BOOL)wide
923 NSString *fontName = (NSString *)font;
925 NSArray *components = [fontName componentsSeparatedByString:@":"];
926 if ([components count] == 2) {
927 size = [[components lastObject] floatValue];
928 fontName = [components objectAtIndex:0];
931 int len = [fontName lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
932 NSMutableData *data = [NSMutableData data];
933 [data appendBytes:&size length:sizeof(float)];
934 [data appendBytes:&len length:sizeof(int)];
937 [data appendBytes:[fontName UTF8String] length:len];
939 return; // Only the wide font can be set to nothing
941 [self queueMessage:(wide ? SetWideFontMsgID : SetFontMsgID) data:data];
944 - (void)executeActionWithName:(NSString *)name
946 int len = [name lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
949 NSMutableData *data = [NSMutableData data];
951 [data appendBytes:&len length:sizeof(int)];
952 [data appendBytes:[name UTF8String] length:len];
954 [self queueMessage:ExecuteActionMsgID data:data];
958 - (void)setMouseShape:(int)shape
960 NSMutableData *data = [NSMutableData data];
961 [data appendBytes:&shape length:sizeof(int)];
962 [self queueMessage:SetMouseShapeMsgID data:data];
965 - (void)setBlinkWait:(int)wait on:(int)on off:(int)off
967 // Vim specifies times in milliseconds, whereas Cocoa wants them in
969 blinkWaitInterval = .001f*wait;
970 blinkOnInterval = .001f*on;
971 blinkOffInterval = .001f*off;
977 [blinkTimer invalidate];
978 [blinkTimer release];
982 if (blinkWaitInterval > 0 && blinkOnInterval > 0 && blinkOffInterval > 0
984 blinkState = MMBlinkStateOn;
986 [[NSTimer scheduledTimerWithTimeInterval:blinkWaitInterval
988 selector:@selector(blinkTimerFired:)
989 userInfo:nil repeats:NO] retain];
990 gui_update_cursor(TRUE, FALSE);
991 [self flushQueue:YES];
997 if (MMBlinkStateOff == blinkState) {
998 gui_update_cursor(TRUE, FALSE);
999 [self flushQueue:YES];
1002 blinkState = MMBlinkStateNone;
1005 - (void)adjustLinespace:(int)linespace
1007 NSMutableData *data = [NSMutableData data];
1008 [data appendBytes:&linespace length:sizeof(int)];
1009 [self queueMessage:AdjustLinespaceMsgID data:data];
1014 [self queueMessage:ActivateMsgID data:nil];
1017 - (void)setPreEditRow:(int)row column:(int)col
1019 NSMutableData *data = [NSMutableData data];
1020 [data appendBytes:&row length:sizeof(int)];
1021 [data appendBytes:&col length:sizeof(int)];
1022 [self queueMessage:SetPreEditPositionMsgID data:data];
1025 - (int)lookupColorWithKey:(NSString *)key
1027 if (!(key && [key length] > 0))
1030 NSString *stripKey = [[[[key lowercaseString]
1031 stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]
1032 componentsSeparatedByString:@" "]
1033 componentsJoinedByString:@""];
1035 if (stripKey && [stripKey length] > 0) {
1036 // First of all try to lookup key in the color dictionary; note that
1037 // all keys in this dictionary are lowercase with no whitespace.
1038 id obj = [colorDict objectForKey:stripKey];
1039 if (obj) return [obj intValue];
1041 // The key was not in the dictionary; is it perhaps of the form
1043 if ([stripKey length] > 1 && [stripKey characterAtIndex:0] == '#') {
1044 NSScanner *scanner = [NSScanner scannerWithString:stripKey];
1045 [scanner setScanLocation:1];
1047 if ([scanner scanHexInt:&hex]) {
1052 // As a last resort, check if it is one of the system defined colors.
1053 // The keys in this dictionary are also lowercase with no whitespace.
1054 obj = [sysColorDict objectForKey:stripKey];
1056 NSColor *col = [NSColor performSelector:NSSelectorFromString(obj)];
1059 col = [col colorUsingColorSpaceName:NSCalibratedRGBColorSpace];
1060 [col getRed:&r green:&g blue:&b alpha:&a];
1061 return (((int)(r*255+.5f) & 0xff) << 16)
1062 + (((int)(g*255+.5f) & 0xff) << 8)
1063 + ((int)(b*255+.5f) & 0xff);
1068 ASLogNotice(@"No color with key %@ found.", stripKey);
1072 - (BOOL)hasSpecialKeyWithValue:(char_u *)value
1075 for (i = 0; special_keys[i].key_sym != 0; i++) {
1076 if (value[0] == special_keys[i].vim_code0
1077 && value[1] == special_keys[i].vim_code1)
1084 - (void)enterFullscreen:(int)fuoptions background:(int)bg
1086 NSMutableData *data = [NSMutableData data];
1087 [data appendBytes:&fuoptions length:sizeof(int)];
1089 [data appendBytes:&bg length:sizeof(int)];
1090 [self queueMessage:EnterFullscreenMsgID data:data];
1093 - (void)leaveFullscreen
1095 [self queueMessage:LeaveFullscreenMsgID data:nil];
1098 - (void)setFullscreenBackgroundColor:(int)color
1100 NSMutableData *data = [NSMutableData data];
1101 color = MM_COLOR(color);
1102 [data appendBytes:&color length:sizeof(int)];
1104 [self queueMessage:SetFullscreenColorMsgID data:data];
1107 - (void)setAntialias:(BOOL)antialias
1109 int msgid = antialias ? EnableAntialiasMsgID : DisableAntialiasMsgID;
1111 [self queueMessage:msgid data:nil];
1114 - (void)updateModifiedFlag
1116 // Notify MacVim if _any_ buffer has changed from unmodified to modified or
1118 int msgid = [self checkForModifiedBuffers]
1119 ? BuffersModifiedMsgID : BuffersNotModifiedMsgID;
1121 [self queueMessage:msgid data:nil];
1124 - (oneway void)processInput:(int)msgid data:(in bycopy NSData *)data
1126 // Look for Ctrl-C immediately instead of waiting until the input queue is
1127 // processed since that only happens in waitForInput: (and Vim regularly
1128 // checks for Ctrl-C in between waiting for input). Note that the flag
1129 // ctrl_c_interrupts is 0 e.g. when the user has mappings to something like
1130 // <C-c>g. Also it seems the flag intr_char is 0 when MacVim was started
1131 // from Finder whereas it is 0x03 (= Ctrl_C) when started from Terminal.
1133 // Similarly, TerminateNowMsgID must be checked immediately otherwise code
1134 // which waits on the run loop will fail to detect this message (e.g. in
1135 // waitForConnectionAcknowledgement).
1137 if (KeyDownMsgID == msgid && data != nil && ctrl_c_interrupts) {
1138 const void *bytes = [data bytes];
1139 /*unsigned mods = *((unsigned*)bytes);*/ bytes += sizeof(unsigned);
1140 /*unsigned code = *((unsigned*)bytes);*/ bytes += sizeof(unsigned);
1141 unsigned len = *((unsigned*)bytes); bytes += sizeof(unsigned);
1143 char_u *str = (char_u*)bytes;
1144 if (str[0] == Ctrl_C || (str[0] == intr_char && intr_char != 0)) {
1145 ASLogDebug(@"Got INT, str[0]=%#x ctrl_c_interrupts=%d "
1146 "intr_char=%#x", str[0], ctrl_c_interrupts, intr_char);
1148 [inputQueue removeAllObjects];
1152 } else if (TerminateNowMsgID == msgid) {
1153 // Terminate immediately (the frontend is about to quit or this process
1154 // was aborted). Don't preserve modified files since the user would
1155 // already have been presented with a dialog warning if there were any
1156 // modified files when we get here.
1157 isTerminating = YES;
1162 // Remove all previous instances of this message from the input queue, else
1163 // the input queue may fill up as a result of Vim not being able to keep up
1164 // with the speed at which new messages are received.
1165 // Keyboard input is never dropped, unless the input represents an
1166 // auto-repeated key.
1168 BOOL isKeyRepeat = NO;
1169 BOOL isKeyboardInput = NO;
1171 if (data && KeyDownMsgID == msgid) {
1172 isKeyboardInput = YES;
1174 // The lowest bit of the first int is set if this key is a repeat.
1175 int flags = *((int*)[data bytes]);
1180 // Keyboard input is not removed from the queue; repeats are ignored if
1181 // there already is keyboard input on the input queue.
1182 if (isKeyRepeat || !isKeyboardInput) {
1183 int i, count = [inputQueue count];
1184 for (i = 1; i < count; i+=2) {
1185 if ([[inputQueue objectAtIndex:i-1] intValue] == msgid) {
1189 [inputQueue removeObjectAtIndex:i];
1190 [inputQueue removeObjectAtIndex:i-1];
1196 [inputQueue addObject:[NSNumber numberWithInt:msgid]];
1197 [inputQueue addObject:(data ? (id)data : [NSNull null])];
1200 - (id)evaluateExpressionCocoa:(in bycopy NSString *)expr
1201 errorString:(out bycopy NSString **)errstr
1203 return evalExprCocoa(expr, errstr);
1207 - (NSString *)evaluateExpression:(in bycopy NSString *)expr
1209 NSString *eval = nil;
1210 char_u *s = (char_u*)[expr UTF8String];
1213 s = CONVERT_FROM_UTF8(s);
1216 char_u *res = eval_client_expr_to_string(s);
1219 CONVERT_FROM_UTF8_FREE(s);
1225 s = CONVERT_TO_UTF8(s);
1227 eval = [NSString stringWithUTF8String:(char*)s];
1229 CONVERT_TO_UTF8_FREE(s);
1237 - (BOOL)starRegisterToPasteboard:(byref NSPasteboard *)pboard
1239 // TODO: This method should share code with clip_mch_request_selection().
1241 if (VIsual_active && (State & NORMAL) && clip_star.available) {
1242 // If there is no pasteboard, return YES to indicate that there is text
1247 clip_copy_selection();
1249 // Get the text to put on the pasteboard.
1250 long_u llen = 0; char_u *str = 0;
1251 int type = clip_convert_selection(&str, &llen, &clip_star);
1255 // TODO: Avoid overflow.
1256 int len = (int)llen;
1258 if (output_conv.vc_type != CONV_NONE) {
1259 char_u *conv_str = string_convert(&output_conv, str, &len);
1267 NSString *string = [[NSString alloc]
1268 initWithBytes:str length:len encoding:NSUTF8StringEncoding];
1270 NSArray *types = [NSArray arrayWithObject:NSStringPboardType];
1271 [pboard declareTypes:types owner:nil];
1272 BOOL ok = [pboard setString:string forType:NSStringPboardType];
1283 - (oneway void)addReply:(in bycopy NSString *)reply
1284 server:(in byref id <MMVimServerProtocol>)server
1286 ASLogDebug(@"reply=%@ server=%@", reply, (id)server);
1288 // Replies might come at any time and in any order so we keep them in an
1289 // array inside a dictionary with the send port used as key.
1291 NSConnection *conn = [(NSDistantObject*)server connectionForProxy];
1292 // HACK! Assume connection uses mach ports.
1293 int port = [(NSMachPort*)[conn sendPort] machPort];
1294 NSNumber *key = [NSNumber numberWithInt:port];
1296 NSMutableArray *replies = [serverReplyDict objectForKey:key];
1298 replies = [NSMutableArray array];
1299 [serverReplyDict setObject:replies forKey:key];
1302 [replies addObject:reply];
1305 - (void)addInput:(in bycopy NSString *)input
1306 client:(in byref id <MMVimClientProtocol>)client
1308 ASLogDebug(@"input=%@ client=%@", input, (id)client);
1310 // NOTE: We don't call addInput: here because it differs from
1311 // server_to_input_buf() in that it always sets the 'silent' flag and we
1312 // don't want the MacVim client/server code to behave differently from
1314 char_u *s = [input vimStringSave];
1315 server_to_input_buf(s);
1318 [self addClient:(id)client];
1321 - (NSString *)evaluateExpression:(in bycopy NSString *)expr
1322 client:(in byref id <MMVimClientProtocol>)client
1324 [self addClient:(id)client];
1325 return [self evaluateExpression:expr];
1328 - (void)registerServerWithName:(NSString *)name
1330 NSString *svrName = name;
1333 if (vimServerConnection) // Paranoia check, should always be nil
1334 [vimServerConnection release];
1336 vimServerConnection = [[NSConnection alloc]
1337 initWithReceivePort:[NSPort port]
1340 for (i = 0; i < MMServerMax; ++i) {
1341 NSString *connName = [self connectionNameFromServerName:svrName];
1343 if ([vimServerConnection registerName:connName]) {
1344 ASLogInfo(@"Registered server with name: %@", svrName);
1346 // TODO: Set request/reply time-outs to something else?
1348 // Don't wait for requests (time-out means that the message is
1350 [vimServerConnection setRequestTimeout:0];
1351 //[vimServerConnection setReplyTimeout:MMReplyTimeout];
1352 [vimServerConnection setRootObject:self];
1354 // NOTE: 'serverName' is a global variable
1355 serverName = [svrName vimStringSave];
1357 set_vim_var_string(VV_SEND_SERVER, serverName, -1);
1360 need_maketitle = TRUE;
1362 [self queueMessage:SetServerNameMsgID
1363 data:[svrName dataUsingEncoding:NSUTF8StringEncoding]];
1367 svrName = [NSString stringWithFormat:@"%@%d", name, i+1];
1371 - (BOOL)sendToServer:(NSString *)name string:(NSString *)string
1372 reply:(char_u **)reply port:(int *)port expression:(BOOL)expr
1375 // NOTE: If 'name' equals 'serverName' then the request is local (client
1376 // and server are the same). This case is not handled separately, so a
1377 // connection will be set up anyway (this simplifies the code).
1379 NSConnection *conn = [self connectionForServerName:name];
1382 char_u *s = (char_u*)[name UTF8String];
1384 s = CONVERT_FROM_UTF8(s);
1386 EMSG2(_(e_noserver), s);
1388 CONVERT_FROM_UTF8_FREE(s);
1395 // HACK! Assume connection uses mach ports.
1396 *port = [(NSMachPort*)[conn sendPort] machPort];
1399 id proxy = [conn rootProxy];
1400 [proxy setProtocolForProxy:@protocol(MMVimServerProtocol)];
1404 NSString *eval = [proxy evaluateExpression:string client:self];
1407 *reply = [eval vimStringSave];
1409 *reply = vim_strsave((char_u*)_(e_invexprmsg));
1416 [proxy addInput:string client:self];
1419 @catch (NSException *ex) {
1420 ASLogDebug(@"Exception: reason=%@", ex);
1427 - (NSArray *)serverList
1429 NSArray *list = nil;
1431 if ([self connection]) {
1432 id proxy = [connection rootProxy];
1433 [proxy setProtocolForProxy:@protocol(MMAppProtocol)];
1436 list = [proxy serverList];
1438 @catch (NSException *ex) {
1439 ASLogDebug(@"serverList failed: reason=%@", ex);
1442 // We get here if a --remote flag is used before MacVim has started.
1443 ASLogInfo(@"No connection to MacVim, server listing not possible.");
1449 - (NSString *)peekForReplyOnPort:(int)port
1451 ASLogDebug(@"port=%d", port);
1453 NSNumber *key = [NSNumber numberWithInt:port];
1454 NSMutableArray *replies = [serverReplyDict objectForKey:key];
1455 if (replies && [replies count]) {
1456 ASLogDebug(@" %d replies, topmost is: %@", [replies count],
1457 [replies objectAtIndex:0]);
1458 return [replies objectAtIndex:0];
1461 ASLogDebug(@" No replies");
1465 - (NSString *)waitForReplyOnPort:(int)port
1467 ASLogDebug(@"port=%d", port);
1469 NSConnection *conn = [self connectionForServerPort:port];
1473 NSNumber *key = [NSNumber numberWithInt:port];
1474 NSMutableArray *replies = nil;
1475 NSString *reply = nil;
1477 // Wait for reply as long as the connection to the server is valid (unless
1478 // user interrupts wait with Ctrl-C).
1479 while (!got_int && [conn isValid] &&
1480 !(replies = [serverReplyDict objectForKey:key])) {
1481 [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
1482 beforeDate:[NSDate distantFuture]];
1486 if ([replies count] > 0) {
1487 reply = [[replies objectAtIndex:0] retain];
1488 ASLogDebug(@" Got reply: %@", reply);
1489 [replies removeObjectAtIndex:0];
1490 [reply autorelease];
1493 if ([replies count] == 0)
1494 [serverReplyDict removeObjectForKey:key];
1500 - (BOOL)sendReply:(NSString *)reply toPort:(int)port
1502 id client = [clientProxyDict objectForKey:[NSNumber numberWithInt:port]];
1505 ASLogDebug(@"reply=%@ port=%d", reply, port);
1506 [client addReply:reply server:self];
1509 @catch (NSException *ex) {
1510 ASLogDebug(@"addReply:server: failed: reason=%@", ex);
1513 ASLogNotice(@"server2client failed; no client with id %d", port);
1524 - (void)setWaitForAck:(BOOL)yn
1529 - (void)waitForConnectionAcknowledgement
1531 if (!waitForAck) return;
1533 while (waitForAck && !got_int && [connection isValid]) {
1534 [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
1535 beforeDate:[NSDate distantFuture]];
1536 ASLogDebug(@" waitForAck=%d got_int=%d isValid=%d",
1537 waitForAck, got_int, [connection isValid]);
1541 ASLogDebug(@"Never received a connection acknowledgement");
1542 [[NSNotificationCenter defaultCenter] removeObserver:self];
1543 [appProxy release]; appProxy = nil;
1545 // NOTE: We intentionally do not call mch_exit() since this in turn
1546 // will lead to -[MMBackend exit] getting called which we want to
1551 ASLogInfo(@"Connection acknowledgement received");
1552 [self processInputQueue];
1555 - (oneway void)acknowledgeConnection
1566 - (void)setImState:(BOOL)activated
1568 imState = activated;
1571 static void netbeansReadCallback(CFSocketRef s,
1572 CFSocketCallBackType callbackType,
1577 // NetBeans socket is readable.
1578 [[MMBackend sharedInstance] messageFromNetbeans];
1581 - (void)messageFromNetbeans
1583 [inputQueue addObject:[NSNumber numberWithInt:NetBeansMsgID]];
1584 [inputQueue addObject:[NSNull null]];
1587 - (void)setNetbeansSocket:(int)socket
1589 if (netbeansSocket) {
1590 CFRelease(netbeansSocket);
1591 netbeansSocket = NULL;
1594 if (netbeansRunLoopSource) {
1595 CFRunLoopSourceInvalidate(netbeansRunLoopSource);
1596 netbeansRunLoopSource = NULL;
1602 // Tell CFRunLoop that we are interested in NetBeans socket input.
1603 netbeansSocket = CFSocketCreateWithNative(kCFAllocatorDefault,
1605 kCFSocketReadCallBack,
1606 &netbeansReadCallback,
1608 netbeansRunLoopSource = CFSocketCreateRunLoopSource(NULL,
1611 CFRunLoopAddSource(CFRunLoopGetCurrent(),
1612 netbeansRunLoopSource,
1613 kCFRunLoopCommonModes);
1620 @implementation MMBackend (Private)
1622 - (void)clearDrawData
1624 [drawData setLength:0];
1625 numWholeLineChanges = offsetForDrawDataPrune = 0;
1628 - (void)didChangeWholeLine
1630 // It may happen that draw queue is filled up with lots of changes that
1631 // affect a whole row. If the number of such changes equals twice the
1632 // number of visible rows then we can prune some commands off the queue.
1634 // NOTE: If we don't perform this pruning the draw queue may grow
1635 // indefinitely if Vim were to repeatedly send draw commands without ever
1636 // waiting for new input (that's when the draw queue is flushed). The one
1637 // instance I know where this can happen is when a command is executed in
1638 // the shell (think ":grep" with thousands of matches).
1640 ++numWholeLineChanges;
1641 if (numWholeLineChanges == gui.num_rows) {
1642 // Remember the offset to prune up to.
1643 offsetForDrawDataPrune = [drawData length];
1644 } else if (numWholeLineChanges == 2*gui.num_rows) {
1645 // Delete all the unnecessary draw commands.
1646 NSMutableData *d = [[NSMutableData alloc]
1647 initWithBytes:[drawData bytes] + offsetForDrawDataPrune
1648 length:[drawData length] - offsetForDrawDataPrune];
1649 offsetForDrawDataPrune = [d length];
1650 numWholeLineChanges -= gui.num_rows;
1656 - (void)waitForDialogReturn
1658 // Keep processing the run loop until a dialog returns. To avoid getting
1659 // stuck in an endless loop (could happen if the setDialogReturn: message
1660 // was lost) we also do some paranoia checks.
1662 // Note that in Cocoa the user can still resize windows and select menu
1663 // items while a sheet is being displayed, so we can't just wait for the
1664 // first message to arrive and assume that is the setDialogReturn: call.
1666 while (nil == dialogReturn && !got_int && [connection isValid])
1667 [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
1668 beforeDate:[NSDate distantFuture]];
1670 // Search for any resize messages on the input queue. All other messages
1671 // on the input queue are dropped. The reason why we single out resize
1672 // messages is because the user may have resized the window while a sheet
1674 int i, count = [inputQueue count];
1676 id textDimData = nil;
1678 for (i = count-2; i >= 0; i -= 2) {
1679 int msgid = [[inputQueue objectAtIndex:i] intValue];
1680 if (SetTextDimensionsMsgID == msgid) {
1681 textDimData = [[inputQueue objectAtIndex:i+1] retain];
1687 [inputQueue removeAllObjects];
1690 [inputQueue addObject:
1691 [NSNumber numberWithInt:SetTextDimensionsMsgID]];
1692 [inputQueue addObject:textDimData];
1693 [textDimData release];
1698 - (void)insertVimStateMessage
1700 // NOTE: This is the place to add Vim state that needs to be accessed from
1701 // MacVim. Do not add state that could potentially require lots of memory
1702 // since this message gets sent each time the output queue is forcibly
1703 // flushed (e.g. storing the currently selected text would be a bad idea).
1704 // We take this approach of "pushing" the state to MacVim to avoid having
1705 // to make synchronous calls from MacVim to Vim in order to get state.
1707 BOOL mmta = curbuf ? curbuf->b_p_mmta : NO;
1708 int numTabs = tabpage_index(NULL) - 1;
1712 NSDictionary *vimState = [NSDictionary dictionaryWithObjectsAndKeys:
1713 [[NSFileManager defaultManager] currentDirectoryPath], @"pwd",
1714 [NSNumber numberWithInt:p_mh], @"p_mh",
1715 [NSNumber numberWithBool:[self unusedEditor]], @"unusedEditor",
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 unsigned pos = *((unsigned*)bytes); bytes += sizeof(unsigned);
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_end_macvim();
2823 if (!preedit_get_status())
2824 im_preedit_start_macvim();
2826 im_preedit_changed_macvim(chars, pos);
2830 @end // MMBackend (Private)
2835 @implementation MMBackend (ClientServer)
2837 - (NSString *)connectionNameFromServerName:(NSString *)name
2839 NSString *bundlePath = [[NSBundle mainBundle] bundlePath];
2841 return [[NSString stringWithFormat:@"%@.%@", bundlePath, name]
2845 - (NSConnection *)connectionForServerName:(NSString *)name
2847 // TODO: Try 'name%d' if 'name' fails.
2848 NSString *connName = [self connectionNameFromServerName:name];
2849 NSConnection *svrConn = [connectionNameDict objectForKey:connName];
2852 svrConn = [NSConnection connectionWithRegisteredName:connName
2854 // Try alternate server...
2855 if (!svrConn && alternateServerName) {
2856 ASLogInfo(@" trying to connect to alternate server: %@",
2857 alternateServerName);
2858 connName = [self connectionNameFromServerName:alternateServerName];
2859 svrConn = [NSConnection connectionWithRegisteredName:connName
2863 // Try looking for alternate servers...
2865 ASLogInfo(@" looking for alternate servers...");
2866 NSString *alt = [self alternateServerNameForName:name];
2867 if (alt != alternateServerName) {
2868 ASLogInfo(@" found alternate server: %@", alt);
2869 [alternateServerName release];
2870 alternateServerName = [alt copy];
2874 // Try alternate server again...
2875 if (!svrConn && alternateServerName) {
2876 ASLogInfo(@" trying to connect to alternate server: %@",
2877 alternateServerName);
2878 connName = [self connectionNameFromServerName:alternateServerName];
2879 svrConn = [NSConnection connectionWithRegisteredName:connName
2884 [connectionNameDict setObject:svrConn forKey:connName];
2886 ASLogDebug(@"Adding %@ as connection observer for %@",
2888 [[NSNotificationCenter defaultCenter] addObserver:self
2889 selector:@selector(serverConnectionDidDie:)
2890 name:NSConnectionDidDieNotification object:svrConn];
2897 - (NSConnection *)connectionForServerPort:(int)port
2900 NSEnumerator *e = [connectionNameDict objectEnumerator];
2902 while ((conn = [e nextObject])) {
2903 // HACK! Assume connection uses mach ports.
2904 if (port == [(NSMachPort*)[conn sendPort] machPort])
2911 - (void)serverConnectionDidDie:(NSNotification *)notification
2913 ASLogDebug(@"notification=%@", notification);
2915 NSConnection *svrConn = [notification object];
2917 ASLogDebug(@"Removing %@ as connection observer from %@", self, svrConn);
2918 [[NSNotificationCenter defaultCenter]
2920 name:NSConnectionDidDieNotification
2923 [connectionNameDict removeObjectsForKeys:
2924 [connectionNameDict allKeysForObject:svrConn]];
2926 // HACK! Assume connection uses mach ports.
2927 int port = [(NSMachPort*)[svrConn sendPort] machPort];
2928 NSNumber *key = [NSNumber numberWithInt:port];
2930 [clientProxyDict removeObjectForKey:key];
2931 [serverReplyDict removeObjectForKey:key];
2934 - (void)addClient:(NSDistantObject *)client
2936 NSConnection *conn = [client connectionForProxy];
2937 // HACK! Assume connection uses mach ports.
2938 int port = [(NSMachPort*)[conn sendPort] machPort];
2939 NSNumber *key = [NSNumber numberWithInt:port];
2941 if (![clientProxyDict objectForKey:key]) {
2942 [client setProtocolForProxy:@protocol(MMVimClientProtocol)];
2943 [clientProxyDict setObject:client forKey:key];
2946 // NOTE: 'clientWindow' is a global variable which is used by <client>
2947 clientWindow = port;
2950 - (NSString *)alternateServerNameForName:(NSString *)name
2952 if (!(name && [name length] > 0))
2955 // Only look for alternates if 'name' doesn't end in a digit.
2956 unichar lastChar = [name characterAtIndex:[name length]-1];
2957 if (lastChar >= '0' && lastChar <= '9')
2960 // Look for alternates among all current servers.
2961 NSArray *list = [self serverList];
2962 if (!(list && [list count] > 0))
2965 // Filter out servers starting with 'name' and ending with a number. The
2966 // (?i) pattern ensures that the match is case insensitive.
2967 NSString *pat = [NSString stringWithFormat:@"(?i)%@[0-9]+\\z", name];
2968 NSPredicate *pred = [NSPredicate predicateWithFormat:
2969 @"SELF MATCHES %@", pat];
2970 list = [list filteredArrayUsingPredicate:pred];
2971 if ([list count] > 0) {
2972 list = [list sortedArrayUsingSelector:@selector(serverNameCompare:)];
2973 return [list objectAtIndex:0];
2979 @end // MMBackend (ClientServer)
2984 @implementation NSString (MMServerNameCompare)
2985 - (NSComparisonResult)serverNameCompare:(NSString *)string
2987 return [self compare:string
2988 options:NSCaseInsensitiveSearch|NSNumericSearch];
2995 static int eventModifierFlagsToVimModMask(int modifierFlags)
2999 if (modifierFlags & NSShiftKeyMask)
3000 modMask |= MOD_MASK_SHIFT;
3001 if (modifierFlags & NSControlKeyMask)
3002 modMask |= MOD_MASK_CTRL;
3003 if (modifierFlags & NSAlternateKeyMask)
3004 modMask |= MOD_MASK_ALT;
3005 if (modifierFlags & NSCommandKeyMask)
3006 modMask |= MOD_MASK_CMD;
3011 static int eventModifierFlagsToVimMouseModMask(int modifierFlags)
3015 if (modifierFlags & NSShiftKeyMask)
3016 modMask |= MOUSE_SHIFT;
3017 if (modifierFlags & NSControlKeyMask)
3018 modMask |= MOUSE_CTRL;
3019 if (modifierFlags & NSAlternateKeyMask)
3020 modMask |= MOUSE_ALT;
3025 static int eventButtonNumberToVimMouseButton(int buttonNumber)
3027 static int mouseButton[] = { MOUSE_LEFT, MOUSE_RIGHT, MOUSE_MIDDLE };
3029 return (buttonNumber >= 0 && buttonNumber < 3)
3030 ? mouseButton[buttonNumber] : -1;
3035 // This function is modeled after the VimToPython function found in if_python.c
3036 // NB This does a deep copy by value, it does not lookup references like the
3037 // VimToPython function does. This is because I didn't want to deal with the
3038 // retain cycles that this would create, and we can cover 99% of the use cases
3039 // by ignoring it. If we ever switch to using GC in MacVim then this
3040 // functionality can be implemented easily.
3041 static id vimToCocoa(typval_T * tv, int depth)
3047 // Avoid infinite recursion
3052 if (tv->v_type == VAR_STRING) {
3053 char_u * val = tv->vval.v_string;
3054 // val can be NULL if the string is empty
3056 result = [NSString string];
3059 val = CONVERT_TO_UTF8(val);
3061 result = [NSString stringWithUTF8String:(char*)val];
3063 CONVERT_TO_UTF8_FREE(val);
3066 } else if (tv->v_type == VAR_NUMBER) {
3067 // looks like sizeof(varnumber_T) is always <= sizeof(long)
3068 result = [NSNumber numberWithLong:(long)tv->vval.v_number];
3069 } else if (tv->v_type == VAR_LIST) {
3070 list_T * list = tv->vval.v_list;
3073 NSMutableArray * arr = result = [NSMutableArray array];
3076 for (curr = list->lv_first; curr != NULL; curr = curr->li_next) {
3077 newObj = vimToCocoa(&curr->li_tv, depth + 1);
3078 [arr addObject:newObj];
3081 } else if (tv->v_type == VAR_DICT) {
3082 NSMutableDictionary * dict = result = [NSMutableDictionary dictionary];
3084 if (tv->vval.v_dict != NULL) {
3085 hashtab_T * ht = &tv->vval.v_dict->dv_hashtab;
3086 int todo = ht->ht_used;
3090 for (hi = ht->ht_array; todo > 0; ++hi) {
3091 if (!HASHITEM_EMPTY(hi)) {
3094 di = dict_lookup(hi);
3095 newObj = vimToCocoa(&di->di_tv, depth + 1);
3097 char_u * keyval = hi->hi_key;
3099 keyval = CONVERT_TO_UTF8(keyval);
3101 NSString * key = [NSString stringWithUTF8String:(char*)keyval];
3103 CONVERT_TO_UTF8_FREE(keyval);
3105 [dict setObject:newObj forKey:key];
3109 } else { // only func refs should fall into this category?
3117 // This function is modeled after eval_client_expr_to_string found in main.c
3118 // Returns nil if there was an error evaluating the expression, and writes a
3119 // message to errorStr.
3120 // TODO Get the error that occurred while evaluating the expression in vim
3122 static id evalExprCocoa(NSString * expr, NSString ** errstr)
3125 char_u *s = (char_u*)[expr UTF8String];
3128 s = CONVERT_FROM_UTF8(s);
3131 int save_dbl = debug_break_level;
3132 int save_ro = redir_off;
3134 debug_break_level = -1;
3138 typval_T * tvres = eval_expr(s, NULL);
3140 debug_break_level = save_dbl;
3141 redir_off = save_ro;
3148 CONVERT_FROM_UTF8_FREE(s);
3153 gui_update_cursor(FALSE, FALSE);
3156 if (tvres == NULL) {
3158 *errstr = @"Expression evaluation failed.";
3161 id res = vimToCocoa(tvres, 1);
3166 *errstr = @"Conversion to cocoa values failed.";
3174 @implementation NSString (VimStrings)
3176 + (id)stringWithVimString:(char_u *)s
3178 // This method ensures a non-nil string is returned. If 's' cannot be
3179 // converted to a utf-8 string it is assumed to be latin-1. If conversion
3180 // still fails an empty NSString is returned.
3181 NSString *string = nil;
3184 s = CONVERT_TO_UTF8(s);
3186 string = [NSString stringWithUTF8String:(char*)s];
3188 // HACK! Apparently 's' is not a valid utf-8 string, maybe it is
3190 string = [NSString stringWithCString:(char*)s
3191 encoding:NSISOLatin1StringEncoding];
3194 CONVERT_TO_UTF8_FREE(s);
3198 return string != nil ? string : [NSString string];
3201 - (char_u *)vimStringSave
3203 char_u *s = (char_u*)[self UTF8String], *ret = NULL;
3206 s = CONVERT_FROM_UTF8(s);
3208 ret = vim_strsave(s);
3210 CONVERT_FROM_UTF8_FREE(s);
3216 @end // NSString (VimStrings)