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 ASLogNotice(@"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*)s length:(int)len row:(int)row column:(int)col
491 cells:(int)cells flags:(int)flags
493 if (len <= 0 || cells <= 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 ASLogNotice(@"processInput:forIdentifer failed: reason=%@", ex);
599 if (![connection isValid]) {
600 ASLogNotice(@"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 ASLogNotice(@"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 ASLogNotice(@"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 ASLogNotice(@"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 ASLogNotice(@"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 ASLogNotice(@"serverList failed: reason=%@", ex);
1442 EMSG(_("E???: No connection to MacVim, server listing not possible."));
1448 - (NSString *)peekForReplyOnPort:(int)port
1450 ASLogDebug(@"port=%d", port);
1452 NSNumber *key = [NSNumber numberWithInt:port];
1453 NSMutableArray *replies = [serverReplyDict objectForKey:key];
1454 if (replies && [replies count]) {
1455 ASLogDebug(@" %d replies, topmost is: %@", [replies count],
1456 [replies objectAtIndex:0]);
1457 return [replies objectAtIndex:0];
1460 ASLogDebug(@" No replies");
1464 - (NSString *)waitForReplyOnPort:(int)port
1466 ASLogDebug(@"port=%d", port);
1468 NSConnection *conn = [self connectionForServerPort:port];
1472 NSNumber *key = [NSNumber numberWithInt:port];
1473 NSMutableArray *replies = nil;
1474 NSString *reply = nil;
1476 // Wait for reply as long as the connection to the server is valid (unless
1477 // user interrupts wait with Ctrl-C).
1478 while (!got_int && [conn isValid] &&
1479 !(replies = [serverReplyDict objectForKey:key])) {
1480 [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
1481 beforeDate:[NSDate distantFuture]];
1485 if ([replies count] > 0) {
1486 reply = [[replies objectAtIndex:0] retain];
1487 ASLogDebug(@" Got reply: %@", reply);
1488 [replies removeObjectAtIndex:0];
1489 [reply autorelease];
1492 if ([replies count] == 0)
1493 [serverReplyDict removeObjectForKey:key];
1499 - (BOOL)sendReply:(NSString *)reply toPort:(int)port
1501 id client = [clientProxyDict objectForKey:[NSNumber numberWithInt:port]];
1504 ASLogDebug(@"reply=%@ port=%d", reply, port);
1505 [client addReply:reply server:self];
1508 @catch (NSException *ex) {
1509 ASLogNotice(@"addReply:server: failed: reason=%@", ex);
1512 EMSG2(_("E???: server2client failed; no client with id 0x%x"), port);
1523 - (void)setWaitForAck:(BOOL)yn
1528 - (void)waitForConnectionAcknowledgement
1530 if (!waitForAck) return;
1532 while (waitForAck && !got_int && [connection isValid]) {
1533 [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
1534 beforeDate:[NSDate distantFuture]];
1535 ASLogDebug(@" waitForAck=%d got_int=%d isValid=%d",
1536 waitForAck, got_int, [connection isValid]);
1540 ASLogDebug(@"Never received a connection acknowledgement");
1541 [[NSNotificationCenter defaultCenter] removeObserver:self];
1542 [appProxy release]; appProxy = nil;
1544 // NOTE: We intentionally do not call mch_exit() since this in turn
1545 // will lead to -[MMBackend exit] getting called which we want to
1550 ASLogInfo(@"Connection acknowledgement received");
1551 [self processInputQueue];
1554 - (oneway void)acknowledgeConnection
1565 - (void)setImState:(BOOL)activated
1567 imState = activated;
1570 static void netbeansReadCallback(CFSocketRef s,
1571 CFSocketCallBackType callbackType,
1576 // NetBeans socket is readable.
1577 [[MMBackend sharedInstance] messageFromNetbeans];
1580 - (void)messageFromNetbeans
1582 [inputQueue addObject:[NSNumber numberWithInt:NetBeansMsgID]];
1583 [inputQueue addObject:[NSNull null]];
1586 - (void)setNetbeansSocket:(int)socket
1588 if (netbeansSocket) {
1589 CFRelease(netbeansSocket);
1590 netbeansSocket = NULL;
1593 if (netbeansRunLoopSource) {
1594 CFRunLoopSourceInvalidate(netbeansRunLoopSource);
1595 netbeansRunLoopSource = NULL;
1601 // Tell CFRunLoop that we are interested in NetBeans socket input.
1602 netbeansSocket = CFSocketCreateWithNative(kCFAllocatorDefault,
1604 kCFSocketReadCallBack,
1605 &netbeansReadCallback,
1607 netbeansRunLoopSource = CFSocketCreateRunLoopSource(NULL,
1610 CFRunLoopAddSource(CFRunLoopGetCurrent(),
1611 netbeansRunLoopSource,
1612 kCFRunLoopCommonModes);
1619 @implementation MMBackend (Private)
1621 - (void)clearDrawData
1623 [drawData setLength:0];
1624 numWholeLineChanges = offsetForDrawDataPrune = 0;
1627 - (void)didChangeWholeLine
1629 // It may happen that draw queue is filled up with lots of changes that
1630 // affect a whole row. If the number of such changes equals twice the
1631 // number of visible rows then we can prune some commands off the queue.
1633 // NOTE: If we don't perform this pruning the draw queue may grow
1634 // indefinitely if Vim were to repeatedly send draw commands without ever
1635 // waiting for new input (that's when the draw queue is flushed). The one
1636 // instance I know where this can happen is when a command is executed in
1637 // the shell (think ":grep" with thousands of matches).
1639 ++numWholeLineChanges;
1640 if (numWholeLineChanges == gui.num_rows) {
1641 // Remember the offset to prune up to.
1642 offsetForDrawDataPrune = [drawData length];
1643 } else if (numWholeLineChanges == 2*gui.num_rows) {
1644 // Delete all the unnecessary draw commands.
1645 NSMutableData *d = [[NSMutableData alloc]
1646 initWithBytes:[drawData bytes] + offsetForDrawDataPrune
1647 length:[drawData length] - offsetForDrawDataPrune];
1648 offsetForDrawDataPrune = [d length];
1649 numWholeLineChanges -= gui.num_rows;
1655 - (void)waitForDialogReturn
1657 // Keep processing the run loop until a dialog returns. To avoid getting
1658 // stuck in an endless loop (could happen if the setDialogReturn: message
1659 // was lost) we also do some paranoia checks.
1661 // Note that in Cocoa the user can still resize windows and select menu
1662 // items while a sheet is being displayed, so we can't just wait for the
1663 // first message to arrive and assume that is the setDialogReturn: call.
1665 while (nil == dialogReturn && !got_int && [connection isValid])
1666 [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
1667 beforeDate:[NSDate distantFuture]];
1669 // Search for any resize messages on the input queue. All other messages
1670 // on the input queue are dropped. The reason why we single out resize
1671 // messages is because the user may have resized the window while a sheet
1673 int i, count = [inputQueue count];
1675 id textDimData = nil;
1677 for (i = count-2; i >= 0; i -= 2) {
1678 int msgid = [[inputQueue objectAtIndex:i] intValue];
1679 if (SetTextDimensionsMsgID == msgid) {
1680 textDimData = [[inputQueue objectAtIndex:i+1] retain];
1686 [inputQueue removeAllObjects];
1689 [inputQueue addObject:
1690 [NSNumber numberWithInt:SetTextDimensionsMsgID]];
1691 [inputQueue addObject:textDimData];
1692 [textDimData release];
1697 - (void)insertVimStateMessage
1699 // NOTE: This is the place to add Vim state that needs to be accessed from
1700 // MacVim. Do not add state that could potentially require lots of memory
1701 // since this message gets sent each time the output queue is forcibly
1702 // flushed (e.g. storing the currently selected text would be a bad idea).
1703 // We take this approach of "pushing" the state to MacVim to avoid having
1704 // to make synchronous calls from MacVim to Vim in order to get state.
1706 BOOL mmta = curbuf ? curbuf->b_p_mmta : NO;
1707 int numTabs = tabpage_index(NULL) - 1;
1711 NSDictionary *vimState = [NSDictionary dictionaryWithObjectsAndKeys:
1712 [[NSFileManager defaultManager] currentDirectoryPath], @"pwd",
1713 [NSNumber numberWithInt:p_mh], @"p_mh",
1714 [NSNumber numberWithBool:[self unusedEditor]], @"unusedEditor",
1715 [NSNumber numberWithBool:mmta], @"p_mmta",
1716 [NSNumber numberWithInt:numTabs], @"numTabs",
1719 // Put the state before all other messages.
1720 int msgid = SetVimStateMsgID;
1721 [outputQueue insertObject:[vimState dictionaryAsData] atIndex:0];
1722 [outputQueue insertObject:[NSData dataWithBytes:&msgid length:sizeof(int)]
1726 - (void)processInputQueue
1728 if ([inputQueue count] == 0) return;
1730 // NOTE: One of the input events may cause this method to be called
1731 // recursively, so copy the input queue to a local variable and clear the
1732 // queue before starting to process input events (otherwise we could get
1733 // stuck in an endless loop).
1734 NSArray *q = [inputQueue copy];
1735 unsigned i, count = [q count];
1737 [inputQueue removeAllObjects];
1739 for (i = 1; i < count; i+=2) {
1740 int msgid = [[q objectAtIndex:i-1] intValue];
1741 id data = [q objectAtIndex:i];
1742 if ([data isEqual:[NSNull null]])
1745 ASLogDebug(@"(%d) %s", i, MessageStrings[msgid]);
1746 [self handleInputEvent:msgid data:data];
1753 - (void)handleInputEvent:(int)msgid data:(NSData *)data
1755 if (KeyDownMsgID == msgid) {
1757 const void *bytes = [data bytes];
1758 unsigned mods = *((unsigned*)bytes); bytes += sizeof(unsigned);
1759 unsigned code = *((unsigned*)bytes); bytes += sizeof(unsigned);
1760 unsigned len = *((unsigned*)bytes); bytes += sizeof(unsigned);
1761 NSString *key = [[NSString alloc] initWithBytes:bytes
1763 encoding:NSUTF8StringEncoding];
1764 mods = eventModifierFlagsToVimModMask(mods);
1766 [self doKeyDown:key keyCode:code modifiers:mods];
1768 } else if (ScrollWheelMsgID == msgid) {
1770 const void *bytes = [data bytes];
1772 int row = *((int*)bytes); bytes += sizeof(int);
1773 int col = *((int*)bytes); bytes += sizeof(int);
1774 int flags = *((int*)bytes); bytes += sizeof(int);
1775 float dy = *((float*)bytes); bytes += sizeof(float);
1777 int button = MOUSE_5;
1778 if (dy > 0) button = MOUSE_4;
1780 flags = eventModifierFlagsToVimMouseModMask(flags);
1782 int numLines = (int)round(dy);
1783 if (numLines < 0) numLines = -numLines;
1784 if (numLines == 0) numLines = 1;
1786 #ifdef FEAT_GUI_SCROLL_WHEEL_FORCE
1787 gui.scroll_wheel_force = numLines;
1790 gui_send_mouse_event(button, col, row, NO, flags);
1791 } else if (MouseDownMsgID == msgid) {
1793 const void *bytes = [data bytes];
1795 int row = *((int*)bytes); bytes += sizeof(int);
1796 int col = *((int*)bytes); bytes += sizeof(int);
1797 int button = *((int*)bytes); bytes += sizeof(int);
1798 int flags = *((int*)bytes); bytes += sizeof(int);
1799 int count = *((int*)bytes); bytes += sizeof(int);
1801 button = eventButtonNumberToVimMouseButton(button);
1803 flags = eventModifierFlagsToVimMouseModMask(flags);
1804 gui_send_mouse_event(button, col, row, count>1, flags);
1806 } else if (MouseUpMsgID == msgid) {
1808 const void *bytes = [data bytes];
1810 int row = *((int*)bytes); bytes += sizeof(int);
1811 int col = *((int*)bytes); bytes += sizeof(int);
1812 int flags = *((int*)bytes); bytes += sizeof(int);
1814 flags = eventModifierFlagsToVimMouseModMask(flags);
1816 gui_send_mouse_event(MOUSE_RELEASE, col, row, NO, flags);
1817 } else if (MouseDraggedMsgID == msgid) {
1819 const void *bytes = [data bytes];
1821 int row = *((int*)bytes); bytes += sizeof(int);
1822 int col = *((int*)bytes); bytes += sizeof(int);
1823 int flags = *((int*)bytes); bytes += sizeof(int);
1825 flags = eventModifierFlagsToVimMouseModMask(flags);
1827 gui_send_mouse_event(MOUSE_DRAG, col, row, NO, flags);
1828 } else if (MouseMovedMsgID == msgid) {
1829 const void *bytes = [data bytes];
1830 int row = *((int*)bytes); bytes += sizeof(int);
1831 int col = *((int*)bytes); bytes += sizeof(int);
1833 gui_mouse_moved(col, row);
1834 } else if (AddInputMsgID == msgid) {
1835 NSString *string = [[NSString alloc] initWithData:data
1836 encoding:NSUTF8StringEncoding];
1838 [self addInput:string];
1841 } else if (SelectTabMsgID == msgid) {
1843 const void *bytes = [data bytes];
1844 int idx = *((int*)bytes) + 1;
1845 send_tabline_event(idx);
1846 } else if (CloseTabMsgID == msgid) {
1848 const void *bytes = [data bytes];
1849 int idx = *((int*)bytes) + 1;
1850 send_tabline_menu_event(idx, TABLINE_MENU_CLOSE);
1851 } else if (AddNewTabMsgID == msgid) {
1852 send_tabline_menu_event(0, TABLINE_MENU_NEW);
1853 } else if (DraggedTabMsgID == msgid) {
1855 const void *bytes = [data bytes];
1856 // NOTE! The destination index is 0 based, so do not add 1 to make it 1
1858 int idx = *((int*)bytes);
1861 } else if (SetTextDimensionsMsgID == msgid || LiveResizeMsgID == msgid
1862 || SetTextRowsMsgID == msgid || SetTextColumnsMsgID == msgid) {
1864 const void *bytes = [data bytes];
1866 if (SetTextColumnsMsgID != msgid) {
1867 rows = *((int*)bytes); bytes += sizeof(int);
1870 if (SetTextRowsMsgID != msgid) {
1871 cols = *((int*)bytes); bytes += sizeof(int);
1875 if (SetTextRowsMsgID == msgid || SetTextColumnsMsgID == msgid) {
1876 int dim[2] = { rows, cols };
1877 d = [NSData dataWithBytes:dim length:2*sizeof(int)];
1878 msgid = SetTextDimensionsReplyMsgID;
1881 if (SetTextDimensionsMsgID == msgid)
1882 msgid = SetTextDimensionsReplyMsgID;
1884 // NOTE! Vim doesn't call gui_mch_set_shellsize() after
1885 // gui_resize_shell(), so we have to manually set the rows and columns
1886 // here since MacVim doesn't change the rows and columns to avoid
1887 // inconsistent states between Vim and MacVim. The message sent back
1888 // indicates that it is a reply to a message that originated in MacVim
1889 // since we need to be able to determine where a message originated.
1890 [self queueMessage:msgid data:d];
1892 gui_resize_shell(cols, rows);
1893 } else if (ExecuteMenuMsgID == msgid) {
1894 NSDictionary *attrs = [NSDictionary dictionaryWithData:data];
1896 NSArray *desc = [attrs objectForKey:@"descriptor"];
1897 vimmenu_T *menu = menu_for_descriptor(desc);
1901 } else if (ToggleToolbarMsgID == msgid) {
1902 [self handleToggleToolbar];
1903 } else if (ScrollbarEventMsgID == msgid) {
1904 [self handleScrollbarEvent:data];
1905 } else if (SetFontMsgID == msgid) {
1906 [self handleSetFont:data];
1907 } else if (VimShouldCloseMsgID == msgid) {
1909 } else if (DropFilesMsgID == msgid) {
1910 [self handleDropFiles:data];
1911 } else if (DropStringMsgID == msgid) {
1912 [self handleDropString:data];
1913 } else if (GotFocusMsgID == msgid) {
1915 [self focusChange:YES];
1916 } else if (LostFocusMsgID == msgid) {
1918 [self focusChange:NO];
1919 } else if (SetMouseShapeMsgID == msgid) {
1920 const void *bytes = [data bytes];
1921 int shape = *((int*)bytes); bytes += sizeof(int);
1922 update_mouseshape(shape);
1923 } else if (XcodeModMsgID == msgid) {
1924 [self handleXcodeMod:data];
1925 } else if (OpenWithArgumentsMsgID == msgid) {
1926 [self handleOpenWithArguments:[NSDictionary dictionaryWithData:data]];
1927 } else if (FindReplaceMsgID == msgid) {
1928 [self handleFindReplace:[NSDictionary dictionaryWithData:data]];
1929 } else if (ActivatedImMsgID == msgid) {
1930 [self setImState:YES];
1931 } else if (DeactivatedImMsgID == msgid) {
1932 [self setImState:NO];
1933 } else if (NetBeansMsgID == msgid) {
1934 #ifdef FEAT_NETBEANS_INTG
1935 messageFromNetbeansMacVim();
1937 } else if (SetMarkedTextMsgID == msgid) {
1938 [self handleMarkedText:data];
1940 ASLogWarn(@"Unknown message received (msgid=%d)", msgid);
1944 - (void)doKeyDown:(NSString *)key
1945 keyCode:(unsigned)code
1948 ASLogDebug(@"key='%@' code=%#x mods=%#x length=%d", key, code, mods,
1952 char_u *str = (char_u*)[key UTF8String];
1953 int i, len = [key lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
1955 if ([self handleSpecialKey:key keyCode:code modifiers:mods])
1959 char_u *conv_str = NULL;
1960 if (input_conv.vc_type != CONV_NONE) {
1961 conv_str = string_convert(&input_conv, str, &len);
1967 if (mods & MOD_MASK_CMD) {
1968 // NOTE: For normal input (non-special, 'macmeta' off) the modifier
1969 // flags are already included in the key event. However, the Cmd key
1970 // flag is special and must always be added manually.
1971 // The Shift flag is already included in the key when the Command
1972 // key is held. The same goes for Alt, unless Ctrl is held or
1973 // 'macmeta' is set. It is important that these flags are cleared
1974 // _after_ special keys have been handled, since they should never be
1975 // cleared for special keys.
1976 mods &= ~MOD_MASK_SHIFT;
1977 if (!(mods & MOD_MASK_CTRL)) {
1978 BOOL mmta = curbuf ? curbuf->b_p_mmta : YES;
1980 mods &= ~MOD_MASK_ALT;
1983 ASLogDebug(@"add mods=%#x", mods);
1984 char_u modChars[3] = { CSI, KS_MODIFIER, mods };
1985 add_to_input_buf(modChars, 3);
1986 } else if (mods & MOD_MASK_ALT && 1 == len && str[0] < 0x80
1987 && curbuf && curbuf->b_p_mmta) {
1988 // HACK! The 'macmeta' is set so we have to handle Alt key presses
1989 // separately. Normally Alt key presses are interpreted by the
1990 // frontend but now we have to manually set the 8th bit and deal with
1991 // UTF-8 conversion.
1992 if ([self handleMacMetaKey:str[0] modifiers:mods])
1997 for (i = 0; i < len; ++i) {
1998 ASLogDebug(@"add byte [%d/%d]: %#x", i, len, str[i]);
1999 add_to_input_buf(str+i, 1);
2000 if (CSI == str[i]) {
2001 // NOTE: If the converted string contains the byte CSI, then it
2002 // must be followed by the bytes KS_EXTRA, KE_CSI or things
2004 static char_u extra[2] = { KS_EXTRA, KE_CSI };
2005 ASLogDebug(@"add KS_EXTRA, KE_CSI");
2006 add_to_input_buf(extra, 2);
2016 - (BOOL)handleSpecialKey:(NSString *)key
2017 keyCode:(unsigned)code
2021 for (i = 0; special_keys[i].key_sym != 0; i++) {
2022 if (special_keys[i].key_sym == code) {
2023 ASLogDebug(@"Special key: %#x", code);
2027 if (special_keys[i].key_sym == 0)
2030 int ikey = special_keys[i].vim_code1 == NUL ? special_keys[i].vim_code0 :
2031 TO_SPECIAL(special_keys[i].vim_code0, special_keys[i].vim_code1);
2032 ikey = simplify_key(ikey, &mods);
2039 if (IS_SPECIAL(ikey)) {
2041 chars[1] = K_SECOND(ikey);
2042 chars[2] = K_THIRD(ikey);
2044 } else if (mods & MOD_MASK_ALT && special_keys[i].vim_code1 == 0
2046 && !enc_dbcs // TODO: ? (taken from gui_gtk_x11.c)
2049 ASLogDebug(@"Alt special=%d", ikey);
2051 // NOTE: The last entries in the special_keys struct when pressed
2052 // together with Alt need to be handled separately or they will not
2054 // The following code was gleaned from gui_gtk_x11.c.
2055 mods &= ~MOD_MASK_ALT;
2056 int mkey = 0x80 | ikey;
2058 if (enc_utf8) { // TODO: What about other encodings?
2060 chars[0] = (mkey >> 6) + 0xc0;
2061 chars[1] = mkey & 0xbf;
2062 if (chars[1] == CSI) {
2063 // We end up here when ikey == ESC
2064 chars[2] = KS_EXTRA;
2077 ASLogDebug(@"Just ikey=%d", ikey);
2084 ASLogDebug(@"Adding mods to special: %d", mods);
2085 char_u modChars[3] = { CSI, KS_MODIFIER, (char_u)mods };
2086 add_to_input_buf(modChars, 3);
2089 ASLogDebug(@"Adding special (%d): %x,%x,%x", len,
2090 chars[0], chars[1], chars[2]);
2091 add_to_input_buf(chars, len);
2097 - (BOOL)handleMacMetaKey:(int)ikey modifiers:(int)mods
2099 ASLogDebug(@"ikey=%d mods=%d", ikey, mods);
2101 // This code was taken from gui_w48.c and gui_gtk_x11.c.
2103 int ch = simplify_key(ikey, &mods);
2105 // Remove the SHIFT modifier for keys where it's already included,
2106 // e.g., '(' and '*'
2107 if (ch < 0x100 && !isalpha(ch) && isprint(ch))
2108 mods &= ~MOD_MASK_SHIFT;
2110 // Interpret the ALT key as making the key META, include SHIFT, etc.
2111 ch = extract_modifiers(ch, &mods);
2117 string[len++] = CSI;
2118 string[len++] = KS_MODIFIER;
2119 string[len++] = mods;
2124 // TODO: What if 'enc' is not "utf-8"?
2125 if (enc_utf8 && (ch & 0x80)) { // convert to utf-8
2126 string[len++] = ch & 0xbf;
2127 string[len-2] = ((unsigned)ch >> 6) + 0xc0;
2128 if (string[len-1] == CSI) {
2129 string[len++] = KS_EXTRA;
2130 string[len++] = (int)KE_CSI;
2135 add_to_input_buf(string, len);
2139 - (void)queueMessage:(int)msgid data:(NSData *)data
2141 [outputQueue addObject:[NSData dataWithBytes:&msgid length:sizeof(int)]];
2143 [outputQueue addObject:data];
2145 [outputQueue addObject:[NSData data]];
2148 - (void)connectionDidDie:(NSNotification *)notification
2150 // If the main connection to MacVim is lost this means that either MacVim
2151 // has crashed or this process did not receive its termination message
2152 // properly (e.g. if the TerminateNowMsgID was dropped).
2154 // NOTE: This is not called if a Vim controller invalidates its connection.
2156 ASLogNotice(@"Main connection was lost before process had a chance "
2157 "to terminate; preserving swap files.");
2158 getout_preserve_modified(1);
2161 - (void)blinkTimerFired:(NSTimer *)timer
2163 NSTimeInterval timeInterval = 0;
2165 [blinkTimer release];
2168 if (MMBlinkStateOn == blinkState) {
2169 gui_undraw_cursor();
2170 blinkState = MMBlinkStateOff;
2171 timeInterval = blinkOffInterval;
2172 } else if (MMBlinkStateOff == blinkState) {
2173 gui_update_cursor(TRUE, FALSE);
2174 blinkState = MMBlinkStateOn;
2175 timeInterval = blinkOnInterval;
2178 if (timeInterval > 0) {
2180 [[NSTimer scheduledTimerWithTimeInterval:timeInterval target:self
2181 selector:@selector(blinkTimerFired:)
2182 userInfo:nil repeats:NO] retain];
2183 [self flushQueue:YES];
2187 - (void)focusChange:(BOOL)on
2189 gui_focus_change(on);
2192 - (void)handleToggleToolbar
2194 // If 'go' contains 'T', then remove it, else add it.
2196 char_u go[sizeof(GO_ALL)+2];
2201 p = vim_strchr(go, GO_TOOLBAR);
2205 char_u *end = go + len;
2211 go[len] = GO_TOOLBAR;
2215 set_option_value((char_u*)"guioptions", 0, go, 0);
2217 [self redrawScreen];
2220 - (void)handleScrollbarEvent:(NSData *)data
2224 const void *bytes = [data bytes];
2225 int32_t ident = *((int32_t*)bytes); bytes += sizeof(int32_t);
2226 int hitPart = *((int*)bytes); bytes += sizeof(int);
2227 float fval = *((float*)bytes); bytes += sizeof(float);
2228 scrollbar_T *sb = gui_find_scrollbar(ident);
2231 scrollbar_T *sb_info = sb->wp ? &sb->wp->w_scrollbars[0] : sb;
2232 long value = sb_info->value;
2233 long size = sb_info->size;
2234 long max = sb_info->max;
2235 BOOL isStillDragging = NO;
2236 BOOL updateKnob = YES;
2239 case NSScrollerDecrementPage:
2240 value -= (size > 2 ? size - 2 : 1);
2242 case NSScrollerIncrementPage:
2243 value += (size > 2 ? size - 2 : 1);
2245 case NSScrollerDecrementLine:
2248 case NSScrollerIncrementLine:
2251 case NSScrollerKnob:
2252 isStillDragging = YES;
2254 case NSScrollerKnobSlot:
2255 value = (long)(fval * (max - size + 1));
2262 gui_drag_scrollbar(sb, value, isStillDragging);
2265 // Dragging the knob or option+clicking automatically updates
2266 // the knob position (on the actual NSScroller), so we only
2267 // need to set the knob position in the other cases.
2269 // Update both the left&right vertical scrollbars.
2270 int32_t idL = (int32_t)sb->wp->w_scrollbars[SBAR_LEFT].ident;
2271 int32_t idR = (int32_t)sb->wp->w_scrollbars[SBAR_RIGHT].ident;
2272 [self setScrollbarThumbValue:value size:size max:max
2274 [self setScrollbarThumbValue:value size:size max:max
2277 // Update the horizontal scrollbar.
2278 [self setScrollbarThumbValue:value size:size max:max
2285 - (void)handleSetFont:(NSData *)data
2289 const void *bytes = [data bytes];
2290 int pointSize = (int)*((float*)bytes); bytes += sizeof(float);
2292 unsigned len = *((unsigned*)bytes); bytes += sizeof(unsigned);
2293 NSMutableString *name = [NSMutableString stringWithUTF8String:bytes];
2296 [name appendString:[NSString stringWithFormat:@":h%d", pointSize]];
2297 char_u *s = (char_u*)[name UTF8String];
2299 unsigned wlen = *((unsigned*)bytes); bytes += sizeof(unsigned);
2302 NSMutableString *wname = [NSMutableString stringWithUTF8String:bytes];
2305 [wname appendString:[NSString stringWithFormat:@":h%d", pointSize]];
2306 ws = (char_u*)[wname UTF8String];
2310 s = CONVERT_FROM_UTF8(s);
2312 ws = CONVERT_FROM_UTF8(ws);
2316 set_option_value((char_u*)"guifont", 0, s, 0);
2318 if (ws && gui.wide_font != NOFONT) {
2319 // NOTE: This message is sent on Cmd-+/Cmd-- and as such should only
2320 // change the wide font if 'gfw' is non-empty (the frontend always has
2321 // some wide font set, even if 'gfw' is empty).
2322 set_option_value((char_u*)"guifontwide", 0, ws, 0);
2327 CONVERT_FROM_UTF8_FREE(ws);
2329 CONVERT_FROM_UTF8_FREE(s);
2332 [self redrawScreen];
2335 - (void)handleDropFiles:(NSData *)data
2337 // TODO: Get rid of this method; instead use Vim script directly. At the
2338 // moment I know how to do this to open files in tabs, but I'm not sure how
2339 // to add the filenames to the command line when in command line mode.
2343 NSMutableDictionary *args = [NSMutableDictionary dictionaryWithData:data];
2346 id obj = [args objectForKey:@"forceOpen"];
2347 BOOL forceOpen = YES;
2349 forceOpen = [obj boolValue];
2351 NSArray *filenames = [args objectForKey:@"filenames"];
2352 if (!(filenames && [filenames count] > 0)) return;
2355 if (!forceOpen && (State & CMDLINE)) {
2356 // HACK! If Vim is in command line mode then the files names
2357 // should be added to the command line, instead of opening the
2358 // files in tabs (unless forceOpen is set). This is taken care of by
2359 // gui_handle_drop().
2360 int n = [filenames count];
2361 char_u **fnames = (char_u **)alloc(n * sizeof(char_u *));
2364 for (i = 0; i < n; ++i)
2365 fnames[i] = [[filenames objectAtIndex:i] vimStringSave];
2367 // NOTE! This function will free 'fnames'.
2368 // HACK! It is assumed that the 'x' and 'y' arguments are
2369 // unused when in command line mode.
2370 gui_handle_drop(0, 0, 0, fnames, n);
2375 [self handleOpenWithArguments:args];
2379 - (void)handleDropString:(NSData *)data
2384 char_u dropkey[3] = { CSI, KS_EXTRA, (char_u)KE_DROP };
2385 const void *bytes = [data bytes];
2386 int len = *((int*)bytes); bytes += sizeof(int);
2387 NSMutableString *string = [NSMutableString stringWithUTF8String:bytes];
2389 // Replace unrecognized end-of-line sequences with \x0a (line feed).
2390 NSRange range = { 0, [string length] };
2391 unsigned n = [string replaceOccurrencesOfString:@"\x0d\x0a"
2392 withString:@"\x0a" options:0
2395 n = [string replaceOccurrencesOfString:@"\x0d" withString:@"\x0a"
2396 options:0 range:range];
2399 len = [string lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
2400 char_u *s = (char_u*)[string UTF8String];
2402 if (input_conv.vc_type != CONV_NONE)
2403 s = string_convert(&input_conv, s, &len);
2405 dnd_yank_drag_data(s, len);
2407 if (input_conv.vc_type != CONV_NONE)
2410 add_to_input_buf(dropkey, sizeof(dropkey));
2414 - (void)startOdbEditWithArguments:(NSDictionary *)args
2416 #ifdef FEAT_ODB_EDITOR
2417 id obj = [args objectForKey:@"remoteID"];
2420 OSType serverID = [obj unsignedIntValue];
2421 NSString *remotePath = [args objectForKey:@"remotePath"];
2423 NSAppleEventDescriptor *token = nil;
2424 NSData *tokenData = [args objectForKey:@"remoteTokenData"];
2425 obj = [args objectForKey:@"remoteTokenDescType"];
2426 if (tokenData && obj) {
2427 DescType tokenType = [obj unsignedLongValue];
2428 token = [NSAppleEventDescriptor descriptorWithDescriptorType:tokenType
2432 NSArray *filenames = [args objectForKey:@"filenames"];
2433 unsigned i, numFiles = [filenames count];
2434 for (i = 0; i < numFiles; ++i) {
2435 NSString *filename = [filenames objectAtIndex:i];
2436 char_u *s = [filename vimStringSave];
2437 buf_T *buf = buflist_findname(s);
2441 if (buf->b_odb_token) {
2442 [(NSAppleEventDescriptor*)(buf->b_odb_token) release];
2443 buf->b_odb_token = NULL;
2446 if (buf->b_odb_fname) {
2447 vim_free(buf->b_odb_fname);
2448 buf->b_odb_fname = NULL;
2451 buf->b_odb_server_id = serverID;
2454 buf->b_odb_token = [token retain];
2456 buf->b_odb_fname = [remotePath vimStringSave];
2458 ASLogWarn(@"Could not find buffer '%@' for ODB editing.", filename);
2461 #endif // FEAT_ODB_EDITOR
2464 - (void)handleXcodeMod:(NSData *)data
2467 const void *bytes = [data bytes];
2468 DescType type = *((DescType*)bytes); bytes += sizeof(DescType);
2469 unsigned len = *((unsigned*)bytes); bytes += sizeof(unsigned);
2473 NSAppleEventDescriptor *replyEvent = [NSAppleEventDescriptor
2474 descriptorWithDescriptorType:type
2480 - (void)handleOpenWithArguments:(NSDictionary *)args
2482 // ARGUMENT: DESCRIPTION:
2483 // -------------------------------------------------------------
2484 // filenames list of filenames
2485 // dontOpen don't open files specified in above argument
2486 // layout which layout to use to open files
2487 // selectionRange range of lines to select
2488 // searchText string to search for
2489 // cursorLine line to position the cursor on
2490 // cursorColumn column to position the cursor on
2491 // (only valid when "cursorLine" is set)
2492 // remoteID ODB parameter
2493 // remotePath ODB parameter
2494 // remoteTokenDescType ODB parameter
2495 // remoteTokenData ODB parameter
2497 ASLogDebug(@"args=%@ (starting=%d)", args, starting);
2499 NSArray *filenames = [args objectForKey:@"filenames"];
2500 int i, numFiles = filenames ? [filenames count] : 0;
2501 BOOL openFiles = ![[args objectForKey:@"dontOpen"] boolValue];
2502 int layout = [[args objectForKey:@"layout"] intValue];
2504 // Change to directory of first file to open if this is an "unused" editor
2505 // (but do not do this if editing remotely).
2506 if (openFiles && numFiles > 0 && ![args objectForKey:@"remoteID"]
2507 && (starting || [self unusedEditor]) ) {
2508 char_u *s = [[filenames objectAtIndex:0] vimStringSave];
2510 mch_chdir((char*)s);
2518 // When Vim is starting we simply add the files to be opened to the
2519 // global arglist and Vim will take care of opening them for us.
2520 if (openFiles && numFiles > 0) {
2521 for (i = 0; i < numFiles; i++) {
2522 NSString *fname = [filenames objectAtIndex:i];
2525 if (ga_grow(&global_alist.al_ga, 1) == FAIL
2526 || (p = [fname vimStringSave]) == NULL)
2527 exit(2); // See comment in -[MMBackend exit]
2529 alist_add(&global_alist, p, 2);
2532 // Vim will take care of arranging the files added to the arglist
2533 // in windows or tabs; all we must do is to specify which layout to
2535 initialWindowLayout = layout;
2538 // When Vim is already open we resort to some trickery to open the
2539 // files with the specified layout.
2541 // TODO: Figure out a better way to handle this?
2542 if (openFiles && numFiles > 0) {
2543 BOOL oneWindowInTab = topframe ? YES
2544 : (topframe->fr_layout == FR_LEAF);
2545 BOOL bufChanged = NO;
2546 BOOL bufHasFilename = NO;
2548 bufChanged = curbufIsChanged();
2549 bufHasFilename = curbuf->b_ffname != NULL;
2552 // Temporarily disable flushing since the following code may
2553 // potentially cause multiple redraws.
2554 flushDisabled = YES;
2556 BOOL onlyOneTab = (first_tabpage->tp_next == NULL);
2557 if (WIN_TABS == layout && !onlyOneTab) {
2558 // By going to the last tabpage we ensure that the new tabs
2559 // will appear last (if this call is left out, the taborder
2564 // Make sure we're in normal mode first.
2565 [self addInput:@"<C-\\><C-N>"];
2568 // With "split layout" we open a new tab before opening
2569 // multiple files if the current tab has more than one window
2570 // or if there is exactly one window but whose buffer has a
2571 // filename. (The :drop command ensures modified buffers get
2572 // their own window.)
2573 if ((WIN_HOR == layout || WIN_VER == layout) &&
2574 (!oneWindowInTab || bufHasFilename))
2575 [self addInput:@":tabnew<CR>"];
2577 // The files are opened by constructing a ":drop ..." command
2578 // and executing it.
2579 NSMutableString *cmd = (WIN_TABS == layout)
2580 ? [NSMutableString stringWithString:@":tab drop"]
2581 : [NSMutableString stringWithString:@":drop"];
2583 for (i = 0; i < numFiles; ++i) {
2584 NSString *file = [filenames objectAtIndex:i];
2585 file = [file stringByEscapingSpecialFilenameCharacters];
2586 [cmd appendString:@" "];
2587 [cmd appendString:file];
2590 // Temporarily clear 'suffixes' so that the files are opened in
2591 // the same order as they appear in the "filenames" array.
2592 [self addInput:@":let mvim_oldsu=&su|set su=<CR>"];
2594 [self addInput:cmd];
2596 // Split the view into multiple windows if requested.
2597 if (WIN_HOR == layout)
2598 [self addInput:@"|sall"];
2599 else if (WIN_VER == layout)
2600 [self addInput:@"|vert sall"];
2602 // Restore the old value of 'suffixes'.
2603 [self addInput:@"|let &su=mvim_oldsu|unlet mvim_oldsu<CR>"];
2605 // When opening one file we try to reuse the current window,
2606 // but not if its buffer is modified or has a filename.
2607 // However, the 'arglist' layout always opens the file in the
2609 NSString *file = [[filenames lastObject]
2610 stringByEscapingSpecialFilenameCharacters];
2612 if (WIN_HOR == layout) {
2613 if (!(bufHasFilename || bufChanged))
2614 cmd = [NSString stringWithFormat:@":e %@", file];
2616 cmd = [NSString stringWithFormat:@":sp %@", file];
2617 } else if (WIN_VER == layout) {
2618 if (!(bufHasFilename || bufChanged))
2619 cmd = [NSString stringWithFormat:@":e %@", file];
2621 cmd = [NSString stringWithFormat:@":vsp %@", file];
2622 } else if (WIN_TABS == layout) {
2623 if (oneWindowInTab && !(bufHasFilename || bufChanged))
2624 cmd = [NSString stringWithFormat:@":e %@", file];
2626 cmd = [NSString stringWithFormat:@":tabe %@", file];
2628 // (The :drop command will split if there is a modified
2630 cmd = [NSString stringWithFormat:@":drop %@", file];
2633 [self addInput:cmd];
2634 [self addInput:@"<CR>"];
2637 // Force screen redraw (does it have to be this complicated?).
2638 // (This code was taken from the end of gui_handle_drop().)
2639 update_screen(NOT_VALID);
2642 gui_update_cursor(FALSE, FALSE);
2649 if ([args objectForKey:@"remoteID"]) {
2650 // NOTE: We have to delay processing any ODB related arguments since
2651 // the file(s) may not be opened until the input buffer is processed.
2652 [self performSelector:@selector(startOdbEditWithArguments:)
2657 NSString *lineString = [args objectForKey:@"cursorLine"];
2658 if (lineString && [lineString intValue] > 0) {
2659 NSString *columnString = [args objectForKey:@"cursorColumn"];
2660 if (!(columnString && [columnString intValue] > 0))
2661 columnString = @"1";
2663 NSString *cmd = [NSString stringWithFormat:@"<C-\\><C-N>:cal "
2664 "cursor(%@,%@)|norm! zz<CR>:f<CR>", lineString, columnString];
2665 [self addInput:cmd];
2668 NSString *rangeString = [args objectForKey:@"selectionRange"];
2670 // Build a command line string that will select the given range of
2671 // lines. If range.length == 0, then position the cursor on the given
2672 // line but do not select.
2673 NSRange range = NSRangeFromString(rangeString);
2675 if (range.length > 0) {
2676 cmd = [NSString stringWithFormat:@"<C-\\><C-N>%dGV%dGz.0",
2677 NSMaxRange(range), range.location];
2679 cmd = [NSString stringWithFormat:@"<C-\\><C-N>%dGz.0",
2683 [self addInput:cmd];
2686 NSString *searchText = [args objectForKey:@"searchText"];
2688 [self addInput:[NSString stringWithFormat:@"<C-\\><C-N>gg/\\c%@/e<CR>",
2693 - (BOOL)checkForModifiedBuffers
2696 for (buf = firstbuf; buf != NULL; buf = buf->b_next) {
2697 if (bufIsChanged(buf)) {
2705 - (void)addInput:(NSString *)input
2707 // NOTE: This code is essentially identical to server_to_input_buf(),
2708 // except the 'silent' flag is TRUE in the call to ins_typebuf() below.
2709 char_u *string = [input vimStringSave];
2710 if (!string) return;
2712 /* Set 'cpoptions' the way we want it.
2713 * B set - backslashes are *not* treated specially
2714 * k set - keycodes are *not* reverse-engineered
2715 * < unset - <Key> sequences *are* interpreted
2716 * The last but one parameter of replace_termcodes() is TRUE so that the
2717 * <lt> sequence is recognised - needed for a real backslash.
2720 char_u *cpo_save = p_cpo;
2721 p_cpo = (char_u *)"Bk";
2722 char_u *str = replace_termcodes((char_u *)string, &ptr, FALSE, TRUE, FALSE);
2725 if (*ptr != NUL) /* trailing CTRL-V results in nothing */
2728 * Add the string to the input stream.
2729 * Can't use add_to_input_buf() here, we now have K_SPECIAL bytes.
2731 * First clear typed characters from the typeahead buffer, there could
2732 * be half a mapping there. Then append to the existing string, so
2733 * that multiple commands from a client are concatenated.
2735 if (typebuf.tb_maplen < typebuf.tb_len)
2736 del_typebuf(typebuf.tb_len - typebuf.tb_maplen, typebuf.tb_maplen);
2737 (void)ins_typebuf(str, REMAP_NONE, typebuf.tb_len, TRUE, TRUE);
2739 /* Let input_available() know we inserted text in the typeahead
2741 typebuf_was_filled = TRUE;
2747 - (BOOL)unusedEditor
2749 BOOL oneWindowInTab = topframe ? YES
2750 : (topframe->fr_layout == FR_LEAF);
2751 BOOL bufChanged = NO;
2752 BOOL bufHasFilename = NO;
2754 bufChanged = curbufIsChanged();
2755 bufHasFilename = curbuf->b_ffname != NULL;
2758 BOOL onlyOneTab = (first_tabpage->tp_next == NULL);
2760 return onlyOneTab && oneWindowInTab && !bufChanged && !bufHasFilename;
2763 - (void)redrawScreen
2765 // Force screen redraw (does it have to be this complicated?).
2766 redraw_all_later(CLEAR);
2767 update_screen(NOT_VALID);
2770 gui_update_cursor(FALSE, FALSE);
2772 // HACK! The cursor is not put back at the command line by the above
2773 // "redraw commands". The following test seems to do the trick though.
2774 if (State & CMDLINE)
2778 - (void)handleFindReplace:(NSDictionary *)args
2782 NSString *findString = [args objectForKey:@"find"];
2783 if (!findString) return;
2785 char_u *find = [findString vimStringSave];
2786 char_u *replace = [[args objectForKey:@"replace"] vimStringSave];
2787 int flags = [[args objectForKey:@"flags"] intValue];
2789 // NOTE: The flag 0x100 is used to indicate a backward search.
2790 gui_do_findrepl(flags, find, replace, (flags & 0x100) == 0);
2797 - (void)handleMarkedText:(NSData *)data
2799 const void *bytes = [data bytes];
2800 unsigned pos = *((unsigned*)bytes); bytes += sizeof(unsigned);
2801 unsigned len = *((unsigned*)bytes); bytes += sizeof(unsigned);
2802 char *chars = (char *)bytes;
2804 ASLogDebug(@"pos=%d len=%d chars=%s", pos, len, chars);
2807 im_preedit_end_macvim();
2809 if (!preedit_get_status())
2810 im_preedit_start_macvim();
2812 im_preedit_changed_macvim(chars, pos);
2816 @end // MMBackend (Private)
2821 @implementation MMBackend (ClientServer)
2823 - (NSString *)connectionNameFromServerName:(NSString *)name
2825 NSString *bundlePath = [[NSBundle mainBundle] bundlePath];
2827 return [[NSString stringWithFormat:@"%@.%@", bundlePath, name]
2831 - (NSConnection *)connectionForServerName:(NSString *)name
2833 // TODO: Try 'name%d' if 'name' fails.
2834 NSString *connName = [self connectionNameFromServerName:name];
2835 NSConnection *svrConn = [connectionNameDict objectForKey:connName];
2838 svrConn = [NSConnection connectionWithRegisteredName:connName
2840 // Try alternate server...
2841 if (!svrConn && alternateServerName) {
2842 ASLogInfo(@" trying to connect to alternate server: %@",
2843 alternateServerName);
2844 connName = [self connectionNameFromServerName:alternateServerName];
2845 svrConn = [NSConnection connectionWithRegisteredName:connName
2849 // Try looking for alternate servers...
2851 ASLogInfo(@" looking for alternate servers...");
2852 NSString *alt = [self alternateServerNameForName:name];
2853 if (alt != alternateServerName) {
2854 ASLogInfo(@" found alternate server: %@", alt);
2855 [alternateServerName release];
2856 alternateServerName = [alt copy];
2860 // Try alternate server again...
2861 if (!svrConn && alternateServerName) {
2862 ASLogInfo(@" trying to connect to alternate server: %@",
2863 alternateServerName);
2864 connName = [self connectionNameFromServerName:alternateServerName];
2865 svrConn = [NSConnection connectionWithRegisteredName:connName
2870 [connectionNameDict setObject:svrConn forKey:connName];
2872 ASLogDebug(@"Adding %@ as connection observer for %@",
2874 [[NSNotificationCenter defaultCenter] addObserver:self
2875 selector:@selector(serverConnectionDidDie:)
2876 name:NSConnectionDidDieNotification object:svrConn];
2883 - (NSConnection *)connectionForServerPort:(int)port
2886 NSEnumerator *e = [connectionNameDict objectEnumerator];
2888 while ((conn = [e nextObject])) {
2889 // HACK! Assume connection uses mach ports.
2890 if (port == [(NSMachPort*)[conn sendPort] machPort])
2897 - (void)serverConnectionDidDie:(NSNotification *)notification
2899 ASLogDebug(@"notification=%@", notification);
2901 NSConnection *svrConn = [notification object];
2903 ASLogDebug(@"Removing %@ as connection observer from %@", self, svrConn);
2904 [[NSNotificationCenter defaultCenter]
2906 name:NSConnectionDidDieNotification
2909 [connectionNameDict removeObjectsForKeys:
2910 [connectionNameDict allKeysForObject:svrConn]];
2912 // HACK! Assume connection uses mach ports.
2913 int port = [(NSMachPort*)[svrConn sendPort] machPort];
2914 NSNumber *key = [NSNumber numberWithInt:port];
2916 [clientProxyDict removeObjectForKey:key];
2917 [serverReplyDict removeObjectForKey:key];
2920 - (void)addClient:(NSDistantObject *)client
2922 NSConnection *conn = [client connectionForProxy];
2923 // HACK! Assume connection uses mach ports.
2924 int port = [(NSMachPort*)[conn sendPort] machPort];
2925 NSNumber *key = [NSNumber numberWithInt:port];
2927 if (![clientProxyDict objectForKey:key]) {
2928 [client setProtocolForProxy:@protocol(MMVimClientProtocol)];
2929 [clientProxyDict setObject:client forKey:key];
2932 // NOTE: 'clientWindow' is a global variable which is used by <client>
2933 clientWindow = port;
2936 - (NSString *)alternateServerNameForName:(NSString *)name
2938 if (!(name && [name length] > 0))
2941 // Only look for alternates if 'name' doesn't end in a digit.
2942 unichar lastChar = [name characterAtIndex:[name length]-1];
2943 if (lastChar >= '0' && lastChar <= '9')
2946 // Look for alternates among all current servers.
2947 NSArray *list = [self serverList];
2948 if (!(list && [list count] > 0))
2951 // Filter out servers starting with 'name' and ending with a number. The
2952 // (?i) pattern ensures that the match is case insensitive.
2953 NSString *pat = [NSString stringWithFormat:@"(?i)%@[0-9]+\\z", name];
2954 NSPredicate *pred = [NSPredicate predicateWithFormat:
2955 @"SELF MATCHES %@", pat];
2956 list = [list filteredArrayUsingPredicate:pred];
2957 if ([list count] > 0) {
2958 list = [list sortedArrayUsingSelector:@selector(serverNameCompare:)];
2959 return [list objectAtIndex:0];
2965 @end // MMBackend (ClientServer)
2970 @implementation NSString (MMServerNameCompare)
2971 - (NSComparisonResult)serverNameCompare:(NSString *)string
2973 return [self compare:string
2974 options:NSCaseInsensitiveSearch|NSNumericSearch];
2981 static int eventModifierFlagsToVimModMask(int modifierFlags)
2985 if (modifierFlags & NSShiftKeyMask)
2986 modMask |= MOD_MASK_SHIFT;
2987 if (modifierFlags & NSControlKeyMask)
2988 modMask |= MOD_MASK_CTRL;
2989 if (modifierFlags & NSAlternateKeyMask)
2990 modMask |= MOD_MASK_ALT;
2991 if (modifierFlags & NSCommandKeyMask)
2992 modMask |= MOD_MASK_CMD;
2997 static int eventModifierFlagsToVimMouseModMask(int modifierFlags)
3001 if (modifierFlags & NSShiftKeyMask)
3002 modMask |= MOUSE_SHIFT;
3003 if (modifierFlags & NSControlKeyMask)
3004 modMask |= MOUSE_CTRL;
3005 if (modifierFlags & NSAlternateKeyMask)
3006 modMask |= MOUSE_ALT;
3011 static int eventButtonNumberToVimMouseButton(int buttonNumber)
3013 static int mouseButton[] = { MOUSE_LEFT, MOUSE_RIGHT, MOUSE_MIDDLE };
3015 return (buttonNumber >= 0 && buttonNumber < 3)
3016 ? mouseButton[buttonNumber] : -1;
3021 // This function is modeled after the VimToPython function found in if_python.c
3022 // NB This does a deep copy by value, it does not lookup references like the
3023 // VimToPython function does. This is because I didn't want to deal with the
3024 // retain cycles that this would create, and we can cover 99% of the use cases
3025 // by ignoring it. If we ever switch to using GC in MacVim then this
3026 // functionality can be implemented easily.
3027 static id vimToCocoa(typval_T * tv, int depth)
3033 // Avoid infinite recursion
3038 if (tv->v_type == VAR_STRING) {
3039 char_u * val = tv->vval.v_string;
3040 // val can be NULL if the string is empty
3042 result = [NSString string];
3045 val = CONVERT_TO_UTF8(val);
3047 result = [NSString stringWithUTF8String:(char*)val];
3049 CONVERT_TO_UTF8_FREE(val);
3052 } else if (tv->v_type == VAR_NUMBER) {
3053 // looks like sizeof(varnumber_T) is always <= sizeof(long)
3054 result = [NSNumber numberWithLong:(long)tv->vval.v_number];
3055 } else if (tv->v_type == VAR_LIST) {
3056 list_T * list = tv->vval.v_list;
3059 NSMutableArray * arr = result = [NSMutableArray array];
3062 for (curr = list->lv_first; curr != NULL; curr = curr->li_next) {
3063 newObj = vimToCocoa(&curr->li_tv, depth + 1);
3064 [arr addObject:newObj];
3067 } else if (tv->v_type == VAR_DICT) {
3068 NSMutableDictionary * dict = result = [NSMutableDictionary dictionary];
3070 if (tv->vval.v_dict != NULL) {
3071 hashtab_T * ht = &tv->vval.v_dict->dv_hashtab;
3072 int todo = ht->ht_used;
3076 for (hi = ht->ht_array; todo > 0; ++hi) {
3077 if (!HASHITEM_EMPTY(hi)) {
3080 di = dict_lookup(hi);
3081 newObj = vimToCocoa(&di->di_tv, depth + 1);
3083 char_u * keyval = hi->hi_key;
3085 keyval = CONVERT_TO_UTF8(keyval);
3087 NSString * key = [NSString stringWithUTF8String:(char*)keyval];
3089 CONVERT_TO_UTF8_FREE(keyval);
3091 [dict setObject:newObj forKey:key];
3095 } else { // only func refs should fall into this category?
3103 // This function is modeled after eval_client_expr_to_string found in main.c
3104 // Returns nil if there was an error evaluating the expression, and writes a
3105 // message to errorStr.
3106 // TODO Get the error that occurred while evaluating the expression in vim
3108 static id evalExprCocoa(NSString * expr, NSString ** errstr)
3111 char_u *s = (char_u*)[expr UTF8String];
3114 s = CONVERT_FROM_UTF8(s);
3117 int save_dbl = debug_break_level;
3118 int save_ro = redir_off;
3120 debug_break_level = -1;
3124 typval_T * tvres = eval_expr(s, NULL);
3126 debug_break_level = save_dbl;
3127 redir_off = save_ro;
3134 CONVERT_FROM_UTF8_FREE(s);
3139 gui_update_cursor(FALSE, FALSE);
3142 if (tvres == NULL) {
3144 *errstr = @"Expression evaluation failed.";
3147 id res = vimToCocoa(tvres, 1);
3152 *errstr = @"Conversion to cocoa values failed.";
3160 @implementation NSString (VimStrings)
3162 + (id)stringWithVimString:(char_u *)s
3164 // This method ensures a non-nil string is returned. If 's' cannot be
3165 // converted to a utf-8 string it is assumed to be latin-1. If conversion
3166 // still fails an empty NSString is returned.
3167 NSString *string = nil;
3170 s = CONVERT_TO_UTF8(s);
3172 string = [NSString stringWithUTF8String:(char*)s];
3174 // HACK! Apparently 's' is not a valid utf-8 string, maybe it is
3176 string = [NSString stringWithCString:(char*)s
3177 encoding:NSISOLatin1StringEncoding];
3180 CONVERT_TO_UTF8_FREE(s);
3184 return string != nil ? string : [NSString string];
3187 - (char_u *)vimStringSave
3189 char_u *s = (char_u*)[self UTF8String], *ret = NULL;
3192 s = CONVERT_FROM_UTF8(s);
3194 ret = vim_strsave(s);
3196 CONVERT_FROM_UTF8_FREE(s);
3202 @end // NSString (VimStrings)