Skip to content

Commit d412800

Browse files
committed
Fix duplicate menu items in Window menu
Previously MacVim would see a lot of duplicate window menu items like "Enter Full Screen" or "Tile Window to Left of Screen" when the user toggles between two windows. This is because the `setWindowsMenu:` call was injecting these items, but AppKit isn't smart enough to de-duplicate them (unlike the window list at the bottom). Just fix this by making a copy of the main menu before passing it in. This way every time we try to set a main menu (which happens whenever we jump among Vim windows as each Vim can have different menu items), it will be set with a fresh one that doesn't have the injected menu items in it. - This also requires adding a refresh functionality because adding/removing items to the original menu no longer get automatically reflected to the app since it only knows about the copied version. Also, set NSFullScreenMenuItemEverywhere to prevent AppKit from injecting "Enter Full Screen" items. MacVim already has similar menu items to handle that. Also, remove old private API call to `setAppleMenu:`. As far as I could tell this is not useful anymore in recent macOS versions and that line of code was written in 2008. Fix #566, Fix #992
1 parent 8b25779 commit d412800

File tree

3 files changed

+81
-35
lines changed

3 files changed

+81
-35
lines changed

src/MacVim/MMAppController.h

+6
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,11 @@
2222
NSMutableArray *vimControllers;
2323
NSString *openSelectionString;
2424
NSMutableDictionary *pidArguments;
25+
2526
NSMenu *defaultMainMenu;
27+
NSMenu *currentMainMenu;
28+
BOOL mainMenuDirty;
29+
2630
NSMenuItem *appMenuItemTemplate;
2731
NSMenuItem *recentFilesMenuItem;
2832
NSMutableArray *cachedVimControllers;
@@ -44,6 +48,8 @@
4448
- (void)removeVimController:(id)controller;
4549
- (void)windowControllerWillOpen:(MMWindowController *)windowController;
4650
- (void)setMainMenu:(NSMenu *)mainMenu;
51+
- (void)markMainMenuDirty:(NSMenu *)mainMenu;
52+
- (void)refreshMainMenu;
4753
- (NSArray *)filterOpenFiles:(NSArray *)filenames;
4854
- (BOOL)openFiles:(NSArray *)filenames withArguments:(NSDictionary *)args;
4955

src/MacVim/MMAppController.m

+56-34
Original file line numberDiff line numberDiff line change
@@ -299,7 +299,7 @@ - (id)init
299299
ASLogCrit(@"Failed to register connection with name '%@'", name);
300300
[connection release]; connection = nil;
301301
}
302-
302+
303303
#if !DISABLE_SPARKLE
304304
// Sparkle is enabled (this is the default). Initialize it. It will
305305
// automatically check for update.
@@ -321,6 +321,7 @@ - (void)dealloc
321321
[openSelectionString release]; openSelectionString = nil;
322322
[recentFilesMenuItem release]; recentFilesMenuItem = nil;
323323
[defaultMainMenu release]; defaultMainMenu = nil;
324+
currentMainMenu = nil;
324325
[appMenuItemTemplate release]; appMenuItemTemplate = nil;
325326
[updater release]; updater = nil;
326327

@@ -329,6 +330,11 @@ - (void)dealloc
329330

330331
- (void)applicationWillFinishLaunching:(NSNotification *)notification
331332
{
333+
// This prevents macOS from injecting "Enter Full Screen" menu item.
334+
// MacVim already has a separate menu item to do that.
335+
// See https://developer.apple.com/library/archive/releasenotes/AppKit/RN-AppKitOlderNotes/index.html#10_11FullScreen
336+
[[NSUserDefaults standardUserDefaults] setBool:NO forKey:@"NSFullScreenMenuItemEverywhere"];
337+
332338
// Remember the default menu so that it can be restored if the user closes
333339
// all editor windows.
334340
defaultMainMenu = [[NSApp mainMenu] retain];
@@ -862,7 +868,42 @@ - (void)windowControllerWillOpen:(MMWindowController *)windowController
862868

863869
- (void)setMainMenu:(NSMenu *)mainMenu
864870
{
865-
if ([NSApp mainMenu] == mainMenu) return;
871+
if (currentMainMenu == mainMenu) {
872+
return;
873+
}
874+
currentMainMenu = mainMenu;
875+
[self refreshMainMenu];
876+
}
877+
878+
// Refresh the currently active main menu. This call is necessary when any
879+
// modification was made to the menu, because refreshMainMenu makes a copy of
880+
// the main menu, meaning that modifications to the original menu wouldn't be
881+
// reflected until refreshMainMenu is invoked.
882+
- (void)markMainMenuDirty:(NSMenu *)mainMenu
883+
{
884+
if (currentMainMenu != mainMenu) {
885+
// The menu being updated is not the currently set menu, so just ignore,
886+
// as this is likely a background Vim window.
887+
return;
888+
}
889+
if (!mainMenuDirty) {
890+
// Mark the main menu as dirty and queue up a refresh. We don't immediately
891+
// execute the refresh so that multiple calls would get batched up in one go.
892+
mainMenuDirty = YES;
893+
[self performSelectorOnMainThread:@selector(refreshMainMenu) withObject:nil waitUntilDone:NO];
894+
}
895+
}
896+
897+
- (void)refreshMainMenu
898+
{
899+
mainMenuDirty = NO;
900+
901+
// Make a copy of the menu before we pass to AppKit. The main reason is
902+
// that setWindowsMenu: below will inject items like "Tile Window to Left
903+
// of Screen" to the Window menu, and on repeated calls it will keep adding
904+
// the same item over and over again, without resolving for duplicates. Using
905+
// copies help keep the source menu clean.
906+
NSMenu *mainMenu = [currentMainMenu copy];
866907

867908
// If the new menu has a "Recent Files" dummy item, then swap the real item
868909
// for the dummy. We are forced to do this since Cocoa initializes the
@@ -871,7 +912,7 @@ - (void)setMainMenu:(NSMenu *)mainMenu
871912
NSMenu *fileMenu = [mainMenu findFileMenu];
872913
if (recentFilesMenuItem && fileMenu) {
873914
int dummyIdx =
874-
[fileMenu indexOfItemWithAction:@selector(recentFilesDummy:)];
915+
[fileMenu indexOfItemWithAction:@selector(recentFilesDummy:)];
875916
if (dummyIdx >= 0) {
876917
NSMenuItem *dummyItem = [[fileMenu itemAtIndex:dummyIdx] retain];
877918
[fileMenu removeItemAtIndex:dummyIdx];
@@ -889,45 +930,26 @@ - (void)setMainMenu:(NSMenu *)mainMenu
889930
}
890931
}
891932

892-
// Now set the new menu. Notice that we keep one menu for each editor
893-
// window since each editor can have its own set of menus. When swapping
894-
// menus we have to tell Cocoa where the new "MacVim", "Windows", and
895-
// "Services" menu are.
896-
[NSApp setMainMenu:mainMenu];
897-
898-
// Setting the "MacVim" (or "Application") menu ensures that it is typeset
899-
// in boldface. (The setAppleMenu: method used to be public but is now
900-
// private so this will have to be considered a bit of a hack!)
901-
NSMenu *appMenu = [mainMenu findApplicationMenu];
902-
[NSApp performSelector:@selector(setAppleMenu:) withObject:appMenu];
903-
904933
#if DISABLE_SPARKLE
934+
NSMenu *appMenu = [mainMenu findApplicationMenu];
935+
905936
// If Sparkle is disabled, we want to remove the "Check for Updates" menu
906937
// item since it's no longer useful.
907938
NSMenuItem *checkForUpdatesItem = [appMenu itemAtIndex:
908939
[appMenu indexOfItemWithAction:@selector(checkForUpdates:)]];
909940
checkForUpdatesItem.hidden = true;
910941
#endif
911942

943+
// Now set the new menu. Notice that we keep one menu for each editor
944+
// window since each editor can have its own set of menus. When swapping
945+
// menus we have to tell Cocoa where the new "MacVim", "Windows", and
946+
// "Services" menu are.
947+
[NSApp setMainMenu:mainMenu];
948+
912949
NSMenu *servicesMenu = [mainMenu findServicesMenu];
913950
[NSApp setServicesMenu:servicesMenu];
914951

915952
NSMenu *windowsMenu = [mainMenu findWindowsMenu];
916-
if (windowsMenu) {
917-
// Cocoa isn't clever enough to get rid of items it has added to the
918-
// "Windows" menu so we have to do it ourselves otherwise there will be
919-
// multiple menu items for each window in the "Windows" menu.
920-
// This code assumes that the only items Cocoa add are ones which
921-
// send off the action makeKeyAndOrderFront:. (Cocoa will not add
922-
// another separator item if the last item on the "Windows" menu
923-
// already is a separator, so we needen't worry about separators.)
924-
int i, count = [windowsMenu numberOfItems];
925-
for (i = count-1; i >= 0; --i) {
926-
NSMenuItem *item = [windowsMenu itemAtIndex:i];
927-
if ([item action] == @selector(makeKeyAndOrderFront:))
928-
[windowsMenu removeItem:item];
929-
}
930-
}
931953
[NSApp setWindowsMenu:windowsMenu];
932954
}
933955

@@ -1220,7 +1242,7 @@ - (IBAction)showVimHelp:(id)sender
12201242
@"-c", @":h gui_mac", @"-c", @":res", nil]
12211243
workingDirectory:nil];
12221244
}
1223-
1245+
12241246
- (IBAction)checkForUpdates:(id)sender
12251247
{
12261248
#if !DISABLE_SPARKLE
@@ -1816,7 +1838,7 @@ - (void)handleGetURLEvent:(NSAppleEventDescriptor *)event
18161838
// Only opens files that already exist.
18171839
if ([[NSFileManager defaultManager] fileExistsAtPath:filePath]) {
18181840
NSArray *filenames = [NSArray arrayWithObject:filePath];
1819-
1841+
18201842
// Look for the line and column options.
18211843
NSDictionary *args = nil;
18221844
NSString *line = [dict objectForKey:@"line"];
@@ -1831,7 +1853,7 @@ - (void)handleGetURLEvent:(NSAppleEventDescriptor *)event
18311853
args = [NSDictionary dictionaryWithObject:line
18321854
forKey:@"cursorLine"];
18331855
}
1834-
1856+
18351857
[self openFiles:filenames withArguments:args];
18361858
} else {
18371859
NSAlert *alert = [[NSAlert alloc] init];
@@ -2198,7 +2220,7 @@ - (void)startWatchingVimDir
21982220

21992221
NSString *path = [@"~/.vim" stringByExpandingTildeInPath];
22002222
NSArray *pathsToWatch = [NSArray arrayWithObject:path];
2201-
2223+
22022224
fsEventStream = FSEventStreamCreate(NULL, &fsEventCallback, NULL,
22032225
(CFArrayRef)pathsToWatch, kFSEventStreamEventIdSinceNow,
22042226
MMEventStreamLatency, kFSEventStreamCreateFlagNone);

src/MacVim/MMVimController.m

+19-1
Original file line numberDiff line numberDiff line change
@@ -1251,7 +1251,8 @@ - (void)addMenuWithDescriptor:(NSArray *)desc atIndex:(int)idx
12511251
[item setSubmenu:menu];
12521252

12531253
NSMenu *parent = [self parentMenuForDescriptor:desc];
1254-
if (!parent && [MMVimController hasPopupPrefix:rootName]) {
1254+
const BOOL isPopup = [MMVimController hasPopupPrefix:rootName];
1255+
if (!parent && isPopup) {
12551256
if ([popupMenuItems count] <= idx) {
12561257
[popupMenuItems addObject:item];
12571258
} else {
@@ -1271,6 +1272,8 @@ - (void)addMenuWithDescriptor:(NSArray *)desc atIndex:(int)idx
12711272

12721273
[item release];
12731274
[menu release];
1275+
if (!isPopup)
1276+
[[MMAppController sharedInstance] markMainMenuDirty:mainMenu];
12741277
}
12751278

12761279
- (void)addMenuItemWithDescriptor:(NSArray *)desc
@@ -1350,6 +1353,9 @@ - (void)addMenuItemWithDescriptor:(NSArray *)desc
13501353
} else {
13511354
[parent insertItem:item atIndex:idx];
13521355
}
1356+
const BOOL isPopup = [MMVimController hasPopupPrefix:rootName];
1357+
if (!isPopup)
1358+
[[MMAppController sharedInstance] markMainMenuDirty:mainMenu];
13531359
}
13541360

13551361
- (void)removeMenuItemWithDescriptor:(NSArray *)desc
@@ -1405,6 +1411,10 @@ - (void)removeMenuItemWithDescriptor:(NSArray *)desc
14051411
[[item menu] removeItem:item];
14061412

14071413
[item release];
1414+
1415+
const BOOL isPopup = [MMVimController hasPopupPrefix:rootName];
1416+
if (!isPopup)
1417+
[[MMAppController sharedInstance] markMainMenuDirty:mainMenu];
14081418
}
14091419

14101420
- (void)enableMenuItemWithDescriptor:(NSArray *)desc state:(BOOL)on
@@ -1439,6 +1449,10 @@ - (void)enableMenuItemWithDescriptor:(NSArray *)desc state:(BOOL)on
14391449
// but at the same time Vim can set if a menu is enabled whenever it
14401450
// wants to.
14411451
[[self menuItemForDescriptor:desc] setTag:on];
1452+
1453+
const BOOL isPopup = [MMVimController hasPopupPrefix:rootName];
1454+
if (!isPopup)
1455+
[[MMAppController sharedInstance] markMainMenuDirty:mainMenu];
14421456
}
14431457

14441458
- (void)updateMenuItemTooltipWithDescriptor:(NSArray *)desc
@@ -1471,6 +1485,10 @@ - (void)updateMenuItemTooltipWithDescriptor:(NSArray *)desc
14711485
}
14721486

14731487
[[self menuItemForDescriptor:desc] setToolTip:tip];
1488+
1489+
const BOOL isPopup = [MMVimController hasPopupPrefix:rootName];
1490+
if (!isPopup)
1491+
[[MMAppController sharedInstance] markMainMenuDirty:mainMenu];
14741492
}
14751493

14761494
- (void)addToolbarItemToDictionaryWithLabel:(NSString *)title

0 commit comments

Comments
 (0)