/
MMAppController.m
1428 lines (1182 loc) · 49.8 KB
/
MMAppController.m
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
/* vi:set ts=8 sts=4 sw=4 ft=objc:
*
* VIM - Vi IMproved by Bram Moolenaar
* MacVim GUI port by Bjorn Winckler
*
* Do ":help uganda" in Vim to read copying and usage conditions.
* Do ":help credits" in Vim to see a list of people who contributed.
* See README.txt for an overview of the Vim source code.
*/
/*
* MMAppController
*
* MMAppController is the delegate of NSApp and as such handles file open
* requests, application termination, etc. It sets up a named NSConnection on
* which it listens to incoming connections from Vim processes. It also
* coordinates all MMVimControllers and takes care of the main menu.
*
* A new Vim process is started by calling launchVimProcessWithArguments:.
* When the Vim process is initialized it notifies the app controller by
* sending a connectBackend:pid: message. At this point a new MMVimController
* is allocated. Afterwards, the Vim process communicates directly with its
* MMVimController.
*
* A Vim process started from the command line connects directly by sending the
* connectBackend:pid: message (launchVimProcessWithArguments: is never called
* in this case).
*
* The main menu is handled as follows. Each Vim controller keeps its own main
* menu. All menus except the "MacVim" menu are controlled by the Vim process.
* The app controller also keeps a reference to the "default main menu" which
* is set up in MainMenu.nib. When no editor window is open the default main
* menu is used. When a new editor window becomes main its main menu becomes
* the new main menu, this is done in -[MMAppController setMainMenu:].
* NOTE: Certain heuristics are used to find the "MacVim", "Windows", "File",
* and "Services" menu. If MainMenu.nib changes these heuristics may have to
* change as well. For specifics see the find... methods defined in the NSMenu
* category "MMExtras".
*/
#import "MMAppController.h"
#import "MMVimController.h"
#import "MMWindowController.h"
#import "MMPreferenceController.h"
#import <unistd.h>
#define MM_HANDLE_XCODE_MOD_EVENT 0
// Default timeout intervals on all connections.
static NSTimeInterval MMRequestTimeout = 5;
static NSTimeInterval MMReplyTimeout = 5;
static NSString *MMWebsiteString = @"http://code.google.com/p/macvim/";
#pragma options align=mac68k
typedef struct
{
short unused1; // 0 (not used)
short lineNum; // line to select (< 0 to specify range)
long startRange; // start of selection range (if line < 0)
long endRange; // end of selection range (if line < 0)
long unused2; // 0 (not used)
long theDate; // modification date/time
} MMSelectionRange;
#pragma options align=reset
static int executeInLoginShell(NSString *path, NSArray *args);
@interface MMAppController (MMServices)
- (void)openSelection:(NSPasteboard *)pboard userData:(NSString *)userData
error:(NSString **)error;
- (void)openFile:(NSPasteboard *)pboard userData:(NSString *)userData
error:(NSString **)error;
@end
@interface MMAppController (Private)
- (MMVimController *)keyVimController;
- (MMVimController *)topmostVimController;
- (int)launchVimProcessWithArguments:(NSArray *)args;
- (NSArray *)filterFilesAndNotify:(NSArray *)files;
- (NSArray *)filterOpenFiles:(NSArray *)filenames
arguments:(NSDictionary *)args;
#if MM_HANDLE_XCODE_MOD_EVENT
- (void)handleXcodeModEvent:(NSAppleEventDescriptor *)event
replyEvent:(NSAppleEventDescriptor *)reply;
#endif
- (int)findLaunchingProcessWithoutArguments;
- (MMVimController *)findUntitledWindow;
- (NSMutableDictionary *)extractArgumentsFromOdocEvent:
(NSAppleEventDescriptor *)desc;
- (void)passArguments:(NSDictionary *)args toVimController:(MMVimController*)vc;
@end
@interface NSNumber (MMExtras)
- (int)tag;
@end
@interface NSMenu (MMExtras)
- (int)indexOfItemWithAction:(SEL)action;
- (NSMenuItem *)itemWithAction:(SEL)action;
- (NSMenu *)findMenuContainingItemWithAction:(SEL)action;
- (NSMenu *)findWindowsMenu;
- (NSMenu *)findApplicationMenu;
- (NSMenu *)findServicesMenu;
- (NSMenu *)findFileMenu;
@end
@implementation MMAppController
+ (void)initialize
{
NSDictionary *dict = [NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithBool:NO], MMNoWindowKey,
[NSNumber numberWithInt:64], MMTabMinWidthKey,
[NSNumber numberWithInt:6*64], MMTabMaxWidthKey,
[NSNumber numberWithInt:132], MMTabOptimumWidthKey,
[NSNumber numberWithInt:2], MMTextInsetLeftKey,
[NSNumber numberWithInt:1], MMTextInsetRightKey,
[NSNumber numberWithInt:1], MMTextInsetTopKey,
[NSNumber numberWithInt:1], MMTextInsetBottomKey,
[NSNumber numberWithBool:NO], MMTerminateAfterLastWindowClosedKey,
@"MMTypesetter", MMTypesetterKey,
[NSNumber numberWithFloat:1], MMCellWidthMultiplierKey,
[NSNumber numberWithFloat:-1], MMBaselineOffsetKey,
[NSNumber numberWithBool:YES], MMTranslateCtrlClickKey,
[NSNumber numberWithBool:NO], MMOpenFilesInTabsKey,
[NSNumber numberWithBool:NO], MMNoFontSubstitutionKey,
[NSNumber numberWithBool:NO], MMLoginShellKey,
[NSNumber numberWithBool:NO], MMAtsuiRendererKey,
[NSNumber numberWithInt:MMUntitledWindowAlways],
MMUntitledWindowKey,
[NSNumber numberWithBool:NO], MMTexturedWindowKey,
[NSNumber numberWithBool:NO], MMZoomBothKey,
@"", MMLoginShellCommandKey,
@"", MMLoginShellArgumentKey,
[NSNumber numberWithBool:YES], MMDialogsTrackPwdKey,
nil];
[[NSUserDefaults standardUserDefaults] registerDefaults:dict];
NSArray *types = [NSArray arrayWithObject:NSStringPboardType];
[NSApp registerServicesMenuSendTypes:types returnTypes:types];
}
- (id)init
{
if ((self = [super init])) {
fontContainerRef = loadFonts();
vimControllers = [NSMutableArray new];
pidArguments = [NSMutableDictionary new];
// NOTE: Do not use the default connection since the Logitech Control
// Center (LCC) input manager steals and this would cause MacVim to
// never open any windows. (This is a bug in LCC but since they are
// unlikely to fix it, we graciously give them the default connection.)
connection = [[NSConnection alloc] initWithReceivePort:[NSPort port]
sendPort:nil];
[connection setRootObject:self];
[connection setRequestTimeout:MMRequestTimeout];
[connection setReplyTimeout:MMReplyTimeout];
// NOTE: When the user is resizing the window the AppKit puts the run
// loop in event tracking mode. Unless the connection listens to
// request in this mode, live resizing won't work.
[connection addRequestMode:NSEventTrackingRunLoopMode];
// NOTE! If the name of the connection changes here it must also be
// updated in MMBackend.m.
NSString *name = [NSString stringWithFormat:@"%@-connection",
[[NSBundle mainBundle] bundleIdentifier]];
//NSLog(@"Registering connection with name '%@'", name);
if (![connection registerName:name]) {
NSLog(@"FATAL ERROR: Failed to register connection with name '%@'",
name);
[connection release]; connection = nil;
}
}
return self;
}
- (void)dealloc
{
//NSLog(@"MMAppController dealloc");
[connection release]; connection = nil;
[pidArguments release]; pidArguments = nil;
[vimControllers release]; vimControllers = nil;
[openSelectionString release]; openSelectionString = nil;
[recentFilesMenuItem release]; recentFilesMenuItem = nil;
[defaultMainMenu release]; defaultMainMenu = nil;
[appMenuItemTemplate release]; appMenuItemTemplate = nil;
[super dealloc];
}
- (void)applicationWillFinishLaunching:(NSNotification *)notification
{
// Remember the default menu so that it can be restored if the user closes
// all editor windows.
defaultMainMenu = [[NSApp mainMenu] retain];
// Store a copy of the default app menu so we can use this as a template
// for all other menus. We make a copy here because the "Services" menu
// will not yet have been populated at this time. If we don't we get
// problems trying to set key equivalents later on because they might clash
// with items on the "Services" menu.
appMenuItemTemplate = [defaultMainMenu itemAtIndex:0];
appMenuItemTemplate = [appMenuItemTemplate copy];
// Set up the "Open Recent" menu. See
// http://lapcatsoftware.com/blog/2007/07/10/
// working-without-a-nib-part-5-open-recent-menu/
// and
// http://www.cocoabuilder.com/archive/message/cocoa/2007/8/15/187793
// for more information.
//
// The menu itself is created in MainMenu.nib but we still seem to have to
// hack around a bit to get it to work. (This has to be done in
// applicationWillFinishLaunching at the latest, otherwise it doesn't
// work.)
NSMenu *fileMenu = [defaultMainMenu findFileMenu];
if (fileMenu) {
int idx = [fileMenu indexOfItemWithAction:@selector(fileOpen:)];
if (idx >= 0 && idx+1 < [fileMenu numberOfItems])
recentFilesMenuItem = [fileMenu itemWithTitle:@"Open Recent"];
[[recentFilesMenuItem submenu] performSelector:@selector(_setMenuName:)
withObject:@"NSRecentDocumentsMenu"];
// Note: The "Recent Files" menu must be moved around since there is no
// -[NSApp setRecentFilesMenu:] method. We keep a reference to it to
// facilitate this move (see setMainMenu: below).
[recentFilesMenuItem retain];
}
#if MM_HANDLE_XCODE_MOD_EVENT
[[NSAppleEventManager sharedAppleEventManager]
setEventHandler:self
andSelector:@selector(handleXcodeModEvent:replyEvent:)
forEventClass:'KAHL'
andEventID:'MOD '];
#endif
}
- (void)applicationDidFinishLaunching:(NSNotification *)notification
{
[NSApp setServicesProvider:self];
}
- (BOOL)applicationShouldOpenUntitledFile:(NSApplication *)sender
{
NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
NSAppleEventManager *aem = [NSAppleEventManager sharedAppleEventManager];
NSAppleEventDescriptor *desc = [aem currentAppleEvent];
// The user default MMUntitledWindow can be set to control whether an
// untitled window should open on 'Open' and 'Reopen' events.
int untitledWindowFlag = [ud integerForKey:MMUntitledWindowKey];
if ([desc eventID] == kAEOpenApplication
&& (untitledWindowFlag & MMUntitledWindowOnOpen) == 0)
return NO;
else if ([desc eventID] == kAEReopenApplication
&& (untitledWindowFlag & MMUntitledWindowOnReopen) == 0)
return NO;
// When a process is started from the command line, the 'Open' event will
// contain a parameter to surpress the opening of an untitled window.
desc = [desc paramDescriptorForKeyword:keyAEPropData];
desc = [desc paramDescriptorForKeyword:keyMMUntitledWindow];
if (desc && ![desc booleanValue])
return NO;
// Never open an untitled window if there is at least one open window or if
// there are processes that are currently launching.
if ([vimControllers count] > 0 || [pidArguments count] > 0)
return NO;
// NOTE! This way it possible to start the app with the command-line
// argument '-nowindow yes' and no window will be opened by default.
return ![ud boolForKey:MMNoWindowKey];
}
- (BOOL)applicationOpenUntitledFile:(NSApplication *)sender
{
[self newWindow:self];
return YES;
}
- (void)application:(NSApplication *)sender openFiles:(NSArray *)filenames
{
// Opening files works like this:
// a) extract ODB/Xcode/Spotlight parameters from the current Apple event
// b) filter out any already open files (see filterOpenFiles::)
// c) open any remaining files
//
// A file is opened in an untitled window if there is one (it may be
// currently launching, or it may already be visible), otherwise a new
// window is opened.
//
// Each launching Vim process has a dictionary of arguments that are passed
// to the process when in checks in (via connectBackend:pid:). The
// arguments for each launching process can be looked up by its PID (in the
// pidArguments dictionary).
NSMutableDictionary *arguments = [self extractArgumentsFromOdocEvent:
[[NSAppleEventManager sharedAppleEventManager] currentAppleEvent]];
// Filter out files that are already open
filenames = [self filterOpenFiles:filenames arguments:arguments];
// Open any files that remain
if ([filenames count]) {
MMVimController *vc;
BOOL openInTabs = [[NSUserDefaults standardUserDefaults]
boolForKey:MMOpenFilesInTabsKey];
[arguments setObject:filenames forKey:@"filenames"];
[arguments setObject:[NSNumber numberWithBool:YES] forKey:@"openFiles"];
// Add file names to "Recent Files" menu.
int i, count = [filenames count];
for (i = 0; i < count; ++i) {
// Don't add files that are being edited remotely (using ODB).
if ([arguments objectForKey:@"remoteID"]) continue;
[[NSDocumentController sharedDocumentController]
noteNewRecentFilePath:[filenames objectAtIndex:i]];
}
if ((openInTabs && (vc = [self topmostVimController]))
|| (vc = [self findUntitledWindow])) {
// Open files in an already open window.
[[[vc windowController] window] makeKeyAndOrderFront:self];
[self passArguments:arguments toVimController:vc];
} else {
// Open files in a launching Vim process or start a new process.
int pid = [self findLaunchingProcessWithoutArguments];
if (!pid) {
// Pass the filenames to the process straight away.
//
// TODO: It would be nicer if all arguments were passed to the
// Vim process in connectBackend::, but if we don't pass the
// filename arguments here, the window 'flashes' once when it
// opens. This is due to the 'welcome' screen first being
// displayed, then quickly thereafter the files are opened.
NSArray *fileArgs = [NSArray arrayWithObject:@"-p"];
fileArgs = [fileArgs arrayByAddingObjectsFromArray:filenames];
pid = [self launchVimProcessWithArguments:fileArgs];
if (-1 == pid) {
// TODO: Notify user of failure?
[NSApp replyToOpenOrPrint:
NSApplicationDelegateReplyFailure];
return;
}
// Make sure these files aren't opened again when
// connectBackend:pid: is called.
[arguments setObject:[NSNumber numberWithBool:NO]
forKey:@"openFiles"];
}
// TODO: If the Vim process fails to start, or if it changes PID,
// then the memory allocated for these parameters will leak.
// Ensure that this cannot happen or somehow detect it.
if ([arguments count] > 0)
[pidArguments setObject:arguments
forKey:[NSNumber numberWithInt:pid]];
}
}
[NSApp replyToOpenOrPrint:NSApplicationDelegateReplySuccess];
// NSApplicationDelegateReplySuccess = 0,
// NSApplicationDelegateReplyCancel = 1,
// NSApplicationDelegateReplyFailure = 2
}
- (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)sender
{
return [[NSUserDefaults standardUserDefaults]
boolForKey:MMTerminateAfterLastWindowClosedKey];
}
- (NSApplicationTerminateReply)applicationShouldTerminate:
(NSApplication *)sender
{
// TODO: Follow Apple's guidelines for 'Graceful Application Termination'
// (in particular, allow user to review changes and save).
int reply = NSTerminateNow;
BOOL modifiedBuffers = NO;
// Go through windows, checking for modified buffers. (Each Vim process
// tells MacVim when any buffer has been modified and MacVim sets the
// 'documentEdited' flag of the window correspondingly.)
NSEnumerator *e = [[NSApp windows] objectEnumerator];
id window;
while ((window = [e nextObject])) {
if ([window isDocumentEdited]) {
modifiedBuffers = YES;
break;
}
}
if (modifiedBuffers) {
NSAlert *alert = [[NSAlert alloc] init];
[alert setAlertStyle:NSWarningAlertStyle];
[alert addButtonWithTitle:NSLocalizedString(@"Quit",
@"Dialog button")];
[alert addButtonWithTitle:NSLocalizedString(@"Cancel",
@"Dialog button")];
[alert setMessageText:NSLocalizedString(@"Quit without saving?",
@"Quit dialog with changed buffers, title")];
[alert setInformativeText:NSLocalizedString(
@"There are modified buffers, "
"if you quit now all changes will be lost. Quit anyway?",
@"Quit dialog with changed buffers, text")];
if ([alert runModal] != NSAlertFirstButtonReturn)
reply = NSTerminateCancel;
[alert release];
} else {
// No unmodified buffers, but give a warning if there are multiple
// windows and/or tabs open.
int numWindows = [vimControllers count];
int numTabs = 0;
// Count the number of open tabs
e = [vimControllers objectEnumerator];
id vc;
while ((vc = [e nextObject])) {
NSString *eval = [vc evaluateVimExpression:@"tabpagenr('$')"];
if (eval) {
int count = [eval intValue];
if (count > 0 && count < INT_MAX)
numTabs += count;
}
}
if (numWindows > 1 || numTabs > 1) {
NSAlert *alert = [[NSAlert alloc] init];
[alert setAlertStyle:NSWarningAlertStyle];
[alert addButtonWithTitle:NSLocalizedString(@"Quit",
@"Dialog button")];
[alert addButtonWithTitle:NSLocalizedString(@"Cancel",
@"Dialog button")];
[alert setMessageText:NSLocalizedString(
@"Are you sure you want to quit MacVim?",
@"Quit dialog with no changed buffers, title")];
NSString *info = nil;
if (numWindows > 1) {
if (numTabs > numWindows)
info = [NSString stringWithFormat:NSLocalizedString(
@"There are %d windows open in MacVim, with a "
"total of %d tabs. Do you want to quit anyway?",
@"Quit dialog with no changed buffers, text"),
numWindows, numTabs];
else
info = [NSString stringWithFormat:NSLocalizedString(
@"There are %d windows open in MacVim. "
"Do you want to quit anyway?",
@"Quit dialog with no changed buffers, text"),
numWindows];
} else {
info = [NSString stringWithFormat:NSLocalizedString(
@"There are %d tabs open in MacVim. "
"Do you want to quit anyway?",
@"Quit dialog with no changed buffers, text"),
numTabs];
}
[alert setInformativeText:info];
if ([alert runModal] != NSAlertFirstButtonReturn)
reply = NSTerminateCancel;
[alert release];
}
}
// Tell all Vim processes to terminate now (otherwise they'll leave swap
// files behind).
if (NSTerminateNow == reply) {
e = [vimControllers objectEnumerator];
id vc;
while ((vc = [e nextObject]))
[vc sendMessage:TerminateNowMsgID data:nil];
}
return reply;
}
- (void)applicationWillTerminate:(NSNotification *)notification
{
#if MM_HANDLE_XCODE_MOD_EVENT
[[NSAppleEventManager sharedAppleEventManager]
removeEventHandlerForEventClass:'KAHL'
andEventID:'MOD '];
#endif
// This will invalidate all connections (since they were spawned from this
// connection).
[connection invalidate];
// Send a SIGINT to all running Vim processes, so that they are sure to
// receive the connectionDidDie: notification (a process has to be checking
// the run-loop for this to happen).
unsigned i, count = [vimControllers count];
for (i = 0; i < count; ++i) {
MMVimController *controller = [vimControllers objectAtIndex:i];
int pid = [controller pid];
if (pid > 0)
kill(pid, SIGINT);
}
if (fontContainerRef) {
ATSFontDeactivate(fontContainerRef, NULL, kATSOptionFlagsDefault);
fontContainerRef = 0;
}
[NSApp setDelegate:nil];
}
+ (MMAppController *)sharedInstance
{
// Note: The app controller is a singleton which is instantiated in
// MainMenu.nib where it is also connected as the delegate of NSApp.
id delegate = [NSApp delegate];
return [delegate isKindOfClass:self] ? (MMAppController*)delegate : nil;
}
- (NSMenu *)defaultMainMenu
{
return defaultMainMenu;
}
- (NSMenuItem *)appMenuItemTemplate
{
return appMenuItemTemplate;
}
- (void)removeVimController:(id)controller
{
//NSLog(@"%s%@", _cmd, controller);
[[controller windowController] close];
[vimControllers removeObject:controller];
if (![vimControllers count]) {
// The last editor window just closed so restore the main menu back to
// its default state (which is defined in MainMenu.nib).
[self setMainMenu:defaultMainMenu];
}
}
- (void)windowControllerWillOpen:(MMWindowController *)windowController
{
NSPoint topLeft = NSZeroPoint;
NSWindow *topWin = [[[self topmostVimController] windowController] window];
NSWindow *win = [windowController window];
if (!win) return;
// If there is a window belonging to a Vim process, cascade from it,
// otherwise use the autosaved window position (if any).
if (topWin) {
NSRect frame = [topWin frame];
topLeft = NSMakePoint(frame.origin.x, NSMaxY(frame));
} else {
NSString *topLeftString = [[NSUserDefaults standardUserDefaults]
stringForKey:MMTopLeftPointKey];
if (topLeftString)
topLeft = NSPointFromString(topLeftString);
}
if (!NSEqualPoints(topLeft, NSZeroPoint)) {
if (topWin)
topLeft = [win cascadeTopLeftFromPoint:topLeft];
[win setFrameTopLeftPoint:topLeft];
}
if (openSelectionString) {
// TODO: Pass this as a parameter instead! Get rid of
// 'openSelectionString' etc.
//
// There is some text to paste into this window as a result of the
// services menu "Open selection ..." being used.
[[windowController vimController] dropString:openSelectionString];
[openSelectionString release];
openSelectionString = nil;
}
}
- (void)setMainMenu:(NSMenu *)mainMenu
{
if ([NSApp mainMenu] == mainMenu) return;
// If the new menu has a "Recent Files" dummy item, then swap the real item
// for the dummy. We are forced to do this since Cocoa initializes the
// "Recent Files" menu and there is no way to simply point Cocoa to a new
// item each time the menus are swapped.
NSMenu *fileMenu = [mainMenu findFileMenu];
if (recentFilesMenuItem && fileMenu) {
int dummyIdx =
[fileMenu indexOfItemWithAction:@selector(recentFilesDummy:)];
if (dummyIdx >= 0) {
NSMenuItem *dummyItem = [[fileMenu itemAtIndex:dummyIdx] retain];
[fileMenu removeItemAtIndex:dummyIdx];
NSMenu *recentFilesParentMenu = [recentFilesMenuItem menu];
int idx = [recentFilesParentMenu indexOfItem:recentFilesMenuItem];
if (idx >= 0) {
[[recentFilesMenuItem retain] autorelease];
[recentFilesParentMenu removeItemAtIndex:idx];
[recentFilesParentMenu insertItem:dummyItem atIndex:idx];
}
[fileMenu insertItem:recentFilesMenuItem atIndex:dummyIdx];
[dummyItem release];
}
}
// Now set the new menu. Notice that we keep one menu for each editor
// window since each editor can have its own set of menus. When swapping
// menus we have to tell Cocoa where the new "MacVim", "Windows", and
// "Services" menu are.
[NSApp setMainMenu:mainMenu];
// Setting the "MacVim" (or "Application") menu ensures that it is typeset
// in boldface. (The setAppleMenu: method used to be public but is now
// private so this will have to be considered a bit of a hack!)
NSMenu *appMenu = [mainMenu findApplicationMenu];
[NSApp performSelector:@selector(setAppleMenu:) withObject:appMenu];
NSMenu *servicesMenu = [mainMenu findServicesMenu];
[NSApp setServicesMenu:servicesMenu];
NSMenu *windowsMenu = [mainMenu findWindowsMenu];
if (windowsMenu) {
// Cocoa isn't clever enough to get rid of items it has added to the
// "Windows" menu so we have to do it ourselves otherwise there will be
// multiple menu items for each window in the "Windows" menu.
// This code assumes that the only items Cocoa add are ones which
// send off the action makeKeyAndOrderFront:. (Cocoa will not add
// another separator item if the last item on the "Windows" menu
// already is a separator, so we needen't worry about separators.)
int i, count = [windowsMenu numberOfItems];
for (i = count-1; i >= 0; --i) {
NSMenuItem *item = [windowsMenu itemAtIndex:i];
if ([item action] == @selector(makeKeyAndOrderFront:))
[windowsMenu removeItem:item];
}
}
[NSApp setWindowsMenu:windowsMenu];
}
- (IBAction)newWindow:(id)sender
{
[self launchVimProcessWithArguments:nil];
}
- (IBAction)fileOpen:(id)sender
{
NSString *dir = nil;
BOOL trackPwd = [[NSUserDefaults standardUserDefaults]
boolForKey:MMDialogsTrackPwdKey];
if (trackPwd) {
MMVimController *vc = [self keyVimController];
if (vc) dir = [[vc vimState] objectForKey:@"pwd"];
}
NSOpenPanel *panel = [NSOpenPanel openPanel];
[panel setAllowsMultipleSelection:YES];
int result = [panel runModalForDirectory:dir file:nil types:nil];
if (NSOKButton == result)
[self application:NSApp openFiles:[panel filenames]];
}
- (IBAction)selectNextWindow:(id)sender
{
unsigned i, count = [vimControllers count];
if (!count) return;
NSWindow *keyWindow = [NSApp keyWindow];
for (i = 0; i < count; ++i) {
MMVimController *vc = [vimControllers objectAtIndex:i];
if ([[[vc windowController] window] isEqual:keyWindow])
break;
}
if (i < count) {
if (++i >= count)
i = 0;
MMVimController *vc = [vimControllers objectAtIndex:i];
[[vc windowController] showWindow:self];
}
}
- (IBAction)selectPreviousWindow:(id)sender
{
unsigned i, count = [vimControllers count];
if (!count) return;
NSWindow *keyWindow = [NSApp keyWindow];
for (i = 0; i < count; ++i) {
MMVimController *vc = [vimControllers objectAtIndex:i];
if ([[[vc windowController] window] isEqual:keyWindow])
break;
}
if (i < count) {
if (i > 0) {
--i;
} else {
i = count - 1;
}
MMVimController *vc = [vimControllers objectAtIndex:i];
[[vc windowController] showWindow:self];
}
}
- (IBAction)orderFrontPreferencePanel:(id)sender
{
[[MMPreferenceController sharedPrefsWindowController] showWindow:self];
}
- (IBAction)openWebsite:(id)sender
{
[[NSWorkspace sharedWorkspace] openURL:
[NSURL URLWithString:MMWebsiteString]];
}
- (IBAction)showVimHelp:(id)sender
{
// Open a new window with the help window maximized.
[self launchVimProcessWithArguments:[NSArray arrayWithObjects:
@"-c", @":h gui_mac", @"-c", @":res", nil]];
}
- (IBAction)zoomAll:(id)sender
{
[NSApp makeWindowsPerform:@selector(performZoom:) inOrder:YES];
}
- (byref id <MMFrontendProtocol>)
connectBackend:(byref in id <MMBackendProtocol>)backend
pid:(int)pid
{
//NSLog(@"Connect backend (pid=%d)", pid);
NSNumber *pidKey = [NSNumber numberWithInt:pid];
MMVimController *vc = nil;
@try {
[(NSDistantObject*)backend
setProtocolForProxy:@protocol(MMBackendProtocol)];
vc = [[[MMVimController alloc]
initWithBackend:backend pid:pid]
autorelease];
if (![vimControllers count]) {
// The first window autosaves its position. (The autosaving
// features of Cocoa are not used because we need more control over
// what is autosaved and when it is restored.)
[[vc windowController] setWindowAutosaveKey:MMTopLeftPointKey];
}
[vimControllers addObject:vc];
id args = [pidArguments objectForKey:pidKey];
if (args && [NSNull null] != args)
[self passArguments:args toVimController:vc];
// HACK! MacVim does not get activated if it is launched from the
// terminal, so we forcibly activate here unless it is an untitled
// window opening. Untitled windows are treated differently, else
// MacVim would steal the focus if another app was activated while the
// untitled window was loading.
if (!args || args != [NSNull null])
[NSApp activateIgnoringOtherApps:YES];
if (args)
[pidArguments removeObjectForKey:pidKey];
return vc;
}
@catch (NSException *e) {
NSLog(@"Exception caught in %s: \"%@\"", _cmd, e);
if (vc)
[vimControllers removeObject:vc];
[pidArguments removeObjectForKey:pidKey];
}
return nil;
}
- (NSArray *)serverList
{
NSMutableArray *array = [NSMutableArray array];
unsigned i, count = [vimControllers count];
for (i = 0; i < count; ++i) {
MMVimController *controller = [vimControllers objectAtIndex:i];
if ([controller serverName])
[array addObject:[controller serverName]];
}
return array;
}
@end // MMAppController
@implementation MMAppController (MMServices)
- (void)openSelection:(NSPasteboard *)pboard userData:(NSString *)userData
error:(NSString **)error
{
if (![[pboard types] containsObject:NSStringPboardType]) {
NSLog(@"WARNING: Pasteboard contains no object of type "
"NSStringPboardType");
return;
}
MMVimController *vc = [self topmostVimController];
if (vc) {
// Open a new tab first, since dropString: does not do this.
[vc sendMessage:AddNewTabMsgID data:nil];
[vc dropString:[pboard stringForType:NSStringPboardType]];
} else {
// NOTE: There is no window to paste the selection into, so save the
// text, open a new window, and paste the text when the next window
// opens. (If this is called several times in a row, then all but the
// last call might be ignored.)
if (openSelectionString) [openSelectionString release];
openSelectionString = [[pboard stringForType:NSStringPboardType] copy];
[self newWindow:self];
}
}
- (void)openFile:(NSPasteboard *)pboard userData:(NSString *)userData
error:(NSString **)error
{
if (![[pboard types] containsObject:NSStringPboardType]) {
NSLog(@"WARNING: Pasteboard contains no object of type "
"NSStringPboardType");
return;
}
// TODO: Parse multiple filenames and create array with names.
NSString *string = [pboard stringForType:NSStringPboardType];
string = [string stringByTrimmingCharactersInSet:
[NSCharacterSet whitespaceAndNewlineCharacterSet]];
string = [string stringByStandardizingPath];
NSArray *filenames = [self filterFilesAndNotify:
[NSArray arrayWithObject:string]];
if ([filenames count] > 0) {
MMVimController *vc = nil;
if (userData && [userData isEqual:@"Tab"])
vc = [self topmostVimController];
if (vc) {
[vc dropFiles:filenames forceOpen:YES];
} else {
[self application:NSApp openFiles:filenames];
}
}
}
@end // MMAppController (MMServices)
@implementation MMAppController (Private)
- (MMVimController *)keyVimController
{
NSWindow *keyWindow = [NSApp keyWindow];
if (keyWindow) {
unsigned i, count = [vimControllers count];
for (i = 0; i < count; ++i) {
MMVimController *vc = [vimControllers objectAtIndex:i];
if ([[[vc windowController] window] isEqual:keyWindow])
return vc;
}
}
return nil;
}
- (MMVimController *)topmostVimController
{
// Find the topmost visible window which has an associated vim controller.
NSEnumerator *e = [[NSApp orderedWindows] objectEnumerator];
id window;
while ((window = [e nextObject]) && [window isVisible]) {
unsigned i, count = [vimControllers count];
for (i = 0; i < count; ++i) {
MMVimController *vc = [vimControllers objectAtIndex:i];
if ([[[vc windowController] window] isEqual:window])
return vc;
}
}
return nil;
}
- (int)launchVimProcessWithArguments:(NSArray *)args
{
int pid = -1;
NSString *path = [[NSBundle mainBundle] pathForAuxiliaryExecutable:@"Vim"];
if (!path) {
NSLog(@"ERROR: Vim executable could not be found inside app bundle!");
return -1;
}
NSArray *taskArgs = [NSArray arrayWithObjects:@"-g", @"-f", nil];
if (args)
taskArgs = [taskArgs arrayByAddingObjectsFromArray:args];
BOOL useLoginShell = [[NSUserDefaults standardUserDefaults]
boolForKey:MMLoginShellKey];
if (useLoginShell) {
// Run process with a login shell, roughly:
// echo "exec Vim -g -f args" | ARGV0=-`basename $SHELL` $SHELL [-l]
pid = executeInLoginShell(path, taskArgs);
} else {
// Run process directly:
// Vim -g -f args
NSTask *task = [NSTask launchedTaskWithLaunchPath:path
arguments:taskArgs];
pid = task ? [task processIdentifier] : -1;
}
if (-1 != pid) {
// NOTE: If the process has no arguments, then add a null argument to
// the pidArguments dictionary. This is later used to detect that a
// process without arguments is being launched.
if (!args)
[pidArguments setObject:[NSNull null]
forKey:[NSNumber numberWithInt:pid]];
} else {
NSLog(@"WARNING: %s%@ failed (useLoginShell=%d)", _cmd, args,
useLoginShell);
}
return pid;
}
- (NSArray *)filterFilesAndNotify:(NSArray *)filenames
{
// Go trough 'filenames' array and make sure each file exists. Present
// warning dialog if some file was missing.
NSString *firstMissingFile = nil;
NSMutableArray *files = [NSMutableArray array];
unsigned i, count = [filenames count];
for (i = 0; i < count; ++i) {
NSString *name = [filenames objectAtIndex:i];
if ([[NSFileManager defaultManager] fileExistsAtPath:name]) {
[files addObject:name];
} else if (!firstMissingFile) {
firstMissingFile = name;
}
}
if (firstMissingFile) {
NSAlert *alert = [[NSAlert alloc] init];
[alert addButtonWithTitle:NSLocalizedString(@"OK",
@"Dialog button")];