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)