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 @@
-
+
-
+
@@ -322,7 +322,7 @@
-
+
-
+
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+
@@ -504,7 +532,7 @@
-
+
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)