From 7b81cc3922680502e186b3366446c7a5d7135b1f Mon Sep 17 00:00:00 2001
From: Yee Cheng Chin <ychin.git@gmail.com>
Date: Sun, 11 Sep 2022 15:19:20 -0700
Subject: [PATCH] Allow pinning the cmdline to be aligned to the bottom of
 window

Add a setting that could pin the command-line portion of Vim to the
bottom of the MacVim window. This is useful when smooth resizing is set,
guioption+=k, or in full screen. In those situations, the MacVim window
size is usually not direct multiples of the Vim text sizes. Previously
the command-line would be drawn like other texts, and hence not aligned
to the bottom and hence looking aesthetically a little off.

When this setting is set, the command-line portion would be aligned to
the bottom of the window. This essentially moves the gap (due to the
extra height of the window) from the bottom to be between cmdline and
the rest of Vim. When cmdheight is changed, or other situations (e.g.
typing too much cmdline height to be increased), the gap will be
adjusted as well.

Implementation-wise, this was done by passing the `commandline_row` var
from Vim to MacVim, which serves as a good estimate of where the
command-line is. This works better than just using the `cmdheight`
option as it is closer to the current state of the cmdline. One issue is
that in hit-enter prompts, the row is set to the 2nd to last row to
anticipate more messages, and we just add a big hack by incrementing the
row by 1 in hit-enter state so only the "Press Enter..." part is aligned
to bottom. We also have to do something similar to when it's showing
"--more--" for similar reasons.

- An alternative would have been to modify Vim to provide us the
  information we want (the number of rows below the status line) but
  it's pretty tricky to do as cmdline_row is used in lots of places.
  It's easier / simpler to do a simple hack like this to localize the
  damage.

Close #833
---
 runtime/doc/gui_mac.txt               |   5 +-
 runtime/doc/tags                      |   1 +
 src/MacVim/Base.lproj/Preferences.xib |  42 +++++++--
 src/MacVim/MMAppController.h          |   1 +
 src/MacVim/MMAppController.m          |  17 ++++
 src/MacVim/MMBackend.m                |  20 +++++
 src/MacVim/MMCoreTextView.h           |   1 +
 src/MacVim/MMCoreTextView.m           | 122 ++++++++++++++++++++++----
 src/MacVim/MMPreferenceController.m   |   5 ++
 src/MacVim/MMTextView.h               |   3 +-
 src/MacVim/MMTextView.m               |   5 ++
 src/MacVim/Miscellaneous.h            |   1 +
 src/MacVim/Miscellaneous.m            |   2 +-
 13 files changed, 199 insertions(+), 26 deletions(-)

diff --git a/runtime/doc/gui_mac.txt b/runtime/doc/gui_mac.txt
index 5404cd3267..a9e733e341 100644
--- a/runtime/doc/gui_mac.txt
+++ b/runtime/doc/gui_mac.txt
@@ -256,12 +256,14 @@ Here is a list of relevant dictionary entries:
 
 KEY				VALUE ~
 *MMCellWidthMultiplier*		width of a normal glyph in em units [float]
+*MMCmdLineAlignBottom*		Pin command-line to bottom of MacVim [bool]
 *MMDialogsTrackPwd*		open/save dialogs track the Vim pwd [bool]
 *MMDisableLaunchAnimation*	disable launch animation when opening a new
 				MacVim window [bool]
-*MMFullScreenFadeTime*		fade delay for non-native fullscreen [float]
+*MMFontPreserveLineSpacing*	use the line-spacing as specified by font [bool]
 *MMLoginShellArgument*		login shell parameter [string]
 *MMLoginShellCommand*		which shell to use to launch Vim [string]
+*MMFullScreenFadeTime*		fade delay for non-native fullscreen [float]
 *MMNativeFullScreen*		use native full screen mode [bool]
 *MMNonNativeFullScreenShowMenu*	show menus when in non-native full screen [bool]
 *MMNonNativeFullScreenSafeAreaBehavior*
@@ -269,7 +271,6 @@ KEY				VALUE ~
 				the safe area (aka the "notch") [int]
 *MMNoFontSubstitution*		disable automatic font substitution [bool]
 				(Deprecated: Non-CoreText renderer only)
-*MMFontPreserveLineSpacing*	use the line-spacing as specified by font [bool]
 *MMNoTitleBarWindow*		hide title bar [bool]
 *MMTitlebarAppearsTransparent*	enable a transparent titlebar [bool]
 *MMAppearanceModeSelection*	dark mode selection (|macvim-dark-mode|)[bool]
diff --git a/runtime/doc/tags b/runtime/doc/tags
index 1e44be0000..24a187a2f1 100644
--- a/runtime/doc/tags
+++ b/runtime/doc/tags
@@ -5405,6 +5405,7 @@ M	motion.txt	/*M*
 MDI	starting.txt	/*MDI*
 MMAppearanceModeSelection	gui_mac.txt	/*MMAppearanceModeSelection*
 MMCellWidthMultiplier	gui_mac.txt	/*MMCellWidthMultiplier*
+MMCmdLineAlignBottom	gui_mac.txt	/*MMCmdLineAlignBottom*
 MMDialogsTrackPwd	gui_mac.txt	/*MMDialogsTrackPwd*
 MMDisableLaunchAnimation	gui_mac.txt	/*MMDisableLaunchAnimation*
 MMFontPreserveLineSpacing	gui_mac.txt	/*MMFontPreserveLineSpacing*
diff --git a/src/MacVim/Base.lproj/Preferences.xib b/src/MacVim/Base.lproj/Preferences.xib
index 3fa5badc61..6ae857cb31 100644
--- a/src/MacVim/Base.lproj/Preferences.xib
+++ b/src/MacVim/Base.lproj/Preferences.xib
@@ -257,11 +257,11 @@
             <point key="canvasLocation" x="137.5" y="21.5"/>
         </customView>
         <customView id="hr4-G4-3ZG" userLabel="Appearance">
-            <rect key="frame" x="0.0" y="0.0" width="483" height="315"/>
+            <rect key="frame" x="0.0" y="0.0" width="483" height="341"/>
             <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
             <subviews>
                 <customView id="fw0-VK-Nbz" userLabel="Dark mode selection">
-                    <rect key="frame" x="19" y="137" width="433" height="156"/>
+                    <rect key="frame" x="19" y="163" width="433" height="156"/>
                     <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
                     <subviews>
                         <textField verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" id="T40-Os-PUf" userLabel="Dark mode selection">
@@ -322,7 +322,7 @@
                     </subviews>
                 </customView>
                 <customView id="7af-iK-4r7" userLabel="Titlebar appearance">
-                    <rect key="frame" x="19" y="91" width="433" height="38"/>
+                    <rect key="frame" x="19" y="117" width="433" height="38"/>
                     <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
                     <subviews>
                         <button id="7ie-0J-0Zr">
@@ -361,7 +361,7 @@
                     </subviews>
                 </customView>
                 <customView id="BpJ-rH-ona" userLabel="Full Screen">
-                    <rect key="frame" x="19" y="45" width="433" height="38"/>
+                    <rect key="frame" x="19" y="71" width="433" height="38"/>
                     <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
                     <subviews>
                         <button toolTip="Use macOS's native full screen mode, which integrates with Mission Control and creates a new Space for the window." id="YKV-u2-Egc" userLabel="Use native full screen">
@@ -405,7 +405,7 @@
                     </subviews>
                 </customView>
                 <customView id="a3v-cB-TFa" userLabel="Font">
-                    <rect key="frame" x="19" y="20" width="433" height="18"/>
+                    <rect key="frame" x="19" y="46" width="433" height="18"/>
                     <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
                     <subviews>
                         <button id="A48-s0-kdR" userLabel="Preserve Line Spacing">
@@ -432,8 +432,36 @@
                         </textField>
                     </subviews>
                 </customView>
+                <customView id="Dey-Wx-2gx" userLabel="Cmdline">
+                    <rect key="frame" x="20" y="20" width="433" height="18"/>
+                    <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
+                    <subviews>
+                        <button id="qMh-iV-0iD">
+                            <rect key="frame" x="189" y="-1" width="244" height="18"/>
+                            <autoresizingMask key="autoresizingMask" flexibleMinY="YES"/>
+                            <string key="toolTip">When using smooth resizing, guioption+=k, or full screen; MacVim's window size can sometimes be slightly larger than Vim's content size, causing the command-line to not be aligned to the bottom. This option will make sure the command-line is always pinned to the bottom.</string>
+                            <buttonCell key="cell" type="check" title="Pin to the bottom of the window" bezelStyle="regularSquare" imagePosition="left" alignment="left" inset="2" id="uZL-IX-Dv8">
+                                <behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
+                                <font key="font" metaFont="system"/>
+                            </buttonCell>
+                            <connections>
+                                <action selector="cmdlineAlignBottomChanged:" target="-2" id="LgN-MI-0Nt"/>
+                                <binding destination="58" name="value" keyPath="values.MMCmdLineAlignBottom" id="pIr-52-5vV"/>
+                            </connections>
+                        </button>
+                        <textField verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" id="Lzq-i0-zWi" userLabel="Cmdline">
+                            <rect key="frame" x="-2" y="0.0" width="187" height="17"/>
+                            <autoresizingMask key="autoresizingMask" flexibleMinY="YES"/>
+                            <textFieldCell key="cell" sendsActionOnEndEditing="YES" alignment="right" title="Command-line:" id="2Lp-vX-AcA">
+                                <font key="font" metaFont="system"/>
+                                <color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
+                                <color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
+                            </textFieldCell>
+                        </textField>
+                    </subviews>
+                </customView>
             </subviews>
-            <point key="canvasLocation" x="137.5" y="412.5"/>
+            <point key="canvasLocation" x="137.5" y="425.5"/>
         </customView>
         <customView id="620" userLabel="Advanced">
             <rect key="frame" x="0.0" y="0.0" width="483" height="264"/>
@@ -504,7 +532,7 @@
                     </connections>
                 </button>
             </subviews>
-            <point key="canvasLocation" x="137.5" y="743"/>
+            <point key="canvasLocation" x="144" y="911"/>
         </customView>
     </objects>
 </document>
diff --git a/src/MacVim/MMAppController.h b/src/MacVim/MMAppController.h
index b776bdc20d..9786457080 100644
--- a/src/MacVim/MMAppController.h
+++ b/src/MacVim/MMAppController.h
@@ -61,6 +61,7 @@
 - (void)refreshAllAppearances;
 - (void)refreshAllFonts;
 - (void)refreshAllResizeConstraints;
+- (void)refreshAllTextViews;
 
 - (IBAction)newWindow:(id)sender;
 - (IBAction)newWindowAndActivate:(id)sender;
diff --git a/src/MacVim/MMAppController.m b/src/MacVim/MMAppController.m
index 95d89867c0..9f25be5331 100644
--- a/src/MacVim/MMAppController.m
+++ b/src/MacVim/MMAppController.m
@@ -40,6 +40,7 @@
 #import "MMAppController.h"
 #import "MMPreferenceController.h"
 #import "MMVimController.h"
+#import "MMVimView.h"
 #import "MMWindowController.h"
 #import "MMTextView.h"
 #import "Miscellaneous.h"
@@ -253,6 +254,7 @@ + (void)initialize
         [NSNumber numberWithInt:0],       MMNonNativeFullScreenSafeAreaBehaviorKey,
         [NSNumber numberWithBool:YES],    MMShareFindPboardKey,
         [NSNumber numberWithBool:NO],     MMSmoothResizeKey,
+        [NSNumber numberWithBool:NO],     MMCmdLineAlignBottomKey,
         nil];
 
     [[NSUserDefaults standardUserDefaults] registerDefaults:dict];
@@ -1124,6 +1126,7 @@ - (void)refreshAllAppearances
     }
 }
 
+/// Refresh all Vim text views' fonts.
 - (void)refreshAllFonts
 {
     unsigned count = [vimControllers count];
@@ -1133,6 +1136,8 @@ - (void)refreshAllFonts
     }
 }
 
+/// Refresh all resize constraints based on smooth resize configurations
+/// and resize the windows to match the constraints.
 - (void)refreshAllResizeConstraints
 {
     const unsigned count = [vimControllers count];
@@ -1142,6 +1147,18 @@ - (void)refreshAllResizeConstraints
     }
 }
 
+/// Refresh all text views and re-render them, as well as updating their
+/// cmdline alignment properties to make sure they are pinned properly.
+- (void)refreshAllTextViews
+{
+    unsigned count = [vimControllers count];
+    for (unsigned i = 0; i < count; ++i) {
+        MMVimController *vc = [vimControllers objectAtIndex:i];
+        [vc.windowController.vimView.textView updateCmdlineRow];
+        vc.windowController.vimView.textView.needsDisplay = YES;
+    }
+}
+
 - (IBAction)newWindow:(id)sender
 {
     ASLogDebug(@"Open new window");
diff --git a/src/MacVim/MMBackend.m b/src/MacVim/MMBackend.m
index 71735cf2ae..3c3dcaddd6 100644
--- a/src/MacVim/MMBackend.m
+++ b/src/MacVim/MMBackend.m
@@ -1816,6 +1816,25 @@ - (void)insertVimStateMessage
     if (numTabs < 0)
         numTabs = 0;
 
+    // Custom hacks to deal with cmdline_row not being perfect for our use cases.
+    int cmdline_row_adjusted = cmdline_row;
+    if (State == MODE_HITRETURN) {
+        // When we are in hit-return mode, Vim does a weird thing and sets
+        // cmdline_row to be the 2nd-to-last row, which would make pinning
+        // cmdline to bottom look weird. This is done in msg_start() and
+        // wait_return().
+        // Instead of modifying Vim, we just hack around this by manually
+        // increasing the row by one. This would make the pin happen right at
+        // the "Hit Enter..." prompt.
+        cmdline_row_adjusted++;
+    } else if (State == MODE_ASKMORE) {
+        // In "more" mode, Vim sometimes set cmdline_row, sometimes it doesn't.
+        // Silver lining is that it always only takes one row and doesn't wrap
+        // like hit-enter, so we know we can always just pin it to the last row
+        // and be done with the hack.
+        cmdline_row_adjusted = Rows - 1;
+    }
+
     NSDictionary *vimState = [NSDictionary dictionaryWithObjectsAndKeys:
         [[NSFileManager defaultManager] currentDirectoryPath], @"pwd",
         [NSNumber numberWithInt:p_mh], @"p_mh",
@@ -1823,6 +1842,7 @@ - (void)insertVimStateMessage
         [NSNumber numberWithInt:numTabs], @"numTabs",
         [NSNumber numberWithInt:fuoptions_flags], @"fullScreenOptions",
         [NSNumber numberWithLong:p_mouset], @"p_mouset",
+        [NSNumber numberWithInt:cmdline_row_adjusted], @"cmdline_row", // Used for pinning cmdline to bottom of window
         nil];
 
     // Put the state before all other messages.
diff --git a/src/MacVim/MMCoreTextView.h b/src/MacVim/MMCoreTextView.h
index 717050713b..ba31aac84f 100644
--- a/src/MacVim/MMCoreTextView.h
+++ b/src/MacVim/MMCoreTextView.h
@@ -86,6 +86,7 @@
 - (BOOL)convertPoint:(NSPoint)point toRow:(int *)row column:(int *)column;
 - (NSRect)rectForRow:(int)row column:(int)column numRows:(int)nr
           numColumns:(int)nc;
+- (void)updateCmdlineRow;
 
 //
 // NSTextView methods
diff --git a/src/MacVim/MMCoreTextView.m b/src/MacVim/MMCoreTextView.m
index 441044415c..4336d41069 100644
--- a/src/MacVim/MMCoreTextView.m
+++ b/src/MacVim/MMCoreTextView.m
@@ -76,13 +76,12 @@ - (MMVimController *)vimController;
 - (NSFont *)fontVariantForTextFlags:(int)textFlags;
 - (CTLineRef)lineForCharacterString:(NSString *)string
                           textFlags:(int)flags;
+- (void)setCmdlineRow:(int)row;
 @end
 
 
 @interface MMCoreTextView (Drawing)
 - (NSPoint)pointForRow:(int)row column:(int)column;
-- (NSRect)rectFromRow:(int)row1 column:(int)col1
-                toRow:(int)row2 column:(int)col2;
 - (NSSize)textAreaSize;
 - (void)batchDrawData:(NSData *)data;
 - (void)setString:(NSString *)string
@@ -215,6 +214,9 @@ static void grid_free(Grid *grid) {
 
 @implementation MMCoreTextView {
     Grid grid;
+
+    BOOL alignCmdLineToBottom; ///< Whether to pin the Vim command-line to the bottom of the window
+    int cmdlineRow; ///< Row number (0-indexed) where the cmdline starts. Used for pinning it to the bottom if desired.
 }
 
 - (id)initWithFrame:(NSRect)frame
@@ -242,6 +244,10 @@ - (id)initWithFrame:(NSRect)frame
             NSFilenamesPboardType, NSStringPboardType, nil]];
 
     ligatures = NO;
+
+    alignCmdLineToBottom = NO; // this would be updated to the user preferences later
+    cmdlineRow = -1; // this would be updated by Vim
+
     return self;
 }
 
@@ -328,6 +334,8 @@ - (NSRect)rectForRowsInRange:(NSRange)range
     // include the top inset as well.  (This method is only used to place the
     // scrollbars inside MMVimView.)
 
+    // Note: This doesn't really take alignCmdLineToBottom into account right now.
+
     NSRect rect = { {0, 0}, {0, 0} };
     unsigned start = range.location > maxRows ? maxRows : range.location;
     unsigned length = range.length;
@@ -545,6 +553,57 @@ - (void)setThinStrokes:(BOOL)state
     thinStrokes = state;
 }
 
+/// Update the cmdline row number from Vim's state and cmdline alignment user settings.
+- (void)updateCmdlineRow
+{
+    [self setCmdlineRow: [[[self vimController] objectForVimStateKey:@"cmdline_row"] intValue]];
+}
+
+/// Set Vim's cmdline row number. This will mark the relevant parts to be repainted
+/// if the row number has changed as we are pinning the cmdline to the bottom,
+/// because otherwise we will have a gap that doesn't get cleared and leaves artifacts.
+///
+/// @param row The row (0-indexed) of the current cmdline in Vim.
+- (void)setCmdlineRow:(int)row
+{
+    const BOOL newAlignCmdLineToBottom = [[NSUserDefaults standardUserDefaults] boolForKey:MMCmdLineAlignBottomKey];
+
+    if (newAlignCmdLineToBottom != alignCmdLineToBottom) {
+        // The user settings has changed (usually through the settings panel). Just update everything.
+        alignCmdLineToBottom = newAlignCmdLineToBottom;
+        cmdlineRow = row;
+        [self setNeedsDisplay:YES];
+        return;
+    }
+
+    if (row != cmdlineRow) {
+        // The cmdline row has changed. Need to redraw the necessary parts if we
+        // are configured to pin cmdline to the bottom.
+        if (alignCmdLineToBottom) {
+            // Since we are changing the cmdline row, we need to repaint the
+            // parts where the gap changed. Just for simplicity, we repaint
+            // both the old/new cmdline rows and the row above them. This way
+            // the gap in between the top and bottom aligned rows should be
+            // touched in the repainting and cleared to bg.
+            [self setNeedsDisplayFromRow:cmdlineRow-1
+                                  column:grid.cols
+                                   toRow:cmdlineRow
+                                  column:grid.cols];
+
+            // Have to do this between the two calls as cmdlineRow would affect
+            // the calculation in them.
+            cmdlineRow = row;
+
+            [self setNeedsDisplayFromRow:cmdlineRow-1
+                                  column:grid.cols
+                                   toRow:cmdlineRow
+                                  column:grid.cols];
+        } else {
+            cmdlineRow = row;
+        }
+    }
+}
+
 - (void)setImControl:(BOOL)enable
 {
     [helper setImControl:enable];
@@ -767,8 +826,8 @@ - (void)drawRect:(NSRect)rect
     CGContextFillRect(ctx, rect);
 
     for (size_t r = 0; r < grid.rows; r++) {
-        CGRect rowRect = [self rectForRow:r column:0 numRows:1 numColumns:grid.cols];
-        CGRect rowClipRect = CGRectIntersection(rowRect, rect);
+        const CGRect rowRect = [self rectForRow:r column:0 numRows:1 numColumns:grid.cols];
+        const CGRect rowClipRect = CGRectIntersection(rowRect, rect);
         if (CGRectIsNull(rowClipRect))
             continue;
         CGContextSaveGState(ctx);
@@ -1057,8 +1116,12 @@ - (IBAction)selectAll:(id)sender
     [[self windowController] vimMenuItemAction:sender];
 }
 
+/// Converts a point in this NSView to a specific Vim row/column.
+///
+/// @param point The point in NSView. Note that it's y-up as that's Mac convention, whereas row starts from the top.
 - (BOOL)convertPoint:(NSPoint)point toRow:(int *)row column:(int *)column
 {
+    // Convert y-up to y-down.
     point.y = [self bounds].size.height - point.y;
 
     NSPoint origin = { insetSize.width, insetSize.height };
@@ -1066,6 +1129,22 @@ - (BOOL)convertPoint:(NSPoint)point toRow:(int *)row column:(int *)column
     if (!(cellSize.width > 0 && cellSize.height > 0))
         return NO;
 
+    if (alignCmdLineToBottom) {
+        // Account for the gap we added to pin cmdline to the bottom of the window
+        const NSRect frame = [self bounds];
+        const int insetBottom = [[NSUserDefaults standardUserDefaults] integerForKey:MMTextInsetBottomKey];
+        const CGFloat gapHeight = frame.size.height - grid.rows*cellSize.height - insetSize.height - insetBottom;
+        const CGFloat cmdlineRowY = insetSize.height + cmdlineRow*cellSize.height + 1;
+        if (point.y > cmdlineRowY) {
+            point.y -= gapHeight;
+            if (point.y <= cmdlineRowY) {
+                // This was inside the gap between top and bottom lines. Round it down
+                // to the next line.
+                point.y = cmdlineRowY + 1;
+            }
+        }
+    }
+
     if (row) *row = floor((point.y-origin.y-1) / cellSize.height);
     if (column) *column = floor((point.x-origin.x-1) / cellSize.width);
 
@@ -1075,6 +1154,11 @@ - (BOOL)convertPoint:(NSPoint)point toRow:(int *)row column:(int *)column
     return YES;
 }
 
+/// Calculates the rect for the row/column range, accounting for insets. This also
+/// has additional for accounting for aligning cmdline to bottom, and filling last
+/// column to the right.
+///
+/// @return Rectangle containing the row/column range.
 - (NSRect)rectForRow:(int)row column:(int)col numRows:(int)nr
           numColumns:(int)nc
 {
@@ -1103,6 +1187,22 @@ - (NSRect)rectForRow:(int)row column:(int)col numRows:(int)nr
         rect.size.width += extraWidth;
     }
 
+    // When configured to align cmdline to bottom, need to adjust the rect with an additional gap to pin
+    // the rect to the bottom.
+    if (alignCmdLineToBottom) {
+        const int insetBottom = [[NSUserDefaults standardUserDefaults] integerForKey:MMTextInsetBottomKey];
+        const CGFloat gapHeight = frame.size.height - grid.rows*cellSize.height - insetSize.height - insetBottom;
+        if (row >= cmdlineRow) {
+            rect.origin.y -= gapHeight;
+        } else if (row + nr - 1 >= cmdlineRow) {
+            // This is an odd case where the gap between cmdline and the top-aligned content is inside
+            // the rect so we need to adjust the height as well. During rendering we draw line-by-line
+            // so this shouldn't cause any issues as we only encounter this situation when calculating
+            // the rect in setNeedsDisplayFromRow:.
+            rect.size.height += gapHeight;
+            rect.origin.y -= gapHeight;
+        }
+    }
     return rect;
 }
 
@@ -1210,17 +1310,6 @@ - (NSPoint)pointForRow:(int)row column:(int)col
             frame.size.height - (row+1)*cellSize.height - insetSize.height);
 }
 
-- (NSRect)rectFromRow:(int)row1 column:(int)col1
-                toRow:(int)row2 column:(int)col2
-{
-    NSRect frame = [self bounds];
-    return NSMakeRect(
-            insetSize.width + col1*cellSize.width,
-            frame.size.height - insetSize.height - (row2+1)*cellSize.height,
-            (col2 + 1 - col1) * cellSize.width,
-            (row2 + 1 - row1) * cellSize.height);
-}
-
 - (NSSize)textAreaSize
 {
     // Calculate the (desired) size of the text area, i.e. the text view area
@@ -1448,6 +1537,9 @@ - (void)batchDrawData:(NSData *)data
 #endif
     // TODO: Sanity check input
 
+    // Update the cmdline rows to decide if we need to update based on whether we are pinning cmdline to bottom or not.
+    [self updateCmdlineRow];
+
     while (bytes < end) {
         struct DrawCmd drawCmd;
         int type = ReadDrawCmd(&bytes, &drawCmd);
diff --git a/src/MacVim/MMPreferenceController.m b/src/MacVim/MMPreferenceController.m
index eb1ceb90a3..c28c2c5554 100644
--- a/src/MacVim/MMPreferenceController.m
+++ b/src/MacVim/MMPreferenceController.m
@@ -168,4 +168,9 @@ - (IBAction)smoothResizeChanged:(id)sender
     [[MMAppController sharedInstance] refreshAllResizeConstraints];
 }
 
+- (IBAction)cmdlineAlignBottomChanged:(id)sender
+{
+    [[MMAppController sharedInstance] refreshAllTextViews];
+}
+
 @end
diff --git a/src/MacVim/MMTextView.h b/src/MacVim/MMTextView.h
index c1387e69ea..c6430d2542 100644
--- a/src/MacVim/MMTextView.h
+++ b/src/MacVim/MMTextView.h
@@ -39,6 +39,8 @@
 - (void)setImControl:(BOOL)enable;
 - (void)activateIm:(BOOL)enable;
 - (void)checkImState;
+- (void)refreshFonts;
+- (void)updateCmdlineRow;
 
 //
 // MMTextStorage methods
@@ -47,7 +49,6 @@
 - (void)setFont:(NSFont *)newFont;
 - (NSFont *)fontWide;
 - (void)setWideFont:(NSFont *)newFont;
-- (void)refreshFonts;
 - (NSSize)cellSize;
 - (void)setLinespace:(float)newLinespace;
 - (void)setColumnspace:(float)newColumnspace;
diff --git a/src/MacVim/MMTextView.m b/src/MacVim/MMTextView.m
index d41ceac9c6..e9933f5f47 100644
--- a/src/MacVim/MMTextView.m
+++ b/src/MacVim/MMTextView.m
@@ -357,6 +357,11 @@ - (void)refreshFonts
     // Doesn't do anything. CoreText renderer only.
 }
 
+- (void)updateCmdlineRow
+{
+    // Doesn't do anything. CoreText renderer only.
+}
+
 - (NSSize)cellSize
 {
     return [(MMTextStorage*)[self textStorage] cellSize];
diff --git a/src/MacVim/Miscellaneous.h b/src/MacVim/Miscellaneous.h
index 635e46bebd..0a76dd141f 100644
--- a/src/MacVim/Miscellaneous.h
+++ b/src/MacVim/Miscellaneous.h
@@ -59,6 +59,7 @@ extern NSString *MMFullScreenFadeTimeKey;
 extern NSString *MMNonNativeFullScreenShowMenuKey;
 extern NSString *MMNonNativeFullScreenSafeAreaBehaviorKey;
 extern NSString *MMSmoothResizeKey;
+extern NSString *MMCmdLineAlignBottomKey;
 
 
 // Enum for MMUntitledWindowKey
diff --git a/src/MacVim/Miscellaneous.m b/src/MacVim/Miscellaneous.m
index b2f52edede..ecbd37bc21 100644
--- a/src/MacVim/Miscellaneous.m
+++ b/src/MacVim/Miscellaneous.m
@@ -55,7 +55,7 @@
 NSString *MMNonNativeFullScreenShowMenuKey  = @"MMNonNativeFullScreenShowMenu";
 NSString *MMNonNativeFullScreenSafeAreaBehaviorKey = @"MMNonNativeFullScreenSafeAreaBehavior";
 NSString *MMSmoothResizeKey               = @"MMSmoothResize";
-
+NSString *MMCmdLineAlignBottomKey         = @"MMCmdLineAlignBottom";
 
 
 @implementation NSIndexSet (MMExtras)