Skip to content

Commit 8d68a01

Browse files
committed
Support native macOS monospace font (SF Mono)
Can now set guifont to `-monospace-` to use the system default monospace font, which is SF Mono on recent macOS versions (but could be updated to other fonts in future macOS releases). The reason why this is necessary instead of specifying the actual font name is that Apple does not expose the SF Mono font for the user and instead only exposes an AppKit API `monospacedSystemFontOfSize:weight:` to access it. The actual font name (`.AppleSystemUIFontMonospaced` in macOS 14) is internal and subject to change in different OS versions. In older macOS versions, setting `-monospace-` will just use `Menlo-Regular` just like the default font. Also allow specifying the font weight for the font, e.g. `-monospace-Semibold` / `-monospace-Light`. The list of weights follows the NSFontWeight enum, but not all values yield unique fonts. E.g. "UltraLight", "Thin", "Light" will all use the "Light" version of SF Mono. The list of all font weights can be tab-completed but only if the user has already filled in `-monospace-` in `:set guifont=`. This helps prevents showing too many options when the user does tab completion just to see the list of all fonts. Note that SF Mono is currently available to be downloaded from Apple's website as a standalone for testing. That font is mostly the same but seems to have slightly different line spacing behavior, and when using bold it uses the "Bold" font variant, whereas the system monospace font uses "Semibold" variant instead. Also make font panel not show misc formatting options like underline as they aren't used by MacVim. Keep the background/foreground option just so the font preview colors in the panel can be adjusted. Also fix an existing potential buffer overflow issue in the Core Text renderer in that `changeFont:` (when setting a new font using font panel or using font size up/down) isn't setting `wideLen` which could cause an unsafe memory access in Vim side. Notes: - Known issue: When using macaction `fontSizeUp:`/`fontSizeDown:` (Cmd +/-), `-monospace-` will get replaced by the internal font name (e.g. `.AppleSystemUIFontMonospaced-Regular`) instead due to how the `changeFont:` currently works. This could be fixed but it's low enough priority that it's ok for now. - In the future, this may become the default font instead of Menlo, to make MacVim more consistent with Apple software like Terminal and Xcode.
1 parent 301f5b4 commit 8d68a01

File tree

12 files changed

+188
-12
lines changed

12 files changed

+188
-12
lines changed

runtime/doc/gui.txt

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1129,11 +1129,22 @@ That's all. XLFDs are not used. For Chinese this is reported to work well: >
11291129
<
11301130
(Replace gui_gtk2 with gui_gtk3 for the GTK+ 3 GUI)
11311131

1132-
For Mac OSX you can use something like this: >
1133-
:set guifont=Monaco:h10
1134-
Also see 'macatsui', it can help fix display problems {not in MacVim}.
1135-
In MacVim, fonts with spaces are set like this: >
1132+
MacVim *macvim-guifont*
1133+
1134+
For MacVim you can use something like this: >
1135+
:set guifont=Menlo:h10
1136+
Fonts with spaces are set like this: >
11361137
:set guifont=DejaVu\ Sans\ Mono:h13
1138+
<To use bold/italic fonts, use the fully specified PostScript name of the
1139+
font, like so: >
1140+
:set guifont=Menlo-Bold:h13
1141+
<To use the system native monospace font (which is SF Mono in new macOS
1142+
versions), use the `-monospace-` keyword: >
1143+
:set guifont=-monospace-:h12
1144+
<You can also specify the font weight of the native monospace font (refer to
1145+
Apple documentation for `NSFontWeight` for possible values): >
1146+
:set guifont=-monospace-Light:h11
1147+
:set guifont=-monospace-Medium:h11
11371148
<
11381149
Mono-spaced fonts *E236*
11391150

runtime/doc/gui_mac.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,9 @@ These are the non-standard options that MacVim supports:
118118
'fuoptions' 'macligatures' 'macmeta' 'macthinstrokes'
119119
'toolbariconsize' 'transparency'
120120

121+
These are GUI-related Vim options that have MacVim-specific behaviors:
122+
'guifont'
123+
121124
*macvim-commands*
122125
These are the non-standard commands that MacVim supports:
123126
|:macaction| |:macmenu|

runtime/doc/tags

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8622,6 +8622,7 @@ macvim-encoding gui_mac.txt /*macvim-encoding*
86228622
macvim-find gui_mac.txt /*macvim-find*
86238623
macvim-full-screen gui_mac.txt /*macvim-full-screen*
86248624
macvim-gestures gui_mac.txt /*macvim-gestures*
8625+
macvim-guifont gui.txt /*macvim-guifont*
86258626
macvim-help-menu gui_mac.txt /*macvim-help-menu*
86268627
macvim-hints gui_mac.txt /*macvim-hints*
86278628
macvim-internal-variables gui_mac.txt /*macvim-internal-variables*

src/MacVim/MMCoreTextView.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ NS_ASSUME_NONNULL_BEGIN
8787
// NSFontChanging methods
8888
//
8989
- (void)changeFont:(nullable id)sender;
90+
- (NSFontPanelModeMask)validModesForFontPanel:(NSFontPanel *)fontPanel;
9091

9192
//
9293
// NSMenuItemValidation

src/MacVim/MMCoreTextView.m

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1299,9 +1299,10 @@ - (NSSize)minSize
12991299
MMMinRows * cellSize.height + insetSize.height + bot);
13001300
}
13011301

1302-
// Called when font panel selection has been made. Send the selected font to
1303-
// MMBackend so it would set guifont which will send a message back to MacVim to
1304-
// call MMWindowController::setFont.
1302+
// Called when font panel selection has been made or when adjusting font size
1303+
// using modifyFont/NSSizeUpFontAction. Send the selected font to MMBackend so
1304+
// it would set guifont which will send a message back to MacVim to call
1305+
// MMWindowController::setFont.
13051306
- (void)changeFont:(id)sender
13061307
{
13071308
NSFont *newFont = [sender convertFont:font];
@@ -1319,11 +1320,25 @@ - (void)changeFont:(id)sender
13191320
[data appendBytes:&len length:sizeof(unsigned)];
13201321
[data appendBytes:[name UTF8String] length:len];
13211322

1323+
// We don't update guifontwide for now, as panel font selection
1324+
// shouldn't affect them. This does mean Cmd +/- does not work for
1325+
// them for now.
1326+
const unsigned wideLen = 0;
1327+
[data appendBytes:&wideLen length:sizeof(unsigned)];
1328+
13221329
[[self vimController] sendMessage:SetFontMsgID data:data];
13231330
}
13241331
}
13251332
}
13261333

1334+
- (NSFontPanelModeMask)validModesForFontPanel:(NSFontPanel *)fontPanel
1335+
{
1336+
// Lets the user pick only the font face / size, as other properties as not
1337+
// useful. Still enable text/document colors as these affect the preview.
1338+
// Otherwise it could just be white text on white background in the preview.
1339+
return NSFontPanelModesMaskStandardModes & (~NSFontPanelModeMaskAllEffects | NSFontPanelModeMaskTextColorEffect | NSFontPanelModeMaskDocumentColorEffect);
1340+
}
1341+
13271342
/// Specifies whether the menu item should be enabled/disabled.
13281343
- (BOOL)validateMenuItem:(NSMenuItem *)item
13291344
{

src/MacVim/MMVimController.m

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -900,7 +900,44 @@ - (void)handleMessage:(int)msgid data:(NSData *)data
900900
NSString *name = [[NSString alloc]
901901
initWithBytes:(void*)bytes length:len
902902
encoding:NSUTF8StringEncoding];
903-
NSFont *font = [NSFont fontWithName:name size:size];
903+
NSFont *font = nil;
904+
if ([name hasPrefix:MMSystemFontAlias]) {
905+
#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_15
906+
if (@available(macos 10.15, *)) {
907+
NSFontWeight fontWeight = NSFontWeightRegular;
908+
if (name.length > MMSystemFontAlias.length) {
909+
const NSRange cmpRange = NSMakeRange(MMSystemFontAlias.length, name.length - MMSystemFontAlias.length);
910+
if ([name compare:@"UltraLight" options:NSCaseInsensitiveSearch range:cmpRange] == NSOrderedSame)
911+
fontWeight = NSFontWeightUltraLight;
912+
else if ([name compare:@"Thin" options:NSCaseInsensitiveSearch range:cmpRange] == NSOrderedSame)
913+
fontWeight = NSFontWeightThin;
914+
else if ([name compare:@"Light" options:NSCaseInsensitiveSearch range:cmpRange] == NSOrderedSame)
915+
fontWeight = NSFontWeightLight;
916+
else if ([name compare:@"Regular" options:NSCaseInsensitiveSearch range:cmpRange] == NSOrderedSame)
917+
fontWeight = NSFontWeightRegular;
918+
else if ([name compare:@"Medium" options:NSCaseInsensitiveSearch range:cmpRange] == NSOrderedSame)
919+
fontWeight = NSFontWeightMedium;
920+
else if ([name compare:@"Semibold" options:NSCaseInsensitiveSearch range:cmpRange] == NSOrderedSame)
921+
fontWeight = NSFontWeightSemibold;
922+
else if ([name compare:@"Bold" options:NSCaseInsensitiveSearch range:cmpRange] == NSOrderedSame)
923+
fontWeight = NSFontWeightBold;
924+
else if ([name compare:@"Heavy" options:NSCaseInsensitiveSearch range:cmpRange] == NSOrderedSame)
925+
fontWeight = NSFontWeightHeavy;
926+
else if ([name compare:@"Black" options:NSCaseInsensitiveSearch range:cmpRange] == NSOrderedSame)
927+
fontWeight = NSFontWeightBlack;
928+
}
929+
font = [NSFont monospacedSystemFontOfSize:size weight:fontWeight];
930+
}
931+
else
932+
#endif
933+
{
934+
// Fallback to Menlo on older macOS versions that don't support the system monospace font API
935+
font = [NSFont fontWithName:@"Menlo-Regular" size:size];
936+
}
937+
}
938+
else {
939+
font = [NSFont fontWithName:name size:size];
940+
}
904941
if (!font) {
905942
// This should only happen if the system default font has changed
906943
// name since MacVim was compiled in which case we fall back on

src/MacVim/MacVim.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -433,7 +433,8 @@ enum {
433433

434434
extern NSString *VimFindPboardType;
435435

436-
436+
// Alias for system monospace font name
437+
extern NSString *MMSystemFontAlias;
437438

438439

439440
@interface NSString (MMExtras)

src/MacVim/MacVim.m

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@
4040
// Vim find pasteboard type (string contains Vim regex patterns)
4141
NSString *VimFindPboardType = @"VimFindPboardType";
4242

43+
NSString *MMSystemFontAlias = @"-monospace-";
44+
4345
int ASLogLevel = MM_ASL_LEVEL_DEFAULT;
4446

4547

src/MacVim/MacVimTests/MacVimTests.m

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010

1111
#import <objc/runtime.h>
1212

13+
#import <Cocoa/Cocoa.h>
14+
1315
#import "Miscellaneous.h"
1416
#import "MMAppController.h"
1517
#import "MMApplication.h"
@@ -425,4 +427,35 @@ - (void) testCmdlineRowCalculation {
425427
[self waitForVimClose];
426428
}
427429

430+
/// Test that using "-monospace-" for system default monospace font works.
431+
- (void) testGuifontSystemMonospace {
432+
MMAppController *app = MMAppController.sharedInstance;
433+
434+
[app openNewWindow:NewWindowClean activate:YES];
435+
[self waitForVimOpenAndMessages];
436+
437+
MMTextView *textView = [[[[app keyVimController] windowController] vimView] textView];
438+
XCTAssertEqualObjects(@"Menlo-Regular", [[textView font] fontName]);
439+
440+
[self sendStringToVim:@":set guifont=-monospace-\n" withMods:0];
441+
[self waitForEventHandlingAndVimProcess];
442+
XCTAssertEqualObjects([textView font], [NSFont monospacedSystemFontOfSize:11 weight:NSFontWeightRegular]);
443+
444+
[self sendStringToVim:@":set guifont=-monospace-Heavy:h12\n" withMods:0];
445+
[self waitForEventHandlingAndVimProcess];
446+
XCTAssertEqualObjects([textView font], [NSFont monospacedSystemFontOfSize:12 weight:NSFontWeightHeavy]);
447+
448+
[[[app keyVimController] windowController] fontSizeUp:nil];
449+
[self waitForEventHandlingAndVimProcess];
450+
XCTAssertEqualObjects([textView font], [NSFont monospacedSystemFontOfSize:13 weight:NSFontWeightHeavy]);
451+
452+
[[[app keyVimController] windowController] fontSizeDown:nil];
453+
[self waitForEventHandlingAndVimProcess];
454+
XCTAssertEqualObjects([textView font], [NSFont monospacedSystemFontOfSize:12 weight:NSFontWeightHeavy]);
455+
456+
// Clean up
457+
[[app keyVimController] sendMessage:VimShouldCloseMsgID data:nil];
458+
[self waitForVimClose];
459+
}
460+
428461
@end

src/MacVim/Miscellaneous.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -153,7 +153,8 @@ enum {
153153

154154

155155
@interface NSNumber (MMExtras)
156-
// HACK to allow font size to be changed via menu (bound to Cmd+/Cmd-)
156+
// Used by modifyFont:/convertFont: to allow font size to be changed via menu
157+
// (bound to Cmd+/Cmd-) or using macaction fontSizeUp:/fontSizeDown:.
157158
- (NSInteger)tag;
158159
@end
159160

src/MacVim/gui_macvim.m

Lines changed: 44 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,11 +32,13 @@
3232

3333
static NSString *MMDefaultFontName = @"Menlo-Regular";
3434
static int MMDefaultFontSize = 11;
35-
static char *MMDefaultFontStr = "Menlo-Regular:h11";
3635
static char *MMDefaultFontSizeStr = "h11";
3736
static int MMMinFontSize = 6;
3837
static int MMMaxFontSize = 100;
3938

39+
// This is duplicated in MMVimController. Could consolidate in the future.
40+
static NSString *(system_font_weights[]) = { @"UltraLight", @"Thin", @"Light", @"Regular", @"Medium", @"Semibold", @"Bold", @"Heavy", @"Black" };
41+
4042
static BOOL MMShareFindPboard = YES;
4143

4244
static GuiFont gui_macvim_font_with_name(char_u *name);
@@ -1141,13 +1143,30 @@
11411143
componentsJoinedByString:@" "];
11421144
}
11431145

1146+
const BOOL isSystemFont = [fontName hasPrefix:MMSystemFontAlias];
1147+
if (isSystemFont) {
1148+
if (fontName.length > MMSystemFontAlias.length) {
1149+
BOOL invalidWeight = YES;
1150+
const NSRange cmpRange = NSMakeRange(MMSystemFontAlias.length, fontName.length - MMSystemFontAlias.length);
1151+
for (size_t i = 0; i < ARRAY_LENGTH(system_font_weights); i++) {
1152+
if ([fontName compare:system_font_weights[i] options:NSCaseInsensitiveSearch range:cmpRange] == NSOrderedSame) {
1153+
invalidWeight = NO;
1154+
break;
1155+
}
1156+
}
1157+
if (invalidWeight)
1158+
return NOFONT;
1159+
}
1160+
}
1161+
11441162
if (!parseFailed && [fontName length] > 0) {
11451163
if (size < MMMinFontSize) size = MMMinFontSize;
11461164
if (size > MMMaxFontSize) size = MMMaxFontSize;
11471165

11481166
// If the default font is requested we don't need to check if NSFont
11491167
// can load it. Otherwise we ask NSFont if it can load it.
11501168
if ([fontName isEqualToString:MMDefaultFontName]
1169+
|| isSystemFont
11511170
|| [NSFont fontWithName:fontName size:size])
11521171
return [[NSString alloc] initWithFormat:@"%@:h%d", fontName, size];
11531172
}
@@ -1170,7 +1189,9 @@
11701189
{
11711190
// If guifont is empty, and we want to fill in the orig value, suggest
11721191
// the default so the user can modify it.
1173-
if (add_match((char_u *)MMDefaultFontStr) != OK)
1192+
NSString *defaultFontStr = [NSString stringWithFormat:@"%@:h%d",
1193+
MMDefaultFontName, MMDefaultFontSize];
1194+
if (add_match((char_u *)[defaultFontStr UTF8String]) != OK)
11741195
return;
11751196
}
11761197

@@ -1185,6 +1206,27 @@
11851206
return;
11861207
}
11871208

1209+
if (!wide) {
1210+
// Add system-native monospace font alias to completion.
1211+
char buf[40];
1212+
[MMSystemFontAlias getCString:buf maxLength:ARRAY_LENGTH(buf) encoding:NSASCIIStringEncoding];
1213+
if (add_match((char_u*)buf) != OK)
1214+
return;
1215+
const size_t fontAliasLen = STRLEN(buf);
1216+
if (STRNCMP(xp->xp_pattern, buf, fontAliasLen) == 0) {
1217+
// We additionally complete with font weights like "bold". We only
1218+
// do so if starting with "-monospace-" already to avoid spamming
1219+
// the user with too many variations on this.
1220+
for (size_t i = 0; i < ARRAY_LENGTH(system_font_weights); i++) {
1221+
[system_font_weights[i] getCString:buf+fontAliasLen
1222+
maxLength:ARRAY_LENGTH(buf)-fontAliasLen
1223+
encoding:NSASCIIStringEncoding];
1224+
if (add_match((char_u*)buf) != OK)
1225+
return;
1226+
}
1227+
}
1228+
}
1229+
11881230
NSFontManager *fontManager = [NSFontManager sharedFontManager];
11891231
NSArray<NSString *> *availableFonts;
11901232
if (wide)

src/testdir/test_gui.vim

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -433,6 +433,28 @@ func Test_set_guifont()
433433
let &guifont = guifont_saved
434434
endfunc
435435

436+
func Test_set_guifont_macvim()
437+
CheckFeature gui_macvim
438+
let guifont_saved = &guifont
439+
let guifontwide_saved = &guifontwide
440+
441+
set guifont=-monospace-
442+
call assert_equal('-monospace-:h11', getfontname())
443+
set guifont=-monospace-Semibold
444+
call assert_equal('-monospace-Semibold:h11', getfontname())
445+
446+
call assert_fails('set guifont=-monospace-SemiboldInvalidWeight', 'E596')
447+
448+
set guifont=Menlo\ Regular
449+
call assert_equal('Menlo Regular:h11', getfontname())
450+
451+
set guifont=
452+
call assert_equal('Menlo-Regular:h11', getfontname())
453+
454+
let &guifontwide = guifontwide_saved
455+
let &guifont = guifont_saved
456+
endfunc
457+
436458
func Test_set_guifontset()
437459
CheckFeature xfontset
438460
let skipped = ''
@@ -641,6 +663,13 @@ func Test_expand_guifont()
641663
call assert_equal(['Menlo-Regular'], getcompletion('set guifont=Menl*lar$', 'cmdline'))
642664
call assert_equal(['Menlo-Regular'], getcompletion('set guifontwide=Menl*lar$', 'cmdline'))
643665

666+
" Test system monospace font option. It's always the first option after
667+
" the existing font.
668+
call assert_equal('-monospace-', getcompletion('set guifont=', 'cmdline')[1])
669+
call assert_equal('-monospace-', getcompletion('set guifont=-monospace-', 'cmdline')[0])
670+
call assert_equal('-monospace-UltraLight', getcompletion('set guifont=-monospace-', 'cmdline')[1])
671+
call assert_equal(['-monospace-Medium'], getcompletion('set guifont=-monospace-Med', 'cmdline'))
672+
644673
" Make sure non-monospace fonts are filtered out only in 'guifont'
645674
call assert_equal([], getcompletion('set guifont=Hel*tica$', 'cmdline'))
646675
call assert_equal(['Helvetica'], getcompletion('set guifontwide=Hel*tica$', 'cmdline'))

0 commit comments

Comments
 (0)