From 212fd5460c1c252995bb5e28571f16a3e852c699 Mon Sep 17 00:00:00 2001 From: Danny Ricciotti Date: Fri, 27 Mar 2026 10:24:17 -0400 Subject: [PATCH] Preserve font zoom level during Automatic Profile Switching When Automatic Profile Switching changes profiles, the font zoom level (set via Cmd+Plus/Cmd-Minus) was lost because setPreferencesFromAddressBookEntry: unconditionally resets the font to the profile's configured font. This computes the zoom delta before the switch and re-applies it after, with care to avoid double-applying when restoring divorced profiles from the stack (which already contain the zoomed font in their overrides). Window frame is saved and restored to prevent rounding glitches from intermediate font size changes. Controlled by a new Advanced Setting: "Preserve font zoom level when Automatic Profile Switching changes profiles" (defaults to YES). Co-Authored-By: Claude Opus 4.6 (1M context) --- sources/PTYSession.m | 38 ++++++++++++++++++++++++++++ sources/iTermAdvancedSettingsModel.h | 1 + sources/iTermAdvancedSettingsModel.m | 1 + 3 files changed, 40 insertions(+) diff --git a/sources/PTYSession.m b/sources/PTYSession.m index 2e2a0313f8..dcd381e335 100644 --- a/sources/PTYSession.m +++ b/sources/PTYSession.m @@ -18285,6 +18285,23 @@ - (NSDictionary *)automaticProfileSwitcherCurrentProfile { - (void)automaticProfileSwitcherLoadProfile:(iTermSavedProfile *)savedProfile { Profile *underlyingProfile = [[ProfileModel sharedInstance] bookmarkWithGuid:savedProfile.originalProfile[KEY_GUID]]; Profile *replacementProfile = underlyingProfile ?: savedProfile.originalProfile; + + // Compute the font zoom delta before switching profiles so we can preserve it. + // The zoom delta is the difference between the current font size and the + // profile\u2019s base font size. + CGFloat fontZoomDelta = 0; + NSRect savedWindowFrame = NSZeroRect; + NSWindow *window = [[_delegate realParentWindow] window]; + if ([iTermAdvancedSettingsModel preserveFontSizeOnAutomaticProfileSwitch]) { + iTermFontTable *originalFontTable = [iTermFontTable fontTableForProfile:_originalProfile]; + CGFloat currentPointSize = _textview.fontTable.asciiFont.font.pointSize; + CGFloat originalPointSize = originalFontTable.asciiFont.font.pointSize; + fontZoomDelta = currentPointSize - originalPointSize; + if (fontZoomDelta != 0 && window) { + savedWindowFrame = window.frame; + } + } + if (![self setProfile:replacementProfile preservingName:NO adjustWindow:NO]) { [_view showUnobtrusiveMessage:[NSString stringWithFormat:@"Can’t switch to profile “%@”—wrong profile type.", underlyingProfile[KEY_NAME]]]; return; @@ -18297,8 +18314,29 @@ - (void)automaticProfileSwitcherLoadProfile:(iTermSavedProfile *)savedProfile { } overrides[key] = savedProfile.profile[key]; } + _windowAdjustmentDisabled = YES; [self setSessionSpecificProfileValues:overrides]; + _windowAdjustmentDisabled = NO; } + + // Re-apply the font zoom delta after the profile switch, but only when + // switching to a new profile. When restoring a divorced profile from the + // stack, the saved overrides already include the zoomed font. + if (fontZoomDelta != 0 && !savedProfile.isDivorced) { + _windowAdjustmentDisabled = YES; + iTermFontTable *zoomedTable = [_textview.fontTable fontTableGrownBy:fontZoomDelta]; + [self setFontTable:zoomedTable + horizontalSpacing:_textview.horizontalSpacing + verticalSpacing:_textview.verticalSpacing]; + _windowAdjustmentDisabled = NO; + } + + // Restore the window frame to eliminate any rounding glitches from + // intermediate font size changes during the profile switch. + if (!NSIsEmptyRect(savedWindowFrame) && window) { + [window setFrame:savedWindowFrame display:YES]; + } + if ([iTermAdvancedSettingsModel showAutomaticProfileSwitchingBanner]) { [_view showUnobtrusiveMessage:[NSString stringWithFormat:@"Switched to profile “%@”.", underlyingProfile[KEY_NAME]]]; } diff --git a/sources/iTermAdvancedSettingsModel.h b/sources/iTermAdvancedSettingsModel.h index 085d85a2dc..e95717fecc 100644 --- a/sources/iTermAdvancedSettingsModel.h +++ b/sources/iTermAdvancedSettingsModel.h @@ -404,6 +404,7 @@ extern NSString *const iTermAdvancedSettingsDidChange; + (BOOL)shouldSetLCTerminal; + (BOOL)shouldSetTerminfoDirs; + (BOOL)showAutomaticProfileSwitchingBanner; ++ (BOOL)preserveFontSizeOnAutomaticProfileSwitch; + (BOOL)showBlockBoundaries; + (BOOL)showButtonsForSelectedCommand; + (BOOL)showHintsInSplitPaneMenuItems; diff --git a/sources/iTermAdvancedSettingsModel.m b/sources/iTermAdvancedSettingsModel.m index 9d5ec8fb8d..877def767b 100644 --- a/sources/iTermAdvancedSettingsModel.m +++ b/sources/iTermAdvancedSettingsModel.m @@ -621,6 +621,7 @@ + (BOOL)settingIsDeprecated:(NSString *)name { DEFINE_BOOL(focusNewSplitPaneWithFocusFollowsMouse, YES, SECTION_SESSION @"When focus follows mouse is enabled, should new split panes automatically be focused?"); DEFINE_BOOL(NoSyncSuppressRestartSessionConfirmationAlert, NO, SECTION_SESSION @"Suppress restart session confirmation alert.\nDon't ask for a confirmation when manually restarting a session."); DEFINE_BOOL(showAutomaticProfileSwitchingBanner, YES, SECTION_SESSION @"Show a “Switched to profile” message when Automatic Profile Switching activates."); +DEFINE_BOOL(preserveFontSizeOnAutomaticProfileSwitch, YES, SECTION_SESSION @"Preserve font zoom level when Automatic Profile Switching changes profiles."); DEFINE_BOOL(autoLockSessionNameOnEdit, YES, SECTION_SESSION @"Auto-lock session name after editing it."); DEFINE_FLOAT(timeoutForDaemonAttachment, 10, SECTION_SESSION @"How long to wait when trying to attach to an iTerm daemon at startup when restoring windows (in seconds)?"); DEFINE_BOOL(logTimestampsWithPlainText, YES, SECTION_SESSION @"When logging plain text, include timestamps for each line?");