diff --git a/src/MacVim/MMAppController.m b/src/MacVim/MMAppController.m index 652ca91133..25b7f1018e 100644 --- a/src/MacVim/MMAppController.m +++ b/src/MacVim/MMAppController.m @@ -7,6 +7,24 @@ * Do ":help credits" in Vim to see a list of people who contributed. * See README.txt for an overview of the Vim source code. */ +/* + * MMAppController + * + * MMAppController is the delegate of NSApp and as such handles file open + * requests, application termination, etc. It sets up a named NSConnection on + * which it listens to incoming connections from Vim processes. It also + * coordinates all MMVimControllers. + * + * A new Vim process is started by calling launchVimProcessWithArguments:. + * When the Vim process is initialized it notifies the app controller by + * sending a connectBackend:pid: message. At this point a new MMVimController + * is allocated. Afterwards, the Vim process communicates directly with its + * MMVimController. + * + * A Vim process started from the command line connects directly by sending the + * connectBackend:pid: message (launchVimProcessWithArguments: is never called + * in this case). + */ #import "MMAppController.h" #import "MMVimController.h" @@ -63,6 +81,7 @@ static NSTimeInterval MMReplyTimeout = 5; [NSNumber numberWithFloat:-1], MMBaselineOffsetKey, [NSNumber numberWithBool:YES], MMTranslateCtrlClickKey, [NSNumber numberWithBool:NO], MMOpenFilesInTabsKey, + [NSNumber numberWithBool:NO], MMNoFontSubstitutionKey, nil]; [[NSUserDefaults standardUserDefaults] registerDefaults:dict]; diff --git a/src/MacVim/MMApplication.m b/src/MacVim/MMApplication.m index 4aab703b13..a21540e282 100644 --- a/src/MacVim/MMApplication.m +++ b/src/MacVim/MMApplication.m @@ -7,6 +7,11 @@ * Do ":help credits" in Vim to see a list of people who contributed. * See README.txt for an overview of the Vim source code. */ +/* + * MMApplication + * + * Some default NSApplication key input behavior is overridden here. + */ #import "MMApplication.h" diff --git a/src/MacVim/MMBackend.h b/src/MacVim/MMBackend.h index f28c64dbf7..2f8cc74cfe 100644 --- a/src/MacVim/MMBackend.h +++ b/src/MacVim/MMBackend.h @@ -43,6 +43,7 @@ NSMutableDictionary *serverReplyDict; NSString *alternateServerName; ATSFontContainerRef fontContainerRef; + NSFont *oldWideFont; } + (MMBackend *)sharedInstance; @@ -60,8 +61,8 @@ toRow:(int)row2 column:(int)col2; - (void)deleteLinesFromRow:(int)row count:(int)count scrollBottom:(int)bottom left:(int)left right:(int)right; -- (void)replaceString:(char*)s length:(int)len row:(int)row column:(int)col - flags:(int)flags; +- (void)drawString:(char*)s length:(int)len row:(int)row column:(int)col + cells:(int)cells flags:(int)flags; - (void)insertLinesFromRow:(int)row count:(int)count scrollBottom:(int)bottom left:(int)left right:(int)right; - (void)drawCursorAtRow:(int)row column:(int)col shape:(int)shape @@ -96,7 +97,8 @@ - (void)setScrollbarPosition:(int)pos length:(int)len identifier:(long)ident; - (void)setScrollbarThumbValue:(long)val size:(long)size max:(long)max identifier:(long)ident; -- (BOOL)setFontWithName:(char *)name; +- (void)setFont:(NSFont *)font; +- (void)setWideFont:(NSFont *)font; - (void)executeActionWithName:(NSString *)name; - (void)setMouseShape:(int)shape; - (void)setBlinkWait:(int)wait on:(int)on off:(int)off; diff --git a/src/MacVim/MMBackend.m b/src/MacVim/MMBackend.m index 4d77f760d8..9dcf64ed93 100644 --- a/src/MacVim/MMBackend.m +++ b/src/MacVim/MMBackend.m @@ -7,6 +7,25 @@ * Do ":help credits" in Vim to see a list of people who contributed. * See README.txt for an overview of the Vim source code. */ +/* + * MMBackend + * + * MMBackend communicates with the frontend (MacVim). It maintains a queue of + * output which is flushed to the frontend under controlled circumstances (so + * as to maintain a steady framerate). Input from the frontend is also handled + * here. + * + * The frontend communicates with the backend via the MMBackendProtocol. In + * particular, input is sent to the backend via processInput:data: and Vim + * state can be queried from the frontend with evaluateExpression:. + * + * It is very important to realize that all state is held by the backend, the + * frontend must either ask for state [MMBackend evaluateExpression:] or wait + * for the backend to update [MMVimController processCommandQueue:]. + * + * The client/server functionality of Vim is handled by the backend. It sets + * up a named NSConnection to which other Vim processes can connect. + */ #import "MMBackend.h" @@ -30,10 +49,6 @@ static int MMFlushQueueLenHint = 80*40; static unsigned MMServerMax = 1000; -// NOTE: The default font is bundled with the application. -static NSString *MMDefaultFontName = @"DejaVu Sans Mono"; -static float MMDefaultFontSize = 12.0f; - // TODO: Move to separate file. static int eventModifierFlagsToVimModMask(int modifierFlags); static int vimModMaskToEventModifierFlags(int mods); @@ -134,6 +149,7 @@ enum { //NSLog(@"%@ %s", [self className], _cmd); [[NSNotificationCenter defaultCenter] removeObserver:self]; + [oldWideFont release]; oldWideFont = nil; [blinkTimer release]; blinkTimer = nil; [alternateServerName release]; alternateServerName = nil; [serverReplyDict release]; serverReplyDict = nil; @@ -319,12 +335,12 @@ enum { [drawData appendBytes:&right length:sizeof(int)]; } -- (void)replaceString:(char*)s length:(int)len row:(int)row column:(int)col - flags:(int)flags +- (void)drawString:(char*)s length:(int)len row:(int)row column:(int)col + cells:(int)cells flags:(int)flags { - if (len <= 0) return; + if (len <= 0 || cells <= 0) return; - int type = ReplaceStringDrawType; + int type = DrawStringDrawType; [drawData appendBytes:&type length:sizeof(int)]; @@ -333,6 +349,7 @@ enum { [drawData appendBytes:&specialColor length:sizeof(unsigned)]; [drawData appendBytes:&row length:sizeof(int)]; [drawData appendBytes:&col length:sizeof(int)]; + [drawData appendBytes:&cells length:sizeof(int)]; [drawData appendBytes:&flags length:sizeof(int)]; [drawData appendBytes:&len length:sizeof(int)]; [drawData appendBytes:s length:len]; @@ -392,6 +409,13 @@ enum { return; if ([drawData length] > 0) { + // HACK! Detect changes to 'guifontwide'. + if (gui.wide_font != (GuiFont)oldWideFont) { + [oldWideFont release]; + oldWideFont = [(NSFont*)gui.wide_font retain]; + [self setWideFont:oldWideFont]; + } + [self queueMessage:BatchDrawMsgID data:[drawData copy]]; [drawData setLength:0]; } @@ -818,81 +842,35 @@ enum { [self queueMessage:SetScrollbarThumbMsgID data:data]; } -- (BOOL)setFontWithName:(char *)name +- (void)setFont:(NSFont *)font { - NSString *fontName = MMDefaultFontName; - float size = MMDefaultFontSize; - BOOL parseFailed = NO; + NSString *fontName = [font displayName]; + float size = [font pointSize]; + int len = [fontName lengthOfBytesUsingEncoding:NSUTF8StringEncoding]; + if (len > 0) { + NSMutableData *data = [NSMutableData data]; - if (name) { - fontName = [NSString stringWithUTF8String:name]; + [data appendBytes:&size length:sizeof(float)]; + [data appendBytes:&len length:sizeof(int)]; + [data appendBytes:[fontName UTF8String] length:len]; - if ([fontName isEqual:@"*"]) { - // :set gfn=* shows the font panel. - do_cmdline_cmd((char_u*)":macaction orderFrontFontPanel:"); - return NO; - } - - NSArray *components = [fontName componentsSeparatedByString:@":"]; - if ([components count] == 2) { - NSString *sizeString = [components lastObject]; - if ([sizeString length] > 0 - && [sizeString characterAtIndex:0] == 'h') { - sizeString = [sizeString substringFromIndex:1]; - if ([sizeString length] > 0) { - size = [sizeString floatValue]; - fontName = [components objectAtIndex:0]; - } - } else { - parseFailed = YES; - } - } else if ([components count] > 2) { - parseFailed = YES; - } - - if (!parseFailed) { - // Replace underscores with spaces. - fontName = [[fontName componentsSeparatedByString:@"_"] - componentsJoinedByString:@" "]; - } + [self queueMessage:SetFontMsgID data:data]; } +} - if (!parseFailed && [fontName length] > 0) { - if (size < 6 || size > 100) { - // Font size 0.0 tells NSFont to use the 'user default size'. - size = 0.0f; - } +- (void)setWideFont:(NSFont *)font +{ + NSString *fontName = [font displayName]; + float size = [font pointSize]; + int len = [fontName lengthOfBytesUsingEncoding:NSUTF8StringEncoding]; + NSMutableData *data = [NSMutableData data]; - NSFont *font = [NSFont fontWithName:fontName size:size]; + [data appendBytes:&size length:sizeof(float)]; + [data appendBytes:&len length:sizeof(int)]; + if (len > 0) + [data appendBytes:[fontName UTF8String] length:len]; - if (!font && MMDefaultFontName == fontName) { - // If for some reason the MacVim default font is not in the app - // bundle, then fall back on the system default font. - size = 0; - font = [NSFont userFixedPitchFontOfSize:size]; - fontName = [font displayName]; - } - - if (font) { - //NSLog(@"Setting font '%@' of size %.2f", fontName, size); - int len = [fontName - lengthOfBytesUsingEncoding:NSUTF8StringEncoding]; - if (len > 0) { - NSMutableData *data = [NSMutableData data]; - - [data appendBytes:&size length:sizeof(float)]; - [data appendBytes:&len length:sizeof(int)]; - [data appendBytes:[fontName UTF8String] length:len]; - - [self queueMessage:SetFontMsgID data:data]; - return YES; - } - } - } - - //NSLog(@"WARNING: Cannot set font with name '%@' of size %.2f", - // fontName, size); - return NO; + [self queueMessage:SetWideFontMsgID data:data]; } - (void)executeActionWithName:(NSString *)name diff --git a/src/MacVim/MMFullscreenWindow.m b/src/MacVim/MMFullscreenWindow.m index 9ebc2d5965..360ff3b994 100644 --- a/src/MacVim/MMFullscreenWindow.m +++ b/src/MacVim/MMFullscreenWindow.m @@ -7,6 +7,11 @@ * Do ":help credits" in Vim to see a list of people who contributed. * See README.txt for an overview of the Vim source code. */ +/* + * MMFullscreen + * + * Support for full-screen editing. + */ #import "MMFullscreenWindow.h" #import diff --git a/src/MacVim/MMTextStorage.h b/src/MacVim/MMTextStorage.h index 50f3e6d10b..9cc03104e0 100644 --- a/src/MacVim/MMTextStorage.h +++ b/src/MacVim/MMTextStorage.h @@ -11,6 +11,17 @@ #import +#define MM_USE_ROW_CACHE 1 + + +#if MM_USE_ROW_CACHE +typedef struct { + unsigned length; // length of row in unichars + int col; // last column accessed (in this row) + unsigned colOffset; // offset of 'col' from start of row (in unichars) +} MMRowCacheEntry; +#endif + @interface MMTextStorage : NSTextStorage { @@ -22,15 +33,25 @@ NSFont *boldFont; NSFont *italicFont; NSFont *boldItalicFont; + NSFont *fontWide; + NSFont *boldFontWide; + NSFont *italicFontWide; + NSFont *boldItalicFontWide; NSColor *defaultBackgroundColor; NSColor *defaultForegroundColor; NSSize cellSize; float linespace; +#if MM_USE_ROW_CACHE + MMRowCacheEntry *rowCache; +#endif + BOOL characterEqualsColumn; } - (NSString *)string; - (NSDictionary *)attributesAtIndex:(unsigned)index effectiveRange:(NSRangePointer)aRange; +- (id)attribute:(NSString *)attrib atIndex:(unsigned)index + effectiveRange:(NSRangePointer)range; - (void)replaceCharactersInRange:(NSRange)aRange withString:(NSString *)aString; - (void)setAttributes:(NSDictionary *)attributes range:(NSRange)aRange; @@ -43,9 +64,10 @@ - (void)setLinespace:(float)newLinespace; - (void)getMaxRows:(int*)rows columns:(int*)cols; - (void)setMaxRows:(int)rows columns:(int)cols; -- (void)replaceString:(NSString *)string atRow:(int)row column:(int)col - withFlags:(int)flags foregroundColor:(NSColor *)fg - backgroundColor:(NSColor *)bg specialColor:(NSColor *)sp; +- (void)drawString:(NSString *)string atRow:(int)row column:(int)col + cells:(int)cells withFlags:(int)flags + foregroundColor:(NSColor *)fg backgroundColor:(NSColor *)bg + specialColor:(NSColor *)sp; - (void)deleteLinesFromRow:(int)row lineCount:(int)count scrollBottom:(int)bottom left:(int)left right:(int)right color:(NSColor *)color; @@ -58,6 +80,7 @@ - (void)setDefaultColorsBackground:(NSColor *)bgColor foreground:(NSColor *)fgColor; - (void)setFont:(NSFont *)newFont; +- (void)setWideFont:(NSFont *)newFont; - (NSFont *)font; - (NSColor *)defaultBackgroundColor; - (NSColor *)defaultForegroundColor; @@ -69,5 +92,9 @@ - (BOOL)resizeToFitSize:(NSSize)size; - (NSSize)fitToSize:(NSSize)size; - (NSSize)fitToSize:(NSSize)size rows:(int *)rows columns:(int *)columns; +- (NSRect)boundingRectForCharacterAtRow:(int)row column:(int)col; +#if MM_USE_ROW_CACHE +- (MMRowCacheEntry *)rowCache; +#endif @end diff --git a/src/MacVim/MMTextStorage.m b/src/MacVim/MMTextStorage.m index 5cd014d463..336d51839c 100644 --- a/src/MacVim/MMTextStorage.m +++ b/src/MacVim/MMTextStorage.m @@ -7,6 +7,11 @@ * Do ":help credits" in Vim to see a list of people who contributed. * See README.txt for an overview of the Vim source code. */ +/* + * MMTextStorage + * + * Text rendering related code. + */ #import "MMTextStorage.h" #import "MacVim.h" @@ -22,12 +27,18 @@ #define DRAW_UNDERL 0x04 /* draw underline text */ #define DRAW_UNDERC 0x08 /* draw undercurl text */ #define DRAW_ITALIC 0x10 /* draw italic text */ +#define DRAW_CURSOR 0x20 + + +static NSString *MMWideCharacterAttributeName = @"MMWideChar"; @interface MMTextStorage (Private) - (void)lazyResize:(BOOL)force; +- (NSRange)charRangeForRow:(int)row column:(int)col cells:(int)cells; +- (void)fixInvalidCharactersInRange:(NSRange)range; @end @@ -41,9 +52,6 @@ // NOTE! It does not matter which font is set here, Vim will set its // own font on startup anyway. Just set some bogus values. font = [[NSFont userFixedPitchFontOfSize:0] retain]; - boldFont = [font retain]; - italicFont = [font retain]; - boldItalicFont = [font retain]; cellSize.height = [font pointSize]; cellSize.width = [font defaultLineHeightForFont]; } @@ -53,9 +61,17 @@ - (void)dealloc { - //NSLog(@"%@ %s", [self className], _cmd); - +#if MM_USE_ROW_CACHE + if (rowCache) { + free(rowCache); + rowCache = NULL; + } +#endif [emptyRowString release]; + [boldItalicFontWide release]; + [italicFontWide release]; + [boldFontWide release]; + [fontWide release]; [boldItalicFont release]; [italicFont release]; [boldFont release]; @@ -68,30 +84,31 @@ - (NSString *)string { - //NSLog(@"%s : attribString=%@", _cmd, attribString); return [attribString string]; } - (NSDictionary *)attributesAtIndex:(unsigned)index effectiveRange:(NSRangePointer)range { - //NSLog(@"%s", _cmd); - if (index>=[attribString length]) { - //NSLog(@"%sWARNING: index (%d) out of bounds", _cmd, index); - if (range) { + if (index >= [attribString length]) { + if (range) *range = NSMakeRange(NSNotFound, 0); - } + return [NSDictionary dictionary]; } return [attribString attributesAtIndex:index effectiveRange:range]; } +- (id)attribute:(NSString *)attrib atIndex:(unsigned)index + effectiveRange:(NSRangePointer)range +{ + return [attribString attribute:attrib atIndex:index effectiveRange:range]; +} + - (void)replaceCharactersInRange:(NSRange)range withString:(NSString *)string { - //NSLog(@"replaceCharactersInRange:(%d,%d) withString:%@", range.location, - // range.length, string); NSLog(@"WARNING: calling %s on MMTextStorage is unsupported", _cmd); //[attribString replaceCharactersInRange:range withString:string]; } @@ -102,22 +119,28 @@ // constantly to 'fix attributes', apply font substitution, etc. #if 0 [attribString setAttributes:attributes range:range]; -#else +#elif 1 // HACK! If the font attribute is being modified, then ensure that the new // font has a fixed advancement which is either the same as the current // font or twice that, depending on whether it is a 'wide' character that - // is being fixed or not. This code really only works if 'range' has - // length 1 or 2. + // is being fixed or not. + // + // TODO: This code assumes that the characters in 'range' all have the same + // width. NSFont *newFont = [attributes objectForKey:NSFontAttributeName]; if (newFont) { + // Allow disabling of font substitution via a user default. Not + // recommended since the typesetter hides the corresponding glyphs and + // the display gets messed up. + if ([[NSUserDefaults standardUserDefaults] + boolForKey:MMNoFontSubstitutionKey]) + return; + float adv = cellSize.width; - if ([attribString length] > range.location+1) { - // If the first char is followed by zero-width space, then it is a - // 'wide' character, so double the advancement. - NSString *string = [attribString string]; - if ([string characterAtIndex:range.location+1] == 0x200b) - adv += adv; - } + if ([attribString attribute:MMWideCharacterAttributeName + atIndex:range.location + effectiveRange:NULL]) + adv += adv; // Create a new font which has the 'fixed advance attribute' set. NSDictionary *dict = [NSDictionary dictionaryWithObjectsAndKeys: @@ -192,9 +215,10 @@ maxColumns = cols; } -- (void)replaceString:(NSString *)string atRow:(int)row column:(int)col - withFlags:(int)flags foregroundColor:(NSColor *)fg - backgroundColor:(NSColor *)bg specialColor:(NSColor *)sp +- (void)drawString:(NSString *)string atRow:(int)row column:(int)col + cells:(int)cells withFlags:(int)flags + foregroundColor:(NSColor *)fg backgroundColor:(NSColor *)bg + specialColor:(NSColor *)sp { //NSLog(@"replaceString:atRow:%d column:%d withFlags:%d " // "foreground:%@ background:%@ special:%@", @@ -202,59 +226,75 @@ [self lazyResize:NO]; if (row < 0 || row >= maxRows || col < 0 || col >= maxColumns - || col+[string length] > maxColumns) { - //NSLog(@"[%s] WARNING : out of range, row=%d (%d) col=%d (%d) " - // "length=%d (%d)", _cmd, row, maxRows, col, maxColumns, - // [string length], [attribString length]); + || col+cells > maxColumns || !string || !(fg && bg && sp)) + return; + + // Find range of characters in text storage to replace. + NSRange range = [self charRangeForRow:row column:col cells:cells]; + if (NSMaxRange(range) > [[attribString string] length]) { + NSLog(@"%s Out of bounds"); return; } - // NOTE: If 'string' was initialized with bad data it might be nil; this - // may be due to 'enc' being set to an unsupported value, so don't print an - // error message or stdout will most likely get flooded. - if (!string) return; - - if (!(fg && bg && sp)) { - NSLog(@"[%s] WARNING: background, foreground or special color not " - "specified", _cmd); - return; - } - - NSRange range = NSMakeRange(col+row*(maxColumns+1), [string length]); - [attribString replaceCharactersInRange:range withString:string]; - + // Create dictionary of attributes to apply to the new characters. NSFont *theFont = font; - if (flags & DRAW_BOLD) - theFont = flags & DRAW_ITALIC ? boldItalicFont : boldFont; - else if (flags & DRAW_ITALIC) - theFont = italicFont; + if (flags & DRAW_WIDE) { + if (flags & DRAW_BOLD) + theFont = flags & DRAW_ITALIC ? boldItalicFontWide : boldFontWide; + else if (flags & DRAW_ITALIC) + theFont = italicFontWide; + else + theFont = fontWide; + } else { + if (flags & DRAW_BOLD) + theFont = flags & DRAW_ITALIC ? boldItalicFont : boldFont; + else if (flags & DRAW_ITALIC) + theFont = italicFont; + } - NSDictionary *attributes = [NSDictionary dictionaryWithObjectsAndKeys: + NSMutableDictionary *attributes = + [NSMutableDictionary dictionaryWithObjectsAndKeys: theFont, NSFontAttributeName, bg, NSBackgroundColorAttributeName, fg, NSForegroundColorAttributeName, sp, NSUnderlineColorAttributeName, nil]; - [attribString setAttributes:attributes range:range]; - if (flags & DRAW_UNDERL) { NSNumber *value = [NSNumber numberWithInt:(NSUnderlineStyleSingle | NSUnderlinePatternSolid)]; // | NSUnderlineByWordMask - [attribString addAttribute:NSUnderlineStyleAttributeName - value:value range:range]; + [attributes setObject:value forKey:NSUnderlineStyleAttributeName]; } - // TODO: figure out how do draw proper undercurls if (flags & DRAW_UNDERC) { + // TODO: figure out how do draw proper undercurls NSNumber *value = [NSNumber numberWithInt:(NSUnderlineStyleThick | NSUnderlinePatternDot)]; // | NSUnderlineByWordMask - [attribString addAttribute:NSUnderlineStyleAttributeName - value:value range:range]; + [attributes setObject:value forKey:NSUnderlineStyleAttributeName]; } + // Mark these characters as wide. This attribute is subsequently checked + // when translating (row,col) pairs to offsets within 'attribString'. + if (flags & DRAW_WIDE) + [attributes setObject:[NSNull null] + forKey:MMWideCharacterAttributeName]; + + // Replace characters in text storage and apply new attributes. + NSRange r = NSMakeRange(range.location, [string length]); + [attribString replaceCharactersInRange:range withString:string]; + [attribString setAttributes:attributes range:r]; + + if ((flags & DRAW_WIDE) || [string length] != cells) + characterEqualsColumn = NO; + + [self fixInvalidCharactersInRange:r]; + [self edited:(NSTextStorageEditedCharacters|NSTextStorageEditedAttributes) - range:range changeInLength:0]; + range:range changeInLength:[string length]-range.length]; + +#if MM_USE_ROW_CACHE + rowCache[row].length += [string length] - range.length; +#endif } /* @@ -268,67 +308,56 @@ //NSLog(@"deleteLinesFromRow:%d lineCount:%d color:%@", row, count, color); [self lazyResize:NO]; - if (row < 0 || row+count > maxRows) { - //NSLog(@"[%s] WARNING : out of range, row=%d (%d) count=%d", _cmd, row, - // maxRows, count); + if (row < 0 || row+count > maxRows) return; - } int total = 1 + bottom - row; int move = total - count; int width = right - left + 1; - NSRange destRange = { row*(maxColumns+1) + left, width }; - NSRange srcRange = { (row+count)*(maxColumns+1) + left, width }; + int destRow = row; + NSRange destRange, srcRange; int i; - if (width != maxColumns) { // if this is the case, then left must be 0 - for (i = 0; i < move; ++i) { - NSAttributedString *srcString = [attribString - attributedSubstringFromRange:srcRange]; - [attribString replaceCharactersInRange:destRange - withAttributedString:srcString]; - [self edited:(NSTextStorageEditedCharacters - | NSTextStorageEditedAttributes) - range:destRange changeInLength:0]; - destRange.location += maxColumns+1; - srcRange.location += maxColumns+1; - } - - NSRange emptyRange = {0,width}; - NSAttributedString *emptyString = - [emptyRowString attributedSubstringFromRange: emptyRange]; - NSDictionary *attribs = [NSDictionary dictionaryWithObjectsAndKeys: - font, NSFontAttributeName, - color, NSBackgroundColorAttributeName, nil]; + for (i = 0; i < move; ++i, ++destRow) { + destRange = [self charRangeForRow:destRow column:left cells:width]; + srcRange = [self charRangeForRow:(destRow+count) column:left + cells:width]; + NSAttributedString *srcString = [attribString + attributedSubstringFromRange:srcRange]; + + [attribString replaceCharactersInRange:destRange + withAttributedString:srcString]; + [self edited:(NSTextStorageEditedCharacters + | NSTextStorageEditedAttributes) range:destRange + changeInLength:([srcString length]-destRange.length)]; + +#if MM_USE_ROW_CACHE + rowCache[destRow].length += [srcString length] - destRange.length; +#endif + } + + NSRange emptyRange = {0,width}; + NSAttributedString *emptyString = + [emptyRowString attributedSubstringFromRange:emptyRange]; + NSDictionary *attribs = [NSDictionary dictionaryWithObjectsAndKeys: + font, NSFontAttributeName, + color, NSBackgroundColorAttributeName, nil]; + + for (i = 0; i < count; ++i, ++destRow) { + destRange = [self charRangeForRow:destRow column:left cells:width]; + + [attribString replaceCharactersInRange:destRange + withAttributedString:emptyString]; + [attribString setAttributes:attribs + range:NSMakeRange(destRange.location, width)]; - for (i = 0; i < count; ++i) { - [attribString replaceCharactersInRange:destRange - withAttributedString:emptyString]; - [attribString setAttributes:attribs range:destRange]; - [self edited:(NSTextStorageEditedAttributes - | NSTextStorageEditedCharacters) range:destRange - changeInLength:0]; - destRange.location += maxColumns+1; - } - } else { - NSRange delRange = {row*(maxColumns+1), count*(maxColumns+1)}; - [attribString deleteCharactersInRange: delRange]; - destRange.location += move*(maxColumns+1); - - NSDictionary *attribs = [NSDictionary dictionaryWithObjectsAndKeys: - font, NSFontAttributeName, - color, NSBackgroundColorAttributeName, nil]; - destRange.length = maxColumns; - for (i = 0; i < count; ++i) { - [attribString insertAttributedString:emptyRowString - atIndex:destRange.location]; - [attribString setAttributes:attribs range:destRange]; - destRange.location += maxColumns+1; - } - NSRange editedRange = {row*(maxColumns+1),total*(maxColumns+1)}; [self edited:(NSTextStorageEditedAttributes - | NSTextStorageEditedCharacters) range:editedRange - changeInLength:0]; + | NSTextStorageEditedCharacters) range:destRange + changeInLength:([emptyString length]-destRange.length)]; + +#if MM_USE_ROW_CACHE + rowCache[destRow].length += [emptyString length] - destRange.length; +#endif } } @@ -343,66 +372,55 @@ //NSLog(@"insertLinesAtRow:%d lineCount:%d color:%@", row, count, color); [self lazyResize:NO]; - if (row < 0 || row+count > maxRows) { - //NSLog(@"[%s] WARNING : out of range, row=%d (%d) count=%d", _cmd, row, - // maxRows, count); + if (row < 0 || row+count > maxRows) return; - } int total = 1 + bottom - row; int move = total - count; int width = right - left + 1; - NSRange destRange = { bottom*(maxColumns+1) + left, width }; - NSRange srcRange = { (row+move-1)*(maxColumns+1) + left, width }; + int destRow = bottom; + int srcRow = row + move - 1; + NSRange destRange, srcRange; int i; - if (width != maxColumns) { // if this is the case, then left must be 0 - for (i = 0; i < move; ++i) { - NSAttributedString *srcString = [attribString - attributedSubstringFromRange:srcRange]; - [attribString replaceCharactersInRange:destRange - withAttributedString:srcString]; - [self edited:(NSTextStorageEditedCharacters - | NSTextStorageEditedAttributes) - range:destRange changeInLength:0]; - destRange.location -= maxColumns+1; - srcRange.location -= maxColumns+1; - } - - NSRange emptyRange = {0,width}; - NSAttributedString *emptyString = - [emptyRowString attributedSubstringFromRange:emptyRange]; - NSDictionary *attribs = [NSDictionary dictionaryWithObjectsAndKeys: - font, NSFontAttributeName, - color, NSBackgroundColorAttributeName, nil]; - - for (i = 0; i < count; ++i) { - [attribString replaceCharactersInRange:destRange - withAttributedString:emptyString]; - [attribString setAttributes:attribs range:destRange]; - [self edited:(NSTextStorageEditedAttributes - | NSTextStorageEditedCharacters) range:destRange - changeInLength:0]; - destRange.location -= maxColumns+1; - } - } else { - NSRange delRange = {(row+move)*(maxColumns+1),count*(maxColumns+1)}; - [attribString deleteCharactersInRange: delRange]; + for (i = 0; i < move; ++i, --destRow, --srcRow) { + destRange = [self charRangeForRow:destRow column:left cells:width]; + srcRange = [self charRangeForRow:srcRow column:left cells:width]; + NSAttributedString *srcString = [attribString + attributedSubstringFromRange:srcRange]; + [attribString replaceCharactersInRange:destRange + withAttributedString:srcString]; + [self edited:(NSTextStorageEditedCharacters + | NSTextStorageEditedAttributes) range:destRange + changeInLength:([srcString length]-destRange.length)]; + +#if MM_USE_ROW_CACHE + rowCache[destRow].length += [srcString length] - destRange.length; +#endif + } + + NSRange emptyRange = {0,width}; + NSAttributedString *emptyString = + [emptyRowString attributedSubstringFromRange:emptyRange]; + NSDictionary *attribs = [NSDictionary dictionaryWithObjectsAndKeys: + font, NSFontAttributeName, + color, NSBackgroundColorAttributeName, nil]; + + for (i = 0; i < count; ++i, --destRow) { + destRange = [self charRangeForRow:destRow column:left cells:width]; + + [attribString replaceCharactersInRange:destRange + withAttributedString:emptyString]; + [attribString setAttributes:attribs + range:NSMakeRange(destRange.location, width)]; - NSDictionary *attribs = [NSDictionary dictionaryWithObjectsAndKeys: - font, NSFontAttributeName, - color, NSBackgroundColorAttributeName, nil]; - - destRange.location = row*(maxColumns+1); - for (i = 0; i < count; ++i) { - [attribString insertAttributedString:emptyRowString - atIndex:destRange.location]; - [attribString setAttributes:attribs range:destRange]; - } - NSRange editedRange = {row*(maxColumns+1),total*(maxColumns+1)}; [self edited:(NSTextStorageEditedAttributes - | NSTextStorageEditedCharacters) range:editedRange - changeInLength:0]; + | NSTextStorageEditedCharacters) range:destRange + changeInLength:([emptyString length]-destRange.length)]; + +#if MM_USE_ROW_CACHE + rowCache[destRow].length += [emptyString length] - destRange.length; +#endif } } @@ -413,45 +431,45 @@ // row1, col1, row2, col2, color); [self lazyResize:NO]; - if (row1 < 0 || row2 >= maxRows || col1 < 0 || col2 > maxColumns) { - //NSLog(@"[%s] WARNING : out of range, row1=%d row2=%d (%d) col1=%d " - // "col2=%d (%d)", _cmd, row1, row2, maxRows, col1, col2, - // maxColumns); + if (row1 < 0 || row2 >= maxRows || col1 < 0 || col2 > maxColumns) return; - } NSDictionary *attribs = [NSDictionary dictionaryWithObjectsAndKeys: font, NSFontAttributeName, color, NSBackgroundColorAttributeName, nil]; - - NSRange range = { row1*(maxColumns+1) + col1, col2-col1+1 }; - - NSRange emptyRange = {0,col2-col1+1}; + int cells = col2 - col1 + 1; + NSRange range, emptyRange = {0, cells}; NSAttributedString *emptyString = [emptyRowString attributedSubstringFromRange:emptyRange]; int r; + for (r=row1; r<=row2; ++r) { + range = [self charRangeForRow:r column:col1 cells:cells]; + [attribString replaceCharactersInRange:range withAttributedString:emptyString]; - [attribString setAttributes:attribs range:range]; + [attribString setAttributes:attribs + range:NSMakeRange(range.location, cells)]; + [self edited:(NSTextStorageEditedAttributes | NSTextStorageEditedCharacters) range:range - changeInLength:0]; - range.location += maxColumns+1; + changeInLength:cells-range.length]; + +#if MM_USE_ROW_CACHE + rowCache[r].length += cells - range.length; +#endif } } - (void)clearAll { - //NSLog(@"%s%@", _cmd, color); + //NSLog(@"%s", _cmd); [self lazyResize:YES]; } - (void)setDefaultColorsBackground:(NSColor *)bgColor foreground:(NSColor *)fgColor { - //NSLog(@"setDefaultColorsBackground:%@ foreground:%@", bgColor, fgColor); - if (defaultBackgroundColor != bgColor) { [defaultBackgroundColor release]; defaultBackgroundColor = bgColor ? [bgColor retain] : nil; @@ -469,10 +487,13 @@ - (void)setFont:(NSFont*)newFont { if (newFont && font != newFont) { + [boldItalicFont release]; + [italicFont release]; + [boldFont release]; [font release]; // NOTE! When setting a new font we make sure that the advancement of - // each glyph is fixed. + // each glyph is fixed. float em = [newFont widthOfString:@"m"]; float cellWidthMultiplier = [[NSUserDefaults standardUserDefaults] @@ -524,6 +545,51 @@ } } +- (void)setWideFont:(NSFont *)newFont +{ + if (!newFont) { + // Use the normal font as the wide font (note that the normal font may + // very well include wide characters.) + if (font) [self setWideFont:font]; + } else if (newFont != fontWide) { + [boldItalicFontWide release]; + [italicFontWide release]; + [boldFontWide release]; + [fontWide release]; + + float pointSize = [newFont pointSize]; + NSFontDescriptor *desc = [newFont fontDescriptor]; + NSDictionary *dictWide = [NSDictionary + dictionaryWithObject:[NSNumber numberWithFloat:2*cellSize.width] + forKey:NSFontFixedAdvanceAttribute]; + + desc = [desc fontDescriptorByAddingAttributes:dictWide]; + fontWide = [NSFont fontWithDescriptor:desc size:pointSize]; + [fontWide retain]; + + boldFontWide = [[NSFontManager sharedFontManager] + convertFont:fontWide toHaveTrait:NSBoldFontMask]; + desc = [boldFontWide fontDescriptor]; + desc = [desc fontDescriptorByAddingAttributes:dictWide]; + boldFontWide = [NSFont fontWithDescriptor:desc size:pointSize]; + [boldFontWide retain]; + + italicFontWide = [[NSFontManager sharedFontManager] + convertFont:fontWide toHaveTrait:NSItalicFontMask]; + desc = [italicFontWide fontDescriptor]; + desc = [desc fontDescriptorByAddingAttributes:dictWide]; + italicFontWide = [NSFont fontWithDescriptor:desc size:pointSize]; + [italicFontWide retain]; + + boldItalicFontWide = [[NSFontManager sharedFontManager] + convertFont:italicFontWide toHaveTrait:NSBoldFontMask]; + desc = [boldItalicFontWide fontDescriptor]; + desc = [desc fontDescriptorByAddingAttributes:dictWide]; + boldItalicFontWide = [NSFont fontWithDescriptor:desc size:pointSize]; + [boldItalicFontWide retain]; + } +} + - (NSFont*)font { return font; @@ -581,14 +647,8 @@ - (unsigned)characterIndexForRow:(int)row column:(int)col { - // Ensure the offset returned is valid. - // This code also works if maxRows and/or maxColumns is 0. - if (row >= maxRows) row = maxRows-1; - if (row < 0) row = 0; - if (col >= maxColumns) col = maxColumns-1; - if (col < 0) col = 0; - - return (unsigned)(col + row*(maxColumns+1)); + NSRange range = [self charRangeForRow:row column:col cells:1]; + return range.location != NSNotFound ? range.location : 0; } // XXX: unused at the moment @@ -661,24 +721,71 @@ return fitSize; } +- (NSRect)boundingRectForCharacterAtRow:(int)row column:(int)col +{ +#if 1 + // This properly computes the position of where Vim expects the glyph to be + // drawn. Had the typesetter actually computed the right position of each + // character and not hidden some, this code would be correct. + NSRect rect = NSZeroRect; + + rect.origin.x = col*cellSize.width; + rect.origin.y = row*cellSize.height; + rect.size = cellSize; + + // Wide character take up twice the width of a normal character. + NSRange r = [self charRangeForRow:row column:col cells:1]; + if (NSNotFound != r.location + && [attribString attribute:MMWideCharacterAttributeName + atIndex:r.location + effectiveRange:nil]) + rect.size.width += rect.size.width; + + return rect; +#else + // Use layout manager to compute bounding rect. This works in situations + // where the layout manager decides to hide glyphs (Vim assumes all glyphs + // are drawn). + NSLayoutManager *lm = [[self layoutManagers] objectAtIndex:0]; + NSTextContainer *tc = [[lm textContainers] objectAtIndex:0]; + NSRange range = [self charRangeForRow:row column:col cells:1]; + NSRange glyphRange = [lm glyphRangeForCharacterRange:range + actualCharacterRange:NULL]; + + return [lm boundingRectForGlyphRange:glyphRange inTextContainer:tc]; +#endif +} + +#if MM_USE_ROW_CACHE +- (MMRowCacheEntry *)rowCache +{ + return rowCache; +} +#endif + @end // MMTextStorage @implementation MMTextStorage (Private) + - (void)lazyResize:(BOOL)force { - int i; - // Do nothing if the dimensions are already right. if (!force && actualRows == maxRows && actualColumns == maxColumns) return; - NSRange oldRange = NSMakeRange(0, actualRows*(actualColumns+1)); + NSRange oldRange = NSMakeRange(0, [attribString length]); actualRows = maxRows; actualColumns = maxColumns; + characterEqualsColumn = YES; + +#if MM_USE_ROW_CACHE + free(rowCache); + rowCache = (MMRowCacheEntry*)calloc(actualRows, sizeof(MMRowCacheEntry)); +#endif NSDictionary *dict; if (defaultBackgroundColor) { @@ -691,6 +798,7 @@ } NSMutableString *rowString = [NSMutableString string]; + int i; for (i = 0; i < maxColumns; ++i) { [rowString appendString:@" "]; } @@ -703,6 +811,9 @@ [attribString release]; attribString = [[NSMutableAttributedString alloc] init]; for (i=0; i 16 bit characters), then we can compute the range. + if (characterEqualsColumn) + return NSMakeRange(row*(actualColumns+1) + col, cells); + + NSString *string = [attribString string]; + NSRange r, range = { NSNotFound, 0 }; + unsigned idx; + int i; + + if (row < 0 || row >= actualRows || col < 0 || col >= actualColumns + || col+cells > actualColumns) { + NSLog(@"%s row=%d col=%d cells=%d is out of range (length=%d)", + _cmd, row, col, cells, [string length]); + return range; + } + +#if MM_USE_ROW_CACHE + // Locate the beginning of the row + MMRowCacheEntry *cache = rowCache; + idx = 0; + for (i = 0; i < row; ++i, ++cache) + idx += cache->length; +#else + // Locate the beginning of the row by scanning for EOL characters. + r.location = 0; + for (i = 0; i < row; ++i) { + r.length = [string length] - r.location; + r = [string rangeOfString:@"\n" options:NSLiteralSearch range:r]; + if (NSNotFound == r.location) + return range; + ++r.location; + } +#endif + + // Locate the column +#if MM_USE_ROW_CACHE + cache = &rowCache[row]; + + i = cache->col; + if (col == i) { + // Cache hit + idx += cache->colOffset; + } else { + range.location = idx; + + // Cache miss + if (col < i - col) { + // Search forward from beginning of line. + i = 0; + } else if (actualColumns - col < col - i) { + // Search backward from end of line. + i = actualColumns - 1; + idx += cache->length - 2; + } else { + // Search from cache spot (forward or backward). + idx += cache->colOffset; + } + + if (col > i) { + // Forward search + while (col > i) { + r = [string rangeOfComposedCharacterSequenceAtIndex:idx]; + + // Wide chars take up two display cells. + if ([attribString attribute:MMWideCharacterAttributeName + atIndex:idx + effectiveRange:nil]) + ++i; + + idx += r.length; + ++i; + } + } else if (col < i) { + // Backward search + while (col < i) { + r = [string rangeOfComposedCharacterSequenceAtIndex:idx-1]; + idx -= r.length; + --i; + + // Wide chars take up two display cells. + if ([attribString attribute:MMWideCharacterAttributeName + atIndex:idx + effectiveRange:nil]) + --i; + } + } + + cache->col = i; + cache->colOffset = idx - range.location; + } +#else + idx = r.location; + for (i = 0; i < col; ++i) { + r = [string rangeOfComposedCharacterSequenceAtIndex:idx]; + + // Wide chars take up two display cells. + if ([attribString attribute:MMWideCharacterAttributeName + atIndex:idx + effectiveRange:nil]) + ++i; + + idx += r.length; + } +#endif + + // Count the number of characters that cover the cells. + range.location = idx; + for (i = 0; i < cells; ++i) { + r = [string rangeOfComposedCharacterSequenceAtIndex:idx]; + + // Wide chars take up two display cells. + if ([attribString attribute:MMWideCharacterAttributeName + atIndex:idx + effectiveRange:nil]) + ++i; + + idx += r.length; + range.length += r.length; + } + + return range; +} + +- (void)fixInvalidCharactersInRange:(NSRange)range +{ + static NSCharacterSet *invalidCharacterSet = nil; + NSRange invalidRange; + unsigned end; + + if (!invalidCharacterSet) + invalidCharacterSet = [[NSCharacterSet characterSetWithRange: + NSMakeRange(0x2028, 2)] retain]; + + // HACK! Replace characters that the text system can't handle (currently + // LINE SEPARATOR U+2028 and PARAGRAPH SEPARATOR U+2029) with space. + // + // TODO: Treat these separately inside of Vim so we don't have to bother + // here. + while (range.length > 0) { + invalidRange = [[attribString string] + rangeOfCharacterFromSet:invalidCharacterSet + options:NSLiteralSearch + range:range]; + if (NSNotFound == invalidRange.location) + break; + + [attribString replaceCharactersInRange:invalidRange withString:@" "]; + + end = NSMaxRange(invalidRange); + range.length -= end - range.location; + range.location = end; + } +} + @end // MMTextStorage (Private) diff --git a/src/MacVim/MMTextView.m b/src/MacVim/MMTextView.m index 0e2399d72e..d7e7ba2794 100644 --- a/src/MacVim/MMTextView.m +++ b/src/MacVim/MMTextView.m @@ -7,6 +7,11 @@ * Do ":help credits" in Vim to see a list of people who contributed. * See README.txt for an overview of the Vim source code. */ +/* + * MMTextView + * + * Dispatches keyboard and mouse input to the backend. Also handles drag&drop. + */ #import "MMTextView.h" #import "MMTextStorage.h" @@ -111,20 +116,9 @@ static NSString *MMKeypadEnterString = @"KA"; if (shouldDrawInsertionPoint) { MMTextStorage *ts = (MMTextStorage*)[self textStorage]; - NSLayoutManager *lm = [self layoutManager]; - NSTextContainer *tc = [self textContainer]; - // Given (row,column), calculate the bounds of the glyph at that spot. - // We use the layout manager because this gives us exactly the size and - // location of the glyph so that we can match the insertion point to - // it. - unsigned charIdx = [ts characterIndexForRow:insertionPointRow - column:insertionPointColumn]; - NSRange glyphRange = - [lm glyphRangeForCharacterRange:NSMakeRange(charIdx,1) - actualCharacterRange:NULL]; - NSRect ipRect = [lm boundingRectForGlyphRange:glyphRange - inTextContainer:tc]; + NSRect ipRect = [ts boundingRectForCharacterAtRow:insertionPointRow + column:insertionPointColumn]; ipRect.origin.x += [self textContainerOrigin].x; ipRect.origin.y += [self textContainerOrigin].y; diff --git a/src/MacVim/MMTypesetter.h b/src/MacVim/MMTypesetter.h index c2d149a16a..e63911cdcb 100644 --- a/src/MacVim/MMTypesetter.h +++ b/src/MacVim/MMTypesetter.h @@ -13,10 +13,8 @@ @interface MMTypesetter : NSATSTypesetter { } - -- (void)layoutGlyphsInLayoutManager:(NSLayoutManager *)lm - startingAtGlyphIndex:(unsigned)startGlyphIdx - maxNumberOfLineFragments:(unsigned)maxNumLines - nextGlyphIndex:(unsigned *)nextGlyph; - +@end + +@interface MMTypesetter2 : NSATSTypesetter { +} @end diff --git a/src/MacVim/MMTypesetter.m b/src/MacVim/MMTypesetter.m index 56ee878f95..44166cb37a 100644 --- a/src/MacVim/MMTypesetter.m +++ b/src/MacVim/MMTypesetter.m @@ -7,28 +7,90 @@ * Do ":help credits" in Vim to see a list of people who contributed. * See README.txt for an overview of the Vim source code. */ +/* + * MMTypesetter + * + * Ensures that each line has a fixed height and deals with some baseline + * issues. + */ #import "MMTypesetter.h" #import "MMTextStorage.h" #import "MacVim.h" -// The 'linerange' functions count U+2028 and U+2029 as line end characters, -// which causes rendering to be screwed up because Vim does not count them as -// line end characters. -#define MM_USE_LINERANGE 0 - - -#if 0 -@interface MMTypesetter (Private) -- (NSCharacterSet *)hiddenCharSet; -@end -#endif - @implementation MMTypesetter +- (void)willSetLineFragmentRect:(NSRectPointer)lineRect + forGlyphRange:(NSRange)glyphRange + usedRect:(NSRectPointer)usedRect + baselineOffset:(float *)baselineOffset +{ + MMTextStorage *ts = (MMTextStorage*)[[self layoutManager] textStorage]; + float h = [ts cellSize].height; + + // HACK! Force each line fragment rect to have a fixed height. By also + // forcing the 'usedRect' to the same height we also ensure that the cursor + // is as high as the line itself. + lineRect->size.height = h; + usedRect->size.height = h; + + // See [MMTextStorage setLinespace:] for info on how 'linespace' support + // works. + *baselineOffset += floor(.5*[ts linespace]); +} + +#if 0 +- (void)setNotShownAttribute:(BOOL)flag forGlyphRange:(NSRange)glyphRange +{ + if (1 != glyphRange.length) + return; + + NSLayoutManager *lm = [self layoutManager]; + unsigned charIdx = [lm characterIndexForGlyphAtIndex:glyphRange.location]; + + if ('\n' == [[[lm textStorage] string] characterAtIndex:charIdx]) + [lm setNotShownAttribute:flag forGlyphAtIndex:glyphRange.location]; +} +#endif + +#if 0 +- (NSTypesetterControlCharacterAction) + actionForControlCharacterAtIndex:(unsigned)charIndex +{ + //NSLog(@"%s%d", _cmd, charIndex); + /*NSTextStorage *ts = [[self layoutManager] textStorage]; + + if ('\n' == [[ts string] characterAtIndex:charIndex]) + return NSTypesetterLineBreakAction;*/ + + return NSTypesetterWhitespaceAction; +} +#endif + +#if 0 +- (void)setLocation:(NSPoint)location + withAdvancements:(const float *)advancements + forStartOfGlyphRange:(NSRange)glyphRange +{ + NSLog(@"setLocation:%@ withAdvancements:%f forStartOfGlyphRange:%@", + NSStringFromPoint(location), advancements ? *advancements : 0, + NSStringFromRange(glyphRange)); + [super setLocation:location withAdvancements:advancements + forStartOfGlyphRange:glyphRange]; +} +#endif + + +@end // MMTypesetter + + + + +@implementation MMTypesetter2 + // // Layout glyphs so that each line fragment has a fixed size. // @@ -37,126 +99,92 @@ // depending on whether it is a wide character or not). This is taken care of // by MMTextStorage in setAttributes:range: and in setFont:. All that is left // for the typesetter to do is to make sure each line fragment has the same -// height and that unwanted glyphs are hidden. +// height and that EOL glyphs are hidden. // - (void)layoutGlyphsInLayoutManager:(NSLayoutManager *)lm startingAtGlyphIndex:(unsigned)startGlyphIdx maxNumberOfLineFragments:(unsigned)maxNumLines nextGlyphIndex:(unsigned *)nextGlyph { - // TODO: Check that it really is an MMTextStorage. + // TODO: Check that it really is an MMTextStorage? MMTextStorage *ts = (MMTextStorage*)[lm textStorage]; - NSTextView *tv = [lm firstTextView]; - NSTextContainer *tc = [tv textContainer]; + NSTextContainer *tc = [[lm firstTextView] textContainer]; NSFont *font = [ts font]; NSString *text = [ts string]; - unsigned textLen = [text length]; - NSSize cellSize = [ts cellSize]; - // NOTE: With non-zero linespace the baseline is adjusted so that the text - // is centered within a line. - float baseline = [font descender] - floor(.5*[ts linespace]); - if (!(lm && ts && tv && tc && font && text && textLen + if (!(lm && ts && tc && font && text && [lm isValidGlyphIndex:startGlyphIdx])) return; - float baselineOffset = [[NSUserDefaults standardUserDefaults] - floatForKey:MMBaselineOffsetKey]; - - baseline += baselineOffset; - + // Note that we always start laying out lines from the beginning of a line, + // even if 'startCharIdx' may be somewhere in the middle. unsigned startCharIdx = [lm characterIndexForGlyphAtIndex:startGlyphIdx]; - unsigned i, numberOfLines = 0, firstLine = 0; - NSRange firstLineRange = { 0, 0 }; + if (startCharIdx >= [text length]) + return; -#if MM_USE_LINERANGE - // Find the first line and its range, and count the number of lines. (This - // info could also be gleaned from MMTextStorage, but we do it here anyway - // to make absolutely sure everything is right.) - for (i = 0; i < textLen; numberOfLines++) { - NSRange lineRange = [text lineRangeForRange:NSMakeRange(i, 0)]; - if (NSLocationInRange(startCharIdx, lineRange)) { - firstLine = numberOfLines; - firstLineRange = lineRange; - } + [lm setTextContainer:tc forGlyphRange: + [lm glyphRangeForCharacterRange:NSMakeRange(0, [text length]) + actualCharacterRange:nil]]; - i = NSMaxRange(lineRange); + // + // STEP 1: Locate the line containing 'startCharIdx'. + // + MMRowCacheEntry *cache = [ts rowCache]; + unsigned lineIdx = 0, nextLineIdx = 0; + int actualRows = [ts actualRows]; + int line = 0; + + for (; line < actualRows; ++line, ++cache) { + lineIdx = nextLineIdx; + nextLineIdx += cache->length; + if (startCharIdx < nextLineIdx) + break; } -#else - unsigned stride = 1 + [ts actualColumns]; - numberOfLines = [ts actualRows]; - firstLine = (unsigned)(startCharIdx/stride); - firstLineRange.location = firstLine * stride; - unsigned len = [text length] - firstLineRange.location; - firstLineRange.length = len < stride ? len : stride; -#endif - // Perform line fragment generation one line at a time. - NSRange lineRange = firstLineRange; - unsigned endGlyphIdx = startGlyphIdx; - for (i = 0; i < maxNumLines && lineRange.length; ++i) { - NSRange glyphRange = [lm glyphRangeForCharacterRange:lineRange - actualCharacterRange:nil]; - NSRect lineRect = { 0, (firstLine+i)*cellSize.height, - cellSize.width*(lineRange.length-1), cellSize.height }; - unsigned endLineIdx = NSMaxRange(lineRange); - NSPoint glyphPt = { 0, cellSize.height+baseline }; - unsigned j; + // + // STEP 2: Generate line fragment rects one line at a time until there are + // no more lines in the text storage, or until 'maxNumLines' have been + // exhausted. (There is no point in just laying out one line, the layout + // manager will keep calling this method until there are no more lines in + // the text storage.) + // - endGlyphIdx = NSMaxRange(glyphRange); + // NOTE: With non-zero linespace the baseline is adjusted so that the text + // is centered within a line. + float baseline = [font descender] - floor(.5*[ts linespace]) + + [[NSUserDefaults standardUserDefaults] + floatForKey:MMBaselineOffsetKey]; + NSSize cellSize = [ts cellSize]; + NSPoint glyphPt = { 0, cellSize.height+baseline }; + + NSRange lineRange = { lineIdx, 0 }; + NSRange glyphRange = { startGlyphIdx, 0 }; + NSRect lineRect = { 0, line*cellSize.height, + [ts actualColumns]*cellSize.width, cellSize.height }; + int endLine = line + maxNumLines; + if (endLine > actualRows) + endLine = actualRows; + + for (; line < endLine; ++line, ++cache) { + lineRange.length = cache->length; + + glyphRange = [lm glyphRangeForCharacterRange:lineRange + actualCharacterRange:nil]; - [lm setTextContainer:tc forGlyphRange:glyphRange]; [lm setLineFragmentRect:lineRect forGlyphRange:glyphRange usedRect:lineRect]; [lm setLocation:glyphPt forStartOfGlyphRange:glyphRange]; - // Hide end-of-line and non-zero space characters (there is one after - // every wide character). - for (j = lineRange.location; j < endLineIdx; ++j) { - unichar ch = [text characterAtIndex:j]; - if (ch == 0x200b || ch == '\n') { - NSRange range = { j, 1 }; - range = [lm glyphRangeForCharacterRange:range - actualCharacterRange:nil]; - [lm setNotShownAttribute:YES forGlyphAtIndex:range.location]; - } - } + lineRange.location += lineRange.length; + lineRect.origin.y += cellSize.height; -#if MM_USE_LINERANGE - lineRange = [text lineRangeForRange:NSMakeRange(endLineIdx, 0)]; -#else - lineRange.location = endLineIdx; - len = [text length] - lineRange.location; - if (len < lineRange.length) - lineRange.length = len; -#endif + // Hide EOL character (otherwise a square will be rendered). + [lm setNotShownAttribute:YES forGlyphAtIndex:lineRange.location-1]; } if (nextGlyph) - *nextGlyph = endGlyphIdx; + *nextGlyph = NSMaxRange(glyphRange); } -@end // MMTypesetter +@end // MMTypesetter2 - - - -#if 0 -@implementation MMTypesetter (Private) - -- (NSCharacterSet *)hiddenCharSet -{ - static NSCharacterSet *hiddenCharSet = nil; - - if (!hiddenCharSet) { - NSString *string = [NSString stringWithFormat:@"%C\n", 0x200b]; - hiddenCharSet = [NSCharacterSet - characterSetWithCharactersInString:string]; - [hiddenCharSet retain]; - } - - return hiddenCharSet; -} - -@end // MMTypesetter (Private) -#endif diff --git a/src/MacVim/MMVimController.h b/src/MacVim/MMVimController.h index e53e5fdf00..78ca65843f 100644 --- a/src/MacVim/MMVimController.h +++ b/src/MacVim/MMVimController.h @@ -56,5 +56,3 @@ timeout:(NSTimeInterval)timeout; - (void)addVimInput:(NSString *)string; @end - -// vim: set ft=objc: diff --git a/src/MacVim/MMVimController.m b/src/MacVim/MMVimController.m index b06b78b7e3..b2aa95b4a1 100644 --- a/src/MacVim/MMVimController.m +++ b/src/MacVim/MMVimController.m @@ -7,6 +7,21 @@ * Do ":help credits" in Vim to see a list of people who contributed. * See README.txt for an overview of the Vim source code. */ +/* + * MMVimController + * + * Coordinates input/output to/from backend. Each MMBackend communicates + * directly with a MMVimController. + * + * MMVimController does not deal with visual presentation. Essentially it + * should be able to run with no window present. + * + * Output from the backend is received in processCommandQueue:. Input is sent + * to the backend via sendMessage:data: or addVimInput:. The latter allows + * execution of arbitrary stings in the Vim process, much like the Vim script + * function remote_send() does. The messages that may be passed between + * frontend and backend are defined in an enum in MacVim.h. + */ #import "MMVimController.h" #import "MMWindowController.h" @@ -716,6 +731,21 @@ static NSTimeInterval MMResendInterval = 0.5; [windowController setFont:font]; [name release]; + } else if (SetWideFontMsgID == msgid) { + const void *bytes = [data bytes]; + float size = *((float*)bytes); bytes += sizeof(float); + int len = *((int*)bytes); bytes += sizeof(int); + if (len > 0) { + NSString *name = [[NSString alloc] + initWithBytes:(void*)bytes length:len + encoding:NSUTF8StringEncoding]; + NSFont *font = [NSFont fontWithName:name size:size]; + [windowController setWideFont:font]; + + [name release]; + } else { + [windowController setWideFont:nil]; + } } else if (SetDefaultColorsMsgID == msgid) { const void *bytes = [data bytes]; unsigned bg = *((unsigned*)bytes); bytes += sizeof(unsigned); @@ -846,12 +876,13 @@ static NSTimeInterval MMResendInterval = 0.5; [textStorage deleteLinesFromRow:row lineCount:count scrollBottom:bot left:left right:right color:[NSColor colorWithArgbInt:color]]; - } else if (ReplaceStringDrawType == type) { + } else if (DrawStringDrawType == type) { int bg = *((int*)bytes); bytes += sizeof(int); int fg = *((int*)bytes); bytes += sizeof(int); int sp = *((int*)bytes); bytes += sizeof(int); int row = *((int*)bytes); bytes += sizeof(int); int col = *((int*)bytes); bytes += sizeof(int); + int cells = *((int*)bytes); bytes += sizeof(int); int flags = *((int*)bytes); bytes += sizeof(int); int len = *((int*)bytes); bytes += sizeof(int); NSString *string = [[NSString alloc] @@ -876,12 +907,13 @@ static NSTimeInterval MMResendInterval = 0.5; // shape:MMInsertionPointBlock // color:color]; } - [textStorage replaceString:string - atRow:row column:col - withFlags:flags - foregroundColor:[NSColor colorWithRgbInt:fg] - backgroundColor:[NSColor colorWithArgbInt:bg] - specialColor:[NSColor colorWithRgbInt:sp]]; + + [textStorage drawString:string + atRow:row column:col cells:cells + withFlags:flags + foregroundColor:[NSColor colorWithRgbInt:fg] + backgroundColor:[NSColor colorWithArgbInt:bg] + specialColor:[NSColor colorWithRgbInt:sp]]; [string release]; } else if (InsertLinesDrawType == type) { diff --git a/src/MacVim/MMVimView.m b/src/MacVim/MMVimView.m index d5a1fb1018..dca6f3cd19 100644 --- a/src/MacVim/MMVimView.m +++ b/src/MacVim/MMVimView.m @@ -7,6 +7,11 @@ * Do ":help credits" in Vim to see a list of people who contributed. * See README.txt for an overview of the Vim source code. */ +/* + * MMVimView + * + * A view class with a tabline, scrollbars, and text view. + */ #import "MMVimView.h" @@ -86,8 +91,12 @@ enum { NSString *typesetterString = [[NSUserDefaults standardUserDefaults] stringForKey:MMTypesetterKey]; - if (![typesetterString isEqual:@"NSTypesetter"]) { - MMTypesetter *typesetter = [[MMTypesetter alloc] init]; + if ([typesetterString isEqual:@"MMTypesetter"]) { + NSTypesetter *typesetter = [[MMTypesetter alloc] init]; + [lm setTypesetter:typesetter]; + [typesetter release]; + } else if ([typesetterString isEqual:@"MMTypesetter2"]) { + NSTypesetter *typesetter = [[MMTypesetter2 alloc] init]; [lm setTypesetter:typesetter]; [typesetter release]; } else { @@ -96,6 +105,10 @@ enum { setFloat:1.0 forKey:MMCellWidthMultiplierKey]; } + // The characters in the text storage are in display order, so disable + // bidirectional text processing (this call is 10.4 only). + [[lm typesetter] setBidiProcessingEnabled:NO]; + [tc setWidthTracksTextView:NO]; [tc setHeightTracksTextView:NO]; [tc setLineFragmentPadding:0]; diff --git a/src/MacVim/MMWindowController.h b/src/MacVim/MMWindowController.h index 782bc5889d..c73bb777f6 100644 --- a/src/MacVim/MMWindowController.h +++ b/src/MacVim/MMWindowController.h @@ -49,6 +49,7 @@ identifier:(long)ident; - (void)setDefaultColorsBackground:(NSColor *)back foreground:(NSColor *)fore; - (void)setFont:(NSFont *)font; +- (void)setWideFont:(NSFont *)font; - (void)processCommandQueueDidFinish; - (void)popupMenu:(NSMenu *)menu atRow:(int)row column:(int)col; - (void)showTabBar:(BOOL)on; diff --git a/src/MacVim/MMWindowController.m b/src/MacVim/MMWindowController.m index 5a5740fac3..9da117808a 100644 --- a/src/MacVim/MMWindowController.m +++ b/src/MacVim/MMWindowController.m @@ -7,6 +7,12 @@ * Do ":help credits" in Vim to see a list of people who contributed. * See README.txt for an overview of the Vim source code. */ +/* + * MMWindowController + * + * Handles resizing of windows, acts as an mediator between MMVimView and + * MMVimController. + */ #import "MMWindowController.h" #import @@ -261,6 +267,11 @@ NSMutableArray *buildMenuAddress(NSMenu *menu) [self updateResizeIncrements]; } +- (void)setWideFont:(NSFont *)font +{ + [[vimView textStorage] setWideFont:font]; +} + - (void)processCommandQueueDidFinish { // XXX: If not in live resize and vimview's desired size differs from actual diff --git a/src/MacVim/MacVim.h b/src/MacVim/MacVim.h index e497c30ac4..6d610d12c5 100644 --- a/src/MacVim/MacVim.h +++ b/src/MacVim/MacVim.h @@ -136,6 +136,7 @@ enum { SetScrollbarThumbMsgID, ScrollbarEventMsgID, SetFontMsgID, + SetWideFontMsgID, VimShouldCloseMsgID, SetDefaultColorsMsgID, ExecuteActionMsgID, @@ -157,11 +158,13 @@ enum { }; +#define DRAW_WIDE 0x40 /* draw wide text */ + enum { ClearAllDrawType = 1, ClearBlockDrawType, DeleteLinesDrawType, - ReplaceStringDrawType, + DrawStringDrawType, InsertLinesDrawType, DrawCursorDrawType }; @@ -206,6 +209,7 @@ extern NSString *MMBaselineOffsetKey; extern NSString *MMTranslateCtrlClickKey; extern NSString *MMTopLeftPointKey; extern NSString *MMOpenFilesInTabsKey; +extern NSString *MMNoFontSubstitutionKey; @@ -220,6 +224,3 @@ ATSFontContainerRef loadFonts(); @interface NSString (MMExtras) - (NSString *)stringByEscapingSpecialFilenameCharacters; @end - - -// vim: set ft=objc: diff --git a/src/MacVim/MacVim.m b/src/MacVim/MacVim.m index 23e903259b..33afe4c5e7 100644 --- a/src/MacVim/MacVim.m +++ b/src/MacVim/MacVim.m @@ -7,6 +7,9 @@ * Do ":help credits" in Vim to see a list of people who contributed. * See README.txt for an overview of the Vim source code. */ +/* + * MacVim.m: Code shared between Vim and MacVim. + */ #import "MacVim.h" @@ -46,6 +49,7 @@ char *MessageStrings[] = "SetScrollbarThumbMsgID", "ScrollbarEventMsgID", "SetFontMsgID", + "SetWideFontMsgID", "VimShouldCloseMsgID", "SetDefaultColorsMsgID", "ExecuteActionMsgID", @@ -86,6 +90,7 @@ NSString *MMBaselineOffsetKey = @"MMBaselineOffset"; NSString *MMTranslateCtrlClickKey = @"MMTranslateCtrlClick"; NSString *MMTopLeftPointKey = @"MMTopLeftPoint"; NSString *MMOpenFilesInTabsKey = @"MMOpenFilesInTabs"; +NSString *MMNoFontSubstitutionKey = @"MMNoFontSubstitution"; diff --git a/src/MacVim/gui_macvim.m b/src/MacVim/gui_macvim.m index 245730494e..58ecde86b8 100644 --- a/src/MacVim/gui_macvim.m +++ b/src/MacVim/gui_macvim.m @@ -7,6 +7,11 @@ * Do ":help credits" in Vim to see a list of people who contributed. * See README.txt for an overview of the Vim source code. */ +/* + * gui_macvim.m + * + * Hooks for the Vim gui code. Mainly passes control on to MMBackend. + */ #import #import "MMBackend.h" @@ -19,7 +24,14 @@ // gui_mch_update()). static NSTimeInterval MMUpdateTimeoutInterval = 0.1f; +// NOTE: The default font is bundled with the application. +static NSString *MMDefaultFontName = @"DejaVu Sans Mono"; +static float MMDefaultFontSize = 12.0f; +static float MMMinFontSize = 6.0f; +static float MMMaxFontSize = 100.0f; + +static NSFont *gui_macvim_font_with_name(char_u *name); static BOOL gui_macvim_is_valid_action(NSString *action); @@ -259,8 +271,8 @@ gui_mch_draw_string(int row, int col, char_u *s, int len, int flags) } #endif - [[MMBackend sharedInstance] replaceString:(char*)s length:len - row:row column:col flags:flags]; + [[MMBackend sharedInstance] drawString:(char*)s length:len row:row + column:col cells:len flags:flags]; #ifdef FEAT_MBYTE if (conv_str) @@ -272,22 +284,12 @@ gui_mch_draw_string(int row, int col, char_u *s, int len, int flags) int gui_macvim_draw_string(int row, int col, char_u *s, int len, int flags) { - // - // Output chars until a wide char found. If a wide char is found, output a - // zero-width space after it so that a wide char looks like two chars to - // MMTextStorage. This way 1 char corresponds to 1 column. - // - - int c; - int cn; - int cl; - int i; + int c, cn, cl, i; int start = 0; int endcol = col; int startcol = col; - BOOL outPad = NO; + BOOL wide = NO; MMBackend *backend = [MMBackend sharedInstance]; - static char ZeroWidthSpace[] = { 0xe2, 0x80, 0x8b }; #ifdef FEAT_MBYTE char_u *conv_str = NULL; @@ -298,74 +300,34 @@ gui_macvim_draw_string(int row, int col, char_u *s, int len, int flags) } #endif + // Loop over each character and output text when it changes from normal to + // wide and vice versa. for (i = 0; i < len; i += cl) { c = utf_ptr2char(s + i); cl = utf_ptr2len(s + i); cn = utf_char2cells(c); if (!utf_iscomposing(c)) { - if (outPad) { - outPad = NO; -#if 0 - NSString *string = [[NSString alloc] - initWithBytesNoCopy:(void*)(s+start) - length:i-start - encoding:NSUTF8StringEncoding - freeWhenDone:NO]; - NSLog(@"Flushing string=%@ len=%d row=%d col=%d end=%d", - string, i-start, row, startcol, endcol); - [string release]; -#endif - [backend replaceString:(char*)(s+start) length:i-start - row:row column:startcol flags:flags]; + if ((cn > 1 && !wide) || (cn <= 1 && wide)) { + // Changed from normal to wide or vice versa. + [backend drawString:(char*)(s+start) length:i-start + row:row column:startcol + cells:endcol-startcol + flags:(wide ? flags|DRAW_WIDE : flags)]; + start = i; startcol = endcol; -#if 0 - NSLog(@"Padding len=%d row=%d col=%d", sizeof(ZeroWidthSpace), - row, endcol-1); -#endif - [backend replaceString:ZeroWidthSpace - length:sizeof(ZeroWidthSpace) - row:row column:endcol-1 flags:flags]; } + wide = cn > 1; endcol += cn; } - - if (cn > 1) { -#if 0 - NSLog(@"Wide char detected! (char=%C hex=%x cells=%d)", c, c, cn); -#endif - outPad = YES; - } } -#if 0 - if (row < 1) { - NSString *string = [[NSString alloc] - initWithBytesNoCopy:(void*)(s+start) - length:len-start - encoding:NSUTF8StringEncoding - freeWhenDone:NO]; - NSLog(@"Output string=%@ len=%d row=%d col=%d", string, len-start, row, - startcol); - [string release]; - } -#endif - // Output remaining characters. - [backend replaceString:(char*)(s+start) length:len-start - row:row column:startcol flags:flags]; - - if (outPad) { -#if 0 - NSLog(@"Padding len=%d row=%d col=%d", sizeof(ZeroWidthSpace), row, - endcol-1); -#endif - [backend replaceString:ZeroWidthSpace - length:sizeof(ZeroWidthSpace) - row:row column:endcol-1 flags:flags]; - } + [backend drawString:(char*)(s+start) length:len-start + row:row column:startcol cells:endcol-startcol + flags:(wide ? flags|DRAW_WIDE : flags)]; #ifdef FEAT_MBYTE if (conv_str) @@ -829,6 +791,10 @@ gui_mch_show_toolbar(int showit) gui_mch_free_font(font) GuiFont font; { + if (font != NOFONT) { + //NSLog(@"gui_mch_free_font(font=0x%x)", font); + [(NSFont*)font release]; + } } @@ -840,7 +806,15 @@ gui_mch_get_font(char_u *name, int giveErrorIfMissing) { //NSLog(@"gui_mch_get_font(name=%s, giveErrorIfMissing=%d)", name, // giveErrorIfMissing); - return 0; + + NSFont *font = gui_macvim_font_with_name(name); + if (font) + return (GuiFont)[font retain]; + + if (giveErrorIfMissing) + EMSG2(_(e_font), name); + + return NOFONT; } @@ -852,8 +826,9 @@ gui_mch_get_font(char_u *name, int giveErrorIfMissing) char_u * gui_mch_get_fontname(GuiFont font, char_u *name) { - //NSLog(@"gui_mch_get_fontname(font=%d, name=%s)", font, name); - return 0; + if (name == NULL) + return NULL; + return vim_strsave(name); } #endif @@ -867,21 +842,30 @@ gui_mch_init_font(char_u *font_name, int fontset) { //NSLog(@"gui_mch_init_font(font_name=%s, fontset=%d)", font_name, fontset); - // HACK! This gets called whenever the user types :set gfn=fontname, so - // for now we set the font here. - // TODO! Proper font handling, the way Vim expects it. + if (font_name && STRCMP(font_name, "*") == 0) { + // :set gfn=* shows the font panel. + do_cmdline_cmd((char_u*)":macaction orderFrontFontPanel:"); + return FAIL; + } -#ifdef FEAT_MBYTE - font_name = CONVERT_TO_UTF8(font_name); -#endif + NSFont *font = gui_macvim_font_with_name(font_name); + if (font) { + [(NSFont*)gui.norm_font release]; + gui.norm_font = (GuiFont)font; - BOOL ok = [[MMBackend sharedInstance] setFontWithName:(char*)font_name]; + // NOTE: MacVim keeps separate track of the normal and wide fonts. + // Unless the user changes 'guifontwide' manually, they are based on + // the same (normal) font. Also note that each time the normal font is + // set, the advancement may change so the wide font needs to be updated + // as well (so that it is always twice the width of the normal font). + [[MMBackend sharedInstance] setFont:font]; + [[MMBackend sharedInstance] setWideFont: + (NOFONT == gui.wide_font ? font : (NSFont*)gui.wide_font)]; -#ifdef FEAT_MBYTE - CONVERT_TO_UTF8_FREE(font_name); -#endif + return OK; + } - return ok; + return FAIL; } @@ -891,9 +875,69 @@ gui_mch_init_font(char_u *font_name, int fontset) void gui_mch_set_font(GuiFont font) { + // Font selection is done inside MacVim...nothing here to do. } + NSFont * +gui_macvim_font_with_name(char_u *name) +{ + NSFont *font = nil; + NSString *fontName = MMDefaultFontName; + float size = MMDefaultFontSize; + BOOL parseFailed = NO; + +#ifdef FEAT_MBYTE + name = CONVERT_TO_UTF8(name); +#endif + + if (name) { + fontName = [NSString stringWithUTF8String:(char*)name]; + + NSArray *components = [fontName componentsSeparatedByString:@":"]; + if ([components count] == 2) { + NSString *sizeString = [components lastObject]; + if ([sizeString length] > 0 + && [sizeString characterAtIndex:0] == 'h') { + sizeString = [sizeString substringFromIndex:1]; + if ([sizeString length] > 0) { + size = [sizeString floatValue]; + fontName = [components objectAtIndex:0]; + } + } else { + parseFailed = YES; + } + } else if ([components count] > 2) { + parseFailed = YES; + } + + if (!parseFailed) { + // Replace underscores with spaces. + fontName = [[fontName componentsSeparatedByString:@"_"] + componentsJoinedByString:@" "]; + } + } + + if (!parseFailed && [fontName length] > 0) { + if (size < MMMinFontSize) size = MMMinFontSize; + if (size > MMMaxFontSize) size = MMMaxFontSize; + + font = [NSFont fontWithName:fontName size:size]; + + if (!font && MMDefaultFontName == fontName) { + // If for some reason the MacVim default font is not in the app + // bundle, then fall back on the system default font. + font = [NSFont userFixedPitchFontOfSize:0]; + } + } + +#ifdef FEAT_MBYTE + CONVERT_TO_UTF8_FREE(name); +#endif + + return font; +} + // -- Scrollbars ------------------------------------------------------------