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];
1941 ASLogWarn(@"Unknown message received (msgid=%d)", msgid);
1945 - (void)doKeyDown:(NSString *)key
1946 keyCode:(unsigned)code
1949 ASLogDebug(@"key='%@' code=%#x mods=%#x length=%d", key, code, mods,
1953 char_u *str = (char_u*)[key UTF8String];
1954 int i, len = [key lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
1956 if ([self handleSpecialKey:key keyCode:code modifiers:mods])
1960 char_u *conv_str = NULL;
1961 if (input_conv.vc_type != CONV_NONE) {
1962 conv_str = string_convert(&input_conv, str, &len);
1968 if (mods & MOD_MASK_CMD) {
1969 // NOTE: For normal input (non-special, 'macmeta' off) the modifier
1970 // flags are already included in the key event. However, the Cmd key
1971 // flag is special and must always be added manually.
1972 // The Shift flag is already included in the key when the Command
1973 // key is held. The same goes for Alt, unless Ctrl is held or
1974 // 'macmeta' is set. It is important that these flags are cleared
1975 // _after_ special keys have been handled, since they should never be
1976 // cleared for special keys.
1977 mods &= ~MOD_MASK_SHIFT;
1978 if (!(mods & MOD_MASK_CTRL)) {
1979 BOOL mmta = curbuf ? curbuf->b_p_mmta : YES;
1981 mods &= ~MOD_MASK_ALT;
1984 ASLogDebug(@"add mods=%#x", mods);
1985 char_u modChars[3] = { CSI, KS_MODIFIER, mods };
1986 add_to_input_buf(modChars, 3);
1987 } else if (mods & MOD_MASK_ALT && 1 == len && str[0] < 0x80
1988 && curbuf && curbuf->b_p_mmta) {
1989 // HACK! The 'macmeta' is set so we have to handle Alt key presses
1990 // separately. Normally Alt key presses are interpreted by the
1991 // frontend but now we have to manually set the 8th bit and deal with
1992 // UTF-8 conversion.
1993 if ([self handleMacMetaKey:str[0] modifiers:mods])
1998 for (i = 0; i < len; ++i) {
1999 ASLogDebug(@"add byte [%d/%d]: %#x", i, len, str[i]);
2000 add_to_input_buf(str+i, 1);
2001 if (CSI == str[i]) {
2002 // NOTE: If the converted string contains the byte CSI, then it
2003 // must be followed by the bytes KS_EXTRA, KE_CSI or things
2005 static char_u extra[2] = { KS_EXTRA, KE_CSI };
2006 ASLogDebug(@"add KS_EXTRA, KE_CSI");
2007 add_to_input_buf(extra, 2);
2017 - (BOOL)handleSpecialKey:(NSString *)key
2018 keyCode:(unsigned)code
2022 for (i = 0; special_keys[i].key_sym != 0; i++) {
2023 if (special_keys[i].key_sym == code) {
2024 ASLogDebug(@"Special key: %#x", code);
2028 if (special_keys[i].key_sym == 0)
2031 int ikey = special_keys[i].vim_code1 == NUL ? special_keys[i].vim_code0 :
2032 TO_SPECIAL(special_keys[i].vim_code0, special_keys[i].vim_code1);
2033 ikey = simplify_key(ikey, &mods);
2040 if (IS_SPECIAL(ikey)) {
2042 chars[1] = K_SECOND(ikey);
2043 chars[2] = K_THIRD(ikey);
2045 } else if (mods & MOD_MASK_ALT && special_keys[i].vim_code1 == 0
2047 && !enc_dbcs // TODO: ? (taken from gui_gtk_x11.c)
2050 ASLogDebug(@"Alt special=%d", ikey);
2052 // NOTE: The last entries in the special_keys struct when pressed
2053 // together with Alt need to be handled separately or they will not
2055 // The following code was gleaned from gui_gtk_x11.c.
2056 mods &= ~MOD_MASK_ALT;
2057 int mkey = 0x80 | ikey;
2059 if (enc_utf8) { // TODO: What about other encodings?
2061 chars[0] = (mkey >> 6) + 0xc0;
2062 chars[1] = mkey & 0xbf;
2063 if (chars[1] == CSI) {
2064 // We end up here when ikey == ESC
2065 chars[2] = KS_EXTRA;
2078 ASLogDebug(@"Just ikey=%d", ikey);
2085 ASLogDebug(@"Adding mods to special: %d", mods);
2086 char_u modChars[3] = { CSI, KS_MODIFIER, (char_u)mods };
2087 add_to_input_buf(modChars, 3);
2090 ASLogDebug(@"Adding special (%d): %x,%x,%x", len,
2091 chars[0], chars[1], chars[2]);
2092 add_to_input_buf(chars, len);
2098 - (BOOL)handleMacMetaKey:(int)ikey modifiers:(int)mods
2100 ASLogDebug(@"ikey=%d mods=%d", ikey, mods);
2102 // This code was taken from gui_w48.c and gui_gtk_x11.c.
2104 int ch = simplify_key(ikey, &mods);
2106 // Remove the SHIFT modifier for keys where it's already included,
2107 // e.g., '(' and '*'
2108 if (ch < 0x100 && !isalpha(ch) && isprint(ch))
2109 mods &= ~MOD_MASK_SHIFT;
2111 // Interpret the ALT key as making the key META, include SHIFT, etc.
2112 ch = extract_modifiers(ch, &mods);
2118 string[len++] = CSI;
2119 string[len++] = KS_MODIFIER;
2120 string[len++] = mods;
2125 // TODO: What if 'enc' is not "utf-8"?
2126 if (enc_utf8 && (ch & 0x80)) { // convert to utf-8
2127 string[len++] = ch & 0xbf;
2128 string[len-2] = ((unsigned)ch >> 6) + 0xc0;
2129 if (string[len-1] == CSI) {
2130 string[len++] = KS_EXTRA;
2131 string[len++] = (int)KE_CSI;
2136 add_to_input_buf(string, len);
2140 - (void)queueMessage:(int)msgid data:(NSData *)data
2142 [outputQueue addObject:[NSData dataWithBytes:&msgid length:sizeof(int)]];
2144 [outputQueue addObject:data];
2146 [outputQueue addObject:[NSData data]];
2149 - (void)connectionDidDie:(NSNotification *)notification
2151 // If the main connection to MacVim is lost this means that either MacVim
2152 // has crashed or this process did not receive its termination message
2153 // properly (e.g. if the TerminateNowMsgID was dropped).
2155 // NOTE: This is not called if a Vim controller invalidates its connection.
2157 ASLogNotice(@"Main connection was lost before process had a chance "
2158 "to terminate; preserving swap files.");
2159 getout_preserve_modified(1);
2162 - (void)blinkTimerFired:(NSTimer *)timer
2164 NSTimeInterval timeInterval = 0;
2166 [blinkTimer release];
2169 if (MMBlinkStateOn == blinkState) {
2170 gui_undraw_cursor();
2171 blinkState = MMBlinkStateOff;
2172 timeInterval = blinkOffInterval;
2173 } else if (MMBlinkStateOff == blinkState) {
2174 gui_update_cursor(TRUE, FALSE);
2175 blinkState = MMBlinkStateOn;
2176 timeInterval = blinkOnInterval;
2179 if (timeInterval > 0) {
2181 [[NSTimer scheduledTimerWithTimeInterval:timeInterval target:self
2182 selector:@selector(blinkTimerFired:)
2183 userInfo:nil repeats:NO] retain];
2184 [self flushQueue:YES];
2188 - (void)focusChange:(BOOL)on
2190 gui_focus_change(on);
2193 - (void)handleToggleToolbar
2195 // If 'go' contains 'T', then remove it, else add it.
2197 char_u go[sizeof(GO_ALL)+2];
2202 p = vim_strchr(go, GO_TOOLBAR);
2206 char_u *end = go + len;
2212 go[len] = GO_TOOLBAR;
2216 set_option_value((char_u*)"guioptions", 0, go, 0);
2218 [self redrawScreen];
2221 - (void)handleScrollbarEvent:(NSData *)data
2225 const void *bytes = [data bytes];
2226 int32_t ident = *((int32_t*)bytes); bytes += sizeof(int32_t);
2227 int hitPart = *((int*)bytes); bytes += sizeof(int);
2228 float fval = *((float*)bytes); bytes += sizeof(float);
2229 scrollbar_T *sb = gui_find_scrollbar(ident);
2232 scrollbar_T *sb_info = sb->wp ? &sb->wp->w_scrollbars[0] : sb;
2233 long value = sb_info->value;
2234 long size = sb_info->size;
2235 long max = sb_info->max;
2236 BOOL isStillDragging = NO;
2237 BOOL updateKnob = YES;
2240 case NSScrollerDecrementPage:
2241 value -= (size > 2 ? size - 2 : 1);
2243 case NSScrollerIncrementPage:
2244 value += (size > 2 ? size - 2 : 1);
2246 case NSScrollerDecrementLine:
2249 case NSScrollerIncrementLine:
2252 case NSScrollerKnob:
2253 isStillDragging = YES;
2255 case NSScrollerKnobSlot:
2256 value = (long)(fval * (max - size + 1));
2263 gui_drag_scrollbar(sb, value, isStillDragging);
2266 // Dragging the knob or option+clicking automatically updates
2267 // the knob position (on the actual NSScroller), so we only
2268 // need to set the knob position in the other cases.
2270 // Update both the left&right vertical scrollbars.
2271 int32_t idL = (int32_t)sb->wp->w_scrollbars[SBAR_LEFT].ident;
2272 int32_t idR = (int32_t)sb->wp->w_scrollbars[SBAR_RIGHT].ident;
2273 [self setScrollbarThumbValue:value size:size max:max
2275 [self setScrollbarThumbValue:value size:size max:max
2278 // Update the horizontal scrollbar.
2279 [self setScrollbarThumbValue:value size:size max:max
2286 - (void)handleSetFont:(NSData *)data
2290 const void *bytes = [data bytes];
2291 int pointSize = (int)*((float*)bytes); bytes += sizeof(float);
2293 unsigned len = *((unsigned*)bytes); bytes += sizeof(unsigned);
2294 NSMutableString *name = [NSMutableString stringWithUTF8String:bytes];
2297 [name appendString:[NSString stringWithFormat:@":h%d", pointSize]];
2298 char_u *s = (char_u*)[name UTF8String];
2300 unsigned wlen = *((unsigned*)bytes); bytes += sizeof(unsigned);
2303 NSMutableString *wname = [NSMutableString stringWithUTF8String:bytes];
2306 [wname appendString:[NSString stringWithFormat:@":h%d", pointSize]];
2307 ws = (char_u*)[wname UTF8String];
2311 s = CONVERT_FROM_UTF8(s);
2313 ws = CONVERT_FROM_UTF8(ws);
2317 set_option_value((char_u*)"guifont", 0, s, 0);
2319 if (ws && gui.wide_font != NOFONT) {
2320 // NOTE: This message is sent on Cmd-+/Cmd-- and as such should only
2321 // change the wide font if 'gfw' is non-empty (the frontend always has
2322 // some wide font set, even if 'gfw' is empty).
2323 set_option_value((char_u*)"guifontwide", 0, ws, 0);
2328 CONVERT_FROM_UTF8_FREE(ws);
2330 CONVERT_FROM_UTF8_FREE(s);
2333 [self redrawScreen];
2336 - (void)handleDropFiles:(NSData *)data
2338 // TODO: Get rid of this method; instead use Vim script directly. At the
2339 // moment I know how to do this to open files in tabs, but I'm not sure how
2340 // to add the filenames to the command line when in command line mode.
2344 NSMutableDictionary *args = [NSMutableDictionary dictionaryWithData:data];
2347 id obj = [args objectForKey:@"forceOpen"];
2348 BOOL forceOpen = YES;
2350 forceOpen = [obj boolValue];
2352 NSArray *filenames = [args objectForKey:@"filenames"];
2353 if (!(filenames && [filenames count] > 0)) return;
2356 if (!forceOpen && (State & CMDLINE)) {
2357 // HACK! If Vim is in command line mode then the files names
2358 // should be added to the command line, instead of opening the
2359 // files in tabs (unless forceOpen is set). This is taken care of by
2360 // gui_handle_drop().
2361 int n = [filenames count];
2362 char_u **fnames = (char_u **)alloc(n * sizeof(char_u *));
2365 for (i = 0; i < n; ++i)
2366 fnames[i] = [[filenames objectAtIndex:i] vimStringSave];
2368 // NOTE! This function will free 'fnames'.
2369 // HACK! It is assumed that the 'x' and 'y' arguments are
2370 // unused when in command line mode.
2371 gui_handle_drop(0, 0, 0, fnames, n);
2376 [self handleOpenWithArguments:args];
2380 - (void)handleDropString:(NSData *)data
2385 char_u dropkey[3] = { CSI, KS_EXTRA, (char_u)KE_DROP };
2386 const void *bytes = [data bytes];
2387 int len = *((int*)bytes); bytes += sizeof(int);
2388 NSMutableString *string = [NSMutableString stringWithUTF8String:bytes];
2390 // Replace unrecognized end-of-line sequences with \x0a (line feed).
2391 NSRange range = { 0, [string length] };
2392 unsigned n = [string replaceOccurrencesOfString:@"\x0d\x0a"
2393 withString:@"\x0a" options:0
2396 n = [string replaceOccurrencesOfString:@"\x0d" withString:@"\x0a"
2397 options:0 range:range];
2400 len = [string lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
2401 char_u *s = (char_u*)[string UTF8String];
2403 if (input_conv.vc_type != CONV_NONE)
2404 s = string_convert(&input_conv, s, &len);
2406 dnd_yank_drag_data(s, len);
2408 if (input_conv.vc_type != CONV_NONE)
2411 add_to_input_buf(dropkey, sizeof(dropkey));
2415 - (void)startOdbEditWithArguments:(NSDictionary *)args
2417 #ifdef FEAT_ODB_EDITOR
2418 id obj = [args objectForKey:@"remoteID"];
2421 OSType serverID = [obj unsignedIntValue];
2422 NSString *remotePath = [args objectForKey:@"remotePath"];
2424 NSAppleEventDescriptor *token = nil;
2425 NSData *tokenData = [args objectForKey:@"remoteTokenData"];
2426 obj = [args objectForKey:@"remoteTokenDescType"];
2427 if (tokenData && obj) {
2428 DescType tokenType = [obj unsignedLongValue];
2429 token = [NSAppleEventDescriptor descriptorWithDescriptorType:tokenType
2433 NSArray *filenames = [args objectForKey:@"filenames"];
2434 unsigned i, numFiles = [filenames count];
2435 for (i = 0; i < numFiles; ++i) {
2436 NSString *filename = [filenames objectAtIndex:i];
2437 char_u *s = [filename vimStringSave];
2438 buf_T *buf = buflist_findname(s);
2442 if (buf->b_odb_token) {
2443 [(NSAppleEventDescriptor*)(buf->b_odb_token) release];
2444 buf->b_odb_token = NULL;
2447 if (buf->b_odb_fname) {
2448 vim_free(buf->b_odb_fname);
2449 buf->b_odb_fname = NULL;
2452 buf->b_odb_server_id = serverID;
2455 buf->b_odb_token = [token retain];
2457 buf->b_odb_fname = [remotePath vimStringSave];
2459 ASLogWarn(@"Could not find buffer '%@' for ODB editing.", filename);
2462 #endif // FEAT_ODB_EDITOR
2465 - (void)handleXcodeMod:(NSData *)data
2468 const void *bytes = [data bytes];
2469 DescType type = *((DescType*)bytes); bytes += sizeof(DescType);
2470 unsigned len = *((unsigned*)bytes); bytes += sizeof(unsigned);
2474 NSAppleEventDescriptor *replyEvent = [NSAppleEventDescriptor
2475 descriptorWithDescriptorType:type
2481 - (void)handleOpenWithArguments:(NSDictionary *)args
2483 // ARGUMENT: DESCRIPTION:
2484 // -------------------------------------------------------------
2485 // filenames list of filenames
2486 // dontOpen don't open files specified in above argument
2487 // layout which layout to use to open files
2488 // selectionRange range of lines to select
2489 // searchText string to search for
2490 // cursorLine line to position the cursor on
2491 // cursorColumn column to position the cursor on
2492 // (only valid when "cursorLine" is set)
2493 // remoteID ODB parameter
2494 // remotePath ODB parameter
2495 // remoteTokenDescType ODB parameter
2496 // remoteTokenData ODB parameter
2498 ASLogDebug(@"args=%@ (starting=%d)", args, starting);
2500 NSArray *filenames = [args objectForKey:@"filenames"];
2501 int i, numFiles = filenames ? [filenames count] : 0;
2502 BOOL openFiles = ![[args objectForKey:@"dontOpen"] boolValue];
2503 int layout = [[args objectForKey:@"layout"] intValue];
2505 // Change to directory of first file to open if this is an "unused" editor
2506 // (but do not do this if editing remotely).
2507 if (openFiles && numFiles > 0 && ![args objectForKey:@"remoteID"]
2508 && (starting || [self unusedEditor]) ) {
2509 char_u *s = [[filenames objectAtIndex:0] vimStringSave];
2511 mch_chdir((char*)s);
2519 // When Vim is starting we simply add the files to be opened to the
2520 // global arglist and Vim will take care of opening them for us.
2521 if (openFiles && numFiles > 0) {
2522 for (i = 0; i < numFiles; i++) {
2523 NSString *fname = [filenames objectAtIndex:i];
2526 if (ga_grow(&global_alist.al_ga, 1) == FAIL
2527 || (p = [fname vimStringSave]) == NULL)
2528 exit(2); // See comment in -[MMBackend exit]
2530 alist_add(&global_alist, p, 2);
2533 // Vim will take care of arranging the files added to the arglist
2534 // in windows or tabs; all we must do is to specify which layout to
2536 initialWindowLayout = layout;
2539 // When Vim is already open we resort to some trickery to open the
2540 // files with the specified layout.
2542 // TODO: Figure out a better way to handle this?
2543 if (openFiles && numFiles > 0) {
2544 BOOL oneWindowInTab = topframe ? YES
2545 : (topframe->fr_layout == FR_LEAF);
2546 BOOL bufChanged = NO;
2547 BOOL bufHasFilename = NO;
2549 bufChanged = curbufIsChanged();
2550 bufHasFilename = curbuf->b_ffname != NULL;
2553 // Temporarily disable flushing since the following code may
2554 // potentially cause multiple redraws.
2555 flushDisabled = YES;
2557 BOOL onlyOneTab = (first_tabpage->tp_next == NULL);
2558 if (WIN_TABS == layout && !onlyOneTab) {
2559 // By going to the last tabpage we ensure that the new tabs
2560 // will appear last (if this call is left out, the taborder
2565 // Make sure we're in normal mode first.
2566 [self addInput:@"<C-\\><C-N>"];
2569 // With "split layout" we open a new tab before opening
2570 // multiple files if the current tab has more than one window
2571 // or if there is exactly one window but whose buffer has a
2572 // filename. (The :drop command ensures modified buffers get
2573 // their own window.)
2574 if ((WIN_HOR == layout || WIN_VER == layout) &&
2575 (!oneWindowInTab || bufHasFilename))
2576 [self addInput:@":tabnew<CR>"];
2578 // The files are opened by constructing a ":drop ..." command
2579 // and executing it.
2580 NSMutableString *cmd = (WIN_TABS == layout)
2581 ? [NSMutableString stringWithString:@":tab drop"]
2582 : [NSMutableString stringWithString:@":drop"];
2584 for (i = 0; i < numFiles; ++i) {
2585 NSString *file = [filenames objectAtIndex:i];
2586 file = [file stringByEscapingSpecialFilenameCharacters];
2587 [cmd appendString:@" "];
2588 [cmd appendString:file];
2591 // Temporarily clear 'suffixes' so that the files are opened in
2592 // the same order as they appear in the "filenames" array.
2593 [self addInput:@":let mvim_oldsu=&su|set su=<CR>"];
2595 [self addInput:cmd];
2597 // Split the view into multiple windows if requested.
2598 if (WIN_HOR == layout)
2599 [self addInput:@"|sall"];
2600 else if (WIN_VER == layout)
2601 [self addInput:@"|vert sall"];
2603 // Restore the old value of 'suffixes'.
2604 [self addInput:@"|let &su=mvim_oldsu|unlet mvim_oldsu<CR>"];
2606 // When opening one file we try to reuse the current window,
2607 // but not if its buffer is modified or has a filename.
2608 // However, the 'arglist' layout always opens the file in the
2610 NSString *file = [[filenames lastObject]
2611 stringByEscapingSpecialFilenameCharacters];
2613 if (WIN_HOR == layout) {
2614 if (!(bufHasFilename || bufChanged))
2615 cmd = [NSString stringWithFormat:@":e %@", file];
2617 cmd = [NSString stringWithFormat:@":sp %@", file];
2618 } else if (WIN_VER == layout) {
2619 if (!(bufHasFilename || bufChanged))
2620 cmd = [NSString stringWithFormat:@":e %@", file];
2622 cmd = [NSString stringWithFormat:@":vsp %@", file];
2623 } else if (WIN_TABS == layout) {
2624 if (oneWindowInTab && !(bufHasFilename || bufChanged))
2625 cmd = [NSString stringWithFormat:@":e %@", file];
2627 cmd = [NSString stringWithFormat:@":tabe %@", file];
2629 // (The :drop command will split if there is a modified
2631 cmd = [NSString stringWithFormat:@":drop %@", file];
2634 [self addInput:cmd];
2635 [self addInput:@"<CR>"];
2638 // Force screen redraw (does it have to be this complicated?).
2639 // (This code was taken from the end of gui_handle_drop().)
2640 update_screen(NOT_VALID);
2643 gui_update_cursor(FALSE, FALSE);
2650 if ([args objectForKey:@"remoteID"]) {
2651 // NOTE: We have to delay processing any ODB related arguments since
2652 // the file(s) may not be opened until the input buffer is processed.
2653 [self performSelector:@selector(startOdbEditWithArguments:)
2658 NSString *lineString = [args objectForKey:@"cursorLine"];
2659 if (lineString && [lineString intValue] > 0) {
2660 NSString *columnString = [args objectForKey:@"cursorColumn"];
2661 if (!(columnString && [columnString intValue] > 0))
2662 columnString = @"1";
2664 NSString *cmd = [NSString stringWithFormat:@"<C-\\><C-N>:cal "
2665 "cursor(%@,%@)|norm! zz<CR>:f<CR>", lineString, columnString];
2666 [self addInput:cmd];
2669 NSString *rangeString = [args objectForKey:@"selectionRange"];
2671 // Build a command line string that will select the given range of
2672 // lines. If range.length == 0, then position the cursor on the given
2673 // line but do not select.
2674 NSRange range = NSRangeFromString(rangeString);
2676 if (range.length > 0) {
2677 cmd = [NSString stringWithFormat:@"<C-\\><C-N>%dGV%dGz.0",
2678 NSMaxRange(range), range.location];
2680 cmd = [NSString stringWithFormat:@"<C-\\><C-N>%dGz.0",
2684 [self addInput:cmd];
2687 NSString *searchText = [args objectForKey:@"searchText"];
2689 [self addInput:[NSString stringWithFormat:@"<C-\\><C-N>gg/\\c%@/e<CR>",
2694 - (BOOL)checkForModifiedBuffers
2697 for (buf = firstbuf; buf != NULL; buf = buf->b_next) {
2698 if (bufIsChanged(buf)) {
2706 - (void)addInput:(NSString *)input
2708 // NOTE: This code is essentially identical to server_to_input_buf(),
2709 // except the 'silent' flag is TRUE in the call to ins_typebuf() below.
2710 char_u *string = [input vimStringSave];
2711 if (!string) return;
2713 /* Set 'cpoptions' the way we want it.
2714 * B set - backslashes are *not* treated specially
2715 * k set - keycodes are *not* reverse-engineered
2716 * < unset - <Key> sequences *are* interpreted
2717 * The last but one parameter of replace_termcodes() is TRUE so that the
2718 * <lt> sequence is recognised - needed for a real backslash.
2721 char_u *cpo_save = p_cpo;
2722 p_cpo = (char_u *)"Bk";
2723 char_u *str = replace_termcodes((char_u *)string, &ptr, FALSE, TRUE, FALSE);
2726 if (*ptr != NUL) /* trailing CTRL-V results in nothing */
2729 * Add the string to the input stream.
2730 * Can't use add_to_input_buf() here, we now have K_SPECIAL bytes.
2732 * First clear typed characters from the typeahead buffer, there could
2733 * be half a mapping there. Then append to the existing string, so
2734 * that multiple commands from a client are concatenated.
2736 if (typebuf.tb_maplen < typebuf.tb_len)
2737 del_typebuf(typebuf.tb_len - typebuf.tb_maplen, typebuf.tb_maplen);
2738 (void)ins_typebuf(str, REMAP_NONE, typebuf.tb_len, TRUE, TRUE);
2740 /* Let input_available() know we inserted text in the typeahead
2742 typebuf_was_filled = TRUE;
2748 - (BOOL)unusedEditor
2750 BOOL oneWindowInTab = topframe ? YES
2751 : (topframe->fr_layout == FR_LEAF);
2752 BOOL bufChanged = NO;
2753 BOOL bufHasFilename = NO;
2755 bufChanged = curbufIsChanged();
2756 bufHasFilename = curbuf->b_ffname != NULL;
2759 BOOL onlyOneTab = (first_tabpage->tp_next == NULL);
2761 return onlyOneTab && oneWindowInTab && !bufChanged && !bufHasFilename;
2764 - (void)redrawScreen
2766 // Force screen redraw (does it have to be this complicated?).
2767 redraw_all_later(CLEAR);
2768 update_screen(NOT_VALID);
2771 gui_update_cursor(FALSE, FALSE);
2773 // HACK! The cursor is not put back at the command line by the above
2774 // "redraw commands". The following test seems to do the trick though.
2775 if (State & CMDLINE)
2779 - (void)handleFindReplace:(NSDictionary *)args
2783 NSString *findString = [args objectForKey:@"find"];
2784 if (!findString) return;
2786 char_u *find = [findString vimStringSave];
2787 char_u *replace = [[args objectForKey:@"replace"] vimStringSave];
2788 int flags = [[args objectForKey:@"flags"] intValue];
2790 // NOTE: The flag 0x100 is used to indicate a backward search.
2791 gui_do_findrepl(flags, find, replace, (flags & 0x100) == 0);
2798 - (void)handleMarkedText:(NSData *)data
2800 const void *bytes = [data bytes];
2801 unsigned pos = *((unsigned*)bytes); bytes += sizeof(unsigned);
2802 unsigned len = *((unsigned*)bytes); bytes += sizeof(unsigned);
2803 char *chars = (char *)bytes;
2805 ASLogDebug(@"pos=%d len=%d chars=%s", pos, len, chars);
2808 im_preedit_end_macvim();
2810 if (!preedit_get_status())
2811 im_preedit_start_macvim();
2813 im_preedit_changed_macvim(chars, pos);
2817 @end // MMBackend (Private)
2822 @implementation MMBackend (ClientServer)
2824 - (NSString *)connectionNameFromServerName:(NSString *)name
2826 NSString *bundlePath = [[NSBundle mainBundle] bundlePath];
2828 return [[NSString stringWithFormat:@"%@.%@", bundlePath, name]
2832 - (NSConnection *)connectionForServerName:(NSString *)name
2834 // TODO: Try 'name%d' if 'name' fails.
2835 NSString *connName = [self connectionNameFromServerName:name];
2836 NSConnection *svrConn = [connectionNameDict objectForKey:connName];
2839 svrConn = [NSConnection connectionWithRegisteredName:connName
2841 // Try alternate server...
2842 if (!svrConn && alternateServerName) {
2843 ASLogInfo(@" trying to connect to alternate server: %@",
2844 alternateServerName);
2845 connName = [self connectionNameFromServerName:alternateServerName];
2846 svrConn = [NSConnection connectionWithRegisteredName:connName
2850 // Try looking for alternate servers...
2852 ASLogInfo(@" looking for alternate servers...");
2853 NSString *alt = [self alternateServerNameForName:name];
2854 if (alt != alternateServerName) {
2855 ASLogInfo(@" found alternate server: %@", alt);
2856 [alternateServerName release];
2857 alternateServerName = [alt copy];
2861 // Try alternate server again...
2862 if (!svrConn && alternateServerName) {
2863 ASLogInfo(@" trying to connect to alternate server: %@",
2864 alternateServerName);
2865 connName = [self connectionNameFromServerName:alternateServerName];
2866 svrConn = [NSConnection connectionWithRegisteredName:connName
2871 [connectionNameDict setObject:svrConn forKey:connName];
2873 ASLogDebug(@"Adding %@ as connection observer for %@",
2875 [[NSNotificationCenter defaultCenter] addObserver:self
2876 selector:@selector(serverConnectionDidDie:)
2877 name:NSConnectionDidDieNotification object:svrConn];
2884 - (NSConnection *)connectionForServerPort:(int)port
2887 NSEnumerator *e = [connectionNameDict objectEnumerator];
2889 while ((conn = [e nextObject])) {
2890 // HACK! Assume connection uses mach ports.
2891 if (port == [(NSMachPort*)[conn sendPort] machPort])
2898 - (void)serverConnectionDidDie:(NSNotification *)notification
2900 ASLogDebug(@"notification=%@", notification);
2902 NSConnection *svrConn = [notification object];
2904 ASLogDebug(@"Removing %@ as connection observer from %@", self, svrConn);
2905 [[NSNotificationCenter defaultCenter]
2907 name:NSConnectionDidDieNotification
2910 [connectionNameDict removeObjectsForKeys:
2911 [connectionNameDict allKeysForObject:svrConn]];
2913 // HACK! Assume connection uses mach ports.
2914 int port = [(NSMachPort*)[svrConn sendPort] machPort];
2915 NSNumber *key = [NSNumber numberWithInt:port];
2917 [clientProxyDict removeObjectForKey:key];
2918 [serverReplyDict removeObjectForKey:key];
2921 - (void)addClient:(NSDistantObject *)client
2923 NSConnection *conn = [client connectionForProxy];
2924 // HACK! Assume connection uses mach ports.
2925 int port = [(NSMachPort*)[conn sendPort] machPort];
2926 NSNumber *key = [NSNumber numberWithInt:port];
2928 if (![clientProxyDict objectForKey:key]) {
2929 [client setProtocolForProxy:@protocol(MMVimClientProtocol)];
2930 [clientProxyDict setObject:client forKey:key];
2933 // NOTE: 'clientWindow' is a global variable which is used by <client>
2934 clientWindow = port;
2937 - (NSString *)alternateServerNameForName:(NSString *)name
2939 if (!(name && [name length] > 0))
2942 // Only look for alternates if 'name' doesn't end in a digit.
2943 unichar lastChar = [name characterAtIndex:[name length]-1];
2944 if (lastChar >= '0' && lastChar <= '9')
2947 // Look for alternates among all current servers.
2948 NSArray *list = [self serverList];
2949 if (!(list && [list count] > 0))
2952 // Filter out servers starting with 'name' and ending with a number. The
2953 // (?i) pattern ensures that the match is case insensitive.
2954 NSString *pat = [NSString stringWithFormat:@"(?i)%@[0-9]+\\z", name];
2955 NSPredicate *pred = [NSPredicate predicateWithFormat:
2956 @"SELF MATCHES %@", pat];
2957 list = [list filteredArrayUsingPredicate:pred];
2958 if ([list count] > 0) {
2959 list = [list sortedArrayUsingSelector:@selector(serverNameCompare:)];
2960 return [list objectAtIndex:0];
2966 @end // MMBackend (ClientServer)
2971 @implementation NSString (MMServerNameCompare)
2972 - (NSComparisonResult)serverNameCompare:(NSString *)string
2974 return [self compare:string
2975 options:NSCaseInsensitiveSearch|NSNumericSearch];
2982 static int eventModifierFlagsToVimModMask(int modifierFlags)
2986 if (modifierFlags & NSShiftKeyMask)
2987 modMask |= MOD_MASK_SHIFT;
2988 if (modifierFlags & NSControlKeyMask)
2989 modMask |= MOD_MASK_CTRL;
2990 if (modifierFlags & NSAlternateKeyMask)
2991 modMask |= MOD_MASK_ALT;
2992 if (modifierFlags & NSCommandKeyMask)
2993 modMask |= MOD_MASK_CMD;
2998 static int eventModifierFlagsToVimMouseModMask(int modifierFlags)
3002 if (modifierFlags & NSShiftKeyMask)
3003 modMask |= MOUSE_SHIFT;
3004 if (modifierFlags & NSControlKeyMask)
3005 modMask |= MOUSE_CTRL;
3006 if (modifierFlags & NSAlternateKeyMask)
3007 modMask |= MOUSE_ALT;
3012 static int eventButtonNumberToVimMouseButton(int buttonNumber)
3014 static int mouseButton[] = { MOUSE_LEFT, MOUSE_RIGHT, MOUSE_MIDDLE };
3016 return (buttonNumber >= 0 && buttonNumber < 3)
3017 ? mouseButton[buttonNumber] : -1;
3022 // This function is modeled after the VimToPython function found in if_python.c
3023 // NB This does a deep copy by value, it does not lookup references like the
3024 // VimToPython function does. This is because I didn't want to deal with the
3025 // retain cycles that this would create, and we can cover 99% of the use cases
3026 // by ignoring it. If we ever switch to using GC in MacVim then this
3027 // functionality can be implemented easily.
3028 static id vimToCocoa(typval_T * tv, int depth)
3034 // Avoid infinite recursion
3039 if (tv->v_type == VAR_STRING) {
3040 char_u * val = tv->vval.v_string;
3041 // val can be NULL if the string is empty
3043 result = [NSString string];
3046 val = CONVERT_TO_UTF8(val);
3048 result = [NSString stringWithUTF8String:(char*)val];
3050 CONVERT_TO_UTF8_FREE(val);
3053 } else if (tv->v_type == VAR_NUMBER) {
3054 // looks like sizeof(varnumber_T) is always <= sizeof(long)
3055 result = [NSNumber numberWithLong:(long)tv->vval.v_number];
3056 } else if (tv->v_type == VAR_LIST) {
3057 list_T * list = tv->vval.v_list;
3060 NSMutableArray * arr = result = [NSMutableArray array];
3063 for (curr = list->lv_first; curr != NULL; curr = curr->li_next) {
3064 newObj = vimToCocoa(&curr->li_tv, depth + 1);
3065 [arr addObject:newObj];
3068 } else if (tv->v_type == VAR_DICT) {
3069 NSMutableDictionary * dict = result = [NSMutableDictionary dictionary];
3071 if (tv->vval.v_dict != NULL) {
3072 hashtab_T * ht = &tv->vval.v_dict->dv_hashtab;
3073 int todo = ht->ht_used;
3077 for (hi = ht->ht_array; todo > 0; ++hi) {
3078 if (!HASHITEM_EMPTY(hi)) {
3081 di = dict_lookup(hi);
3082 newObj = vimToCocoa(&di->di_tv, depth + 1);
3084 char_u * keyval = hi->hi_key;
3086 keyval = CONVERT_TO_UTF8(keyval);
3088 NSString * key = [NSString stringWithUTF8String:(char*)keyval];
3090 CONVERT_TO_UTF8_FREE(keyval);
3092 [dict setObject:newObj forKey:key];
3096 } else { // only func refs should fall into this category?
3104 // This function is modeled after eval_client_expr_to_string found in main.c
3105 // Returns nil if there was an error evaluating the expression, and writes a
3106 // message to errorStr.
3107 // TODO Get the error that occurred while evaluating the expression in vim
3109 static id evalExprCocoa(NSString * expr, NSString ** errstr)
3112 char_u *s = (char_u*)[expr UTF8String];
3115 s = CONVERT_FROM_UTF8(s);
3118 int save_dbl = debug_break_level;
3119 int save_ro = redir_off;
3121 debug_break_level = -1;
3125 typval_T * tvres = eval_expr(s, NULL);
3127 debug_break_level = save_dbl;
3128 redir_off = save_ro;
3135 CONVERT_FROM_UTF8_FREE(s);
3140 gui_update_cursor(FALSE, FALSE);
3143 if (tvres == NULL) {
3145 *errstr = @"Expression evaluation failed.";
3148 id res = vimToCocoa(tvres, 1);
3153 *errstr = @"Conversion to cocoa values failed.";
3161 @implementation NSString (VimStrings)
3163 + (id)stringWithVimString:(char_u *)s
3165 // This method ensures a non-nil string is returned. If 's' cannot be
3166 // converted to a utf-8 string it is assumed to be latin-1. If conversion
3167 // still fails an empty NSString is returned.
3168 NSString *string = nil;
3171 s = CONVERT_TO_UTF8(s);
3173 string = [NSString stringWithUTF8String:(char*)s];
3175 // HACK! Apparently 's' is not a valid utf-8 string, maybe it is
3177 string = [NSString stringWithCString:(char*)s
3178 encoding:NSISOLatin1StringEncoding];
3181 CONVERT_TO_UTF8_FREE(s);
3185 return string != nil ? string : [NSString string];
3188 - (char_u *)vimStringSave
3190 char_u *s = (char_u*)[self UTF8String], *ret = NULL;
3193 s = CONVERT_FROM_UTF8(s);
3195 ret = vim_strsave(s);
3197 CONVERT_FROM_UTF8_FREE(s);
3203 @end // NSString (VimStrings)