1 /* vi:set ts=8 sts=4 sw=4 ft=objc:
3 * VIM - Vi IMproved by Bram Moolenaar
4 * MacVim GUI port by Bjorn Winckler
6 * Do ":help uganda" in Vim to read copying and usage conditions.
7 * Do ":help credits" in Vim to see a list of people who contributed.
8 * See README.txt for an overview of the Vim source code.
13 * Text rendering related code.
16 * - There are exactly 'actualRows' number of rows
17 * - Each row is terminated by an EOL character ('\n')
18 * - Each row must cover exactly 'actualColumns' display cells
19 * - The attribute "MMWideChar" denotes a character that covers two cells, a
20 * character without this attribute covers one cell
21 * - Unicode line (U+2028) and paragraph (U+2029) terminators are considered
22 * invalid and are replaced by spaces
23 * - Spaces are used to fill out blank spaces
25 * In order to locate a (row,col) pair it is in general necessary to search one
26 * character at a time. To speed things up we cache the length of each row, as
27 * well as the offset of the last column searched within each row.
29 * If each character in the text storage has length 1 and is not wide, then
30 * there is no need to search for a (row, col) pair since it can easily be
34 #import "MMTextStorage.h"
36 #import "Miscellaneous.h"
40 // Enable debug log messages for situations that should never occur.
41 #define MM_TS_PARANOIA_LOG 1
45 // TODO: What does DRAW_TRANSP flag do? If the background isn't drawn when
46 // this flag is set, then sometimes the character after the cursor becomes
47 // blank. Everything seems to work fine by just ignoring this flag.
48 #define DRAW_TRANSP 0x01 /* draw with transparant bg */
49 #define DRAW_BOLD 0x02 /* draw bold text */
50 #define DRAW_UNDERL 0x04 /* draw underline text */
51 #define DRAW_UNDERC 0x08 /* draw undercurl text */
52 #define DRAW_ITALIC 0x10 /* draw italic text */
53 #define DRAW_CURSOR 0x20
56 static NSString *MMWideCharacterAttributeName = @"MMWideChar";
61 @interface MMTextStorage (Private)
62 - (void)lazyResize:(BOOL)force;
63 - (NSRange)charRangeForRow:(int)row column:(int*)col cells:(int*)cells;
64 - (void)fixInvalidCharactersInRange:(NSRange)range;
69 @implementation MMTextStorage
73 if ((self = [super init])) {
74 attribString = [[NSMutableAttributedString alloc] initWithString:@""];
75 // NOTE! It does not matter which font is set here, Vim will set its
76 // own font on startup anyway. Just set some bogus values.
77 font = [[NSFont userFixedPitchFontOfSize:0] retain];
78 cellSize.height = 16.0;
95 [emptyRowString release]; emptyRowString = nil;
96 [boldItalicFontWide release]; boldItalicFontWide = nil;
97 [italicFontWide release]; italicFontWide = nil;
98 [boldFontWide release]; boldFontWide = nil;
99 [fontWide release]; fontWide = nil;
100 [boldItalicFont release]; boldItalicFont = nil;
101 [italicFont release]; italicFont = nil;
102 [boldFont release]; boldFont = nil;
103 [font release]; font = nil;
104 [defaultBackgroundColor release]; defaultBackgroundColor = nil;
105 [defaultForegroundColor release]; defaultForegroundColor = nil;
106 [attribString release]; attribString = nil;
112 return [attribString string];
115 - (NSDictionary *)attributesAtIndex:(unsigned)index
116 effectiveRange:(NSRangePointer)range
118 if (index >= [attribString length]) {
120 *range = NSMakeRange(NSNotFound, 0);
122 return [NSDictionary dictionary];
125 return [attribString attributesAtIndex:index effectiveRange:range];
128 - (id)attribute:(NSString *)attrib atIndex:(unsigned)index
129 effectiveRange:(NSRangePointer)range
131 return [attribString attribute:attrib atIndex:index effectiveRange:range];
134 - (void)replaceCharactersInRange:(NSRange)range
135 withString:(NSString *)string
137 #if MM_TS_PARANOIA_LOG
138 NSLog(@"WARNING: calling %s on MMTextStorage is unsupported", _cmd);
140 //[attribString replaceCharactersInRange:range withString:string];
143 - (void)setAttributes:(NSDictionary *)attributes range:(NSRange)range
145 //NSLog(@"%s%@", _cmd, NSStringFromRange(range));
147 // NOTE! This method must be implemented since the text system calls it
148 // constantly to 'fix attributes', apply font substitution, etc.
150 [attribString setAttributes:attributes range:range];
152 // HACK! If the font attribute is being modified, then ensure that the new
153 // font has a fixed advancement which is either the same as the current
154 // font or twice that, depending on whether it is a 'wide' character that
155 // is being fixed or not.
157 // TODO: This code assumes that the characters in 'range' all have the same
159 NSFont *newFont = [attributes objectForKey:NSFontAttributeName];
161 // Allow disabling of font substitution via a user default. Not
162 // recommended since the typesetter hides the corresponding glyphs and
163 // the display gets messed up.
164 if ([[NSUserDefaults standardUserDefaults]
165 boolForKey:MMNoFontSubstitutionKey])
168 float adv = cellSize.width;
169 if ([attribString attribute:MMWideCharacterAttributeName
170 atIndex:range.location
171 effectiveRange:NULL])
174 // Create a new font which has the 'fixed advance attribute' set.
175 NSDictionary *dict = [NSDictionary dictionaryWithObjectsAndKeys:
176 [NSNumber numberWithFloat:adv], NSFontFixedAdvanceAttribute, nil];
177 NSFontDescriptor *desc = [newFont fontDescriptor];
178 desc = [desc fontDescriptorByAddingAttributes:dict];
179 newFont = [NSFont fontWithDescriptor:desc size:[newFont pointSize]];
181 // Now modify the 'attributes' dictionary to hold the new font.
182 NSMutableDictionary *newAttr = [NSMutableDictionary
183 dictionaryWithDictionary:attributes];
184 [newAttr setObject:newFont forKey:NSFontAttributeName];
186 [attribString setAttributes:newAttr range:range];
188 //NSLog(@"NOT fixing font attribute!");
189 [attribString setAttributes:attributes range:range];
211 return actualColumns;
219 - (void)setLinespace:(float)newLinespace
221 NSLayoutManager *lm = [[self layoutManagers] objectAtIndex:0];
223 NSLog(@"WARNING: No layout manager available in call to %s", _cmd);
227 linespace = newLinespace;
229 // NOTE: The linespace is added to the cell height in order for a multiline
230 // selection not to have white (background color) gaps between lines. Also
231 // this simplifies the code a lot because there is no need to check the
232 // linespace when calculating the size of the text view etc. When the
233 // linespace is non-zero the baseline will be adjusted as well; check
235 cellSize.height = linespace + [lm defaultLineHeightForFont:font];
238 - (void)getMaxRows:(int*)rows columns:(int*)cols
240 if (rows) *rows = maxRows;
241 if (cols) *cols = maxColumns;
244 - (void)setMaxRows:(int)rows columns:(int)cols
246 // NOTE: Just remember the new values, the actual resizing is done lazily.
251 - (void)drawString:(NSString *)string atRow:(int)row column:(int)col
252 cells:(int)cells withFlags:(int)flags
253 foregroundColor:(NSColor *)fg backgroundColor:(NSColor *)bg
254 specialColor:(NSColor *)sp
256 //NSLog(@"replaceString:atRow:%d column:%d withFlags:%d "
257 // "foreground:%@ background:%@ special:%@",
258 // row, col, flags, fg, bg, sp);
259 [self lazyResize:NO];
261 if (row < 0 || row >= maxRows || col < 0 || col >= maxColumns
262 || col+cells > maxColumns || !string || !(fg && bg && sp))
265 BOOL hasControlChars = [string rangeOfCharacterFromSet:
266 [NSCharacterSet controlCharacterSet]].location != NSNotFound;
267 if (hasControlChars) {
268 // HACK! If a string for some reason contains control characters, then
269 // draw blanks instead (otherwise charRangeForRow::: fails).
270 NSRange subRange = { 0, cells };
272 string = [[emptyRowString string] substringWithRange:subRange];
275 // Find range of characters in text storage to replace.
278 NSRange range = [self charRangeForRow:row column:&acol cells:&acells];
279 if (NSNotFound == range.location) {
280 #if MM_TS_PARANOIA_LOG
281 NSLog(@"INTERNAL ERROR [%s] Out of bounds", _cmd);
286 // Create dictionary of attributes to apply to the new characters.
287 NSFont *theFont = font;
288 if (flags & DRAW_WIDE) {
289 if (flags & DRAW_BOLD)
290 theFont = flags & DRAW_ITALIC ? boldItalicFontWide : boldFontWide;
291 else if (flags & DRAW_ITALIC)
292 theFont = italicFontWide;
296 if (flags & DRAW_BOLD)
297 theFont = flags & DRAW_ITALIC ? boldItalicFont : boldFont;
298 else if (flags & DRAW_ITALIC)
299 theFont = italicFont;
302 NSMutableDictionary *attributes =
303 [NSMutableDictionary dictionaryWithObjectsAndKeys:
304 theFont, NSFontAttributeName,
305 bg, NSBackgroundColorAttributeName,
306 fg, NSForegroundColorAttributeName,
307 sp, NSUnderlineColorAttributeName,
308 [NSNumber numberWithInt:0], NSLigatureAttributeName,
311 if (flags & DRAW_UNDERL) {
312 NSNumber *value = [NSNumber numberWithInt:(NSUnderlineStyleSingle
313 | NSUnderlinePatternSolid)]; // | NSUnderlineByWordMask
314 [attributes setObject:value forKey:NSUnderlineStyleAttributeName];
317 if (flags & DRAW_UNDERC) {
318 // TODO: figure out how do draw proper undercurls
319 NSNumber *value = [NSNumber numberWithInt:(NSUnderlineStyleThick
320 | NSUnderlinePatternDot)]; // | NSUnderlineByWordMask
321 [attributes setObject:value forKey:NSUnderlineStyleAttributeName];
324 // Mark these characters as wide. This attribute is subsequently checked
325 // when translating (row,col) pairs to offsets within 'attribString'.
326 if (flags & DRAW_WIDE)
327 [attributes setObject:[NSNull null]
328 forKey:MMWideCharacterAttributeName];
330 // Replace characters in text storage and apply new attributes.
331 NSRange r = NSMakeRange(range.location, [string length]);
332 [attribString replaceCharactersInRange:range withString:string];
333 [attribString setAttributes:attributes range:r];
335 unsigned changeInLength = [string length] - range.length;
336 if (acells != cells || acol != col) {
337 if (acells == cells + 1) {
338 // NOTE: A normal width character replaced a double width
339 // character. To maintain the invariant that each row covers the
340 // same amount of cells, we compensate by adding an empty column.
341 [attribString replaceCharactersInRange:NSMakeRange(NSMaxRange(r),0)
342 withAttributedString:[emptyRowString
343 attributedSubstringFromRange:NSMakeRange(0,1)]];
346 } else if (acol == col - 1) {
347 NSLog(@"acol == col - 1");
348 [attribString replaceCharactersInRange:NSMakeRange(r.location,0)
349 withAttributedString:[emptyRowString
350 attributedSubstringFromRange:NSMakeRange(0,1)]];
352 } else if (acol == col + 1) {
353 NSLog(@"acol == col + 1");
354 [attribString replaceCharactersInRange:NSMakeRange(r.location-1,1)
355 withAttributedString:[emptyRowString
356 attributedSubstringFromRange:NSMakeRange(0,2)]];
360 // NOTE: It seems that this never gets called. If it ever does,
361 // then there is another case to treat.
362 #if MM_TS_PARANOIA_LOG
363 NSLog(@"row=%d col=%d acol=%d cells=%d acells=%d", row, col, acol,
369 if ((flags & DRAW_WIDE) || [string length] != cells)
370 characterEqualsColumn = NO;
372 [self fixInvalidCharactersInRange:r];
374 [self edited:(NSTextStorageEditedCharacters|NSTextStorageEditedAttributes)
375 range:range changeInLength:changeInLength];
378 rowCache[row].length += changeInLength;
383 * Delete 'count' lines from 'row' and insert 'count' empty lines at the bottom
384 * of the scroll region.
386 - (void)deleteLinesFromRow:(int)row lineCount:(int)count
387 scrollBottom:(int)bottom left:(int)left right:(int)right
388 color:(NSColor *)color
390 //NSLog(@"deleteLinesFromRow:%d lineCount:%d color:%@", row, count, color);
391 [self lazyResize:NO];
393 if (row < 0 || row+count > maxRows || bottom > maxRows || left < 0
394 || right > maxColumns)
397 int total = 1 + bottom - row;
398 int move = total - count;
399 int width = right - left + 1;
401 NSRange destRange, srcRange;
404 for (i = 0; i < move; ++i, ++destRow) {
407 destRange = [self charRangeForRow:destRow column:&acol cells:&acells];
408 #if MM_TS_PARANOIA_LOG
409 if (acells != width || acol != left)
410 NSLog(@"INTERNAL ERROR [%s]", _cmd);
413 acol = left; acells = width;
414 srcRange = [self charRangeForRow:(destRow+count) column:&acol
416 #if MM_TS_PARANOIA_LOG
417 if (acells != width || acol != left)
418 NSLog(@"INTERNAL ERROR [%s]", _cmd);
421 if (NSNotFound == destRange.location || NSNotFound == srcRange.location)
423 #if MM_TS_PARANOIA_LOG
424 NSLog(@"INTERNAL ERROR [%s] Out of bounds", _cmd);
429 NSAttributedString *srcString = [attribString
430 attributedSubstringFromRange:srcRange];
432 [attribString replaceCharactersInRange:destRange
433 withAttributedString:srcString];
434 [self edited:(NSTextStorageEditedCharacters
435 | NSTextStorageEditedAttributes) range:destRange
436 changeInLength:([srcString length]-destRange.length)];
439 rowCache[destRow].length += [srcString length] - destRange.length;
443 NSRange emptyRange = {0,width};
444 NSAttributedString *emptyString =
445 [emptyRowString attributedSubstringFromRange:emptyRange];
446 NSDictionary *attribs = [NSDictionary dictionaryWithObjectsAndKeys:
447 font, NSFontAttributeName,
448 color, NSBackgroundColorAttributeName, nil];
450 for (i = 0; i < count; ++i, ++destRow) {
453 destRange = [self charRangeForRow:destRow column:&acol cells:&acells];
454 #if MM_TS_PARANOIA_LOG
455 if (acells != width || acol != left)
456 NSLog(@"INTERNAL ERROR [%s]", _cmd);
458 if (NSNotFound == destRange.location) {
459 #if MM_TS_PARANOIA_LOG
460 NSLog(@"INTERNAL ERROR [%s] Out of bounds", _cmd);
465 [attribString replaceCharactersInRange:destRange
466 withAttributedString:emptyString];
467 [attribString setAttributes:attribs
468 range:NSMakeRange(destRange.location, width)];
470 [self edited:(NSTextStorageEditedAttributes
471 | NSTextStorageEditedCharacters) range:destRange
472 changeInLength:([emptyString length]-destRange.length)];
475 rowCache[destRow].length += [emptyString length] - destRange.length;
481 * Insert 'count' empty lines at 'row' and delete 'count' lines from the bottom
482 * of the scroll region.
484 - (void)insertLinesAtRow:(int)row lineCount:(int)count
485 scrollBottom:(int)bottom left:(int)left right:(int)right
486 color:(NSColor *)color
488 //NSLog(@"insertLinesAtRow:%d lineCount:%d color:%@", row, count, color);
489 [self lazyResize:NO];
491 if (row < 0 || row+count > maxRows || bottom > maxRows || left < 0
492 || right > maxColumns)
495 int total = 1 + bottom - row;
496 int move = total - count;
497 int width = right - left + 1;
498 int destRow = bottom;
499 int srcRow = row + move - 1;
500 NSRange destRange, srcRange;
503 for (i = 0; i < move; ++i, --destRow, --srcRow) {
506 destRange = [self charRangeForRow:destRow column:&acol cells:&acells];
507 #if MM_TS_PARANOIA_LOG
508 if (acells != width || acol != left)
509 NSLog(@"INTERNAL ERROR [%s]", _cmd);
512 acol = left; acells = width;
513 srcRange = [self charRangeForRow:srcRow column:&acol cells:&acells];
514 #if MM_TS_PARANOIA_LOG
515 if (acells != width || acol != left)
516 NSLog(@"INTERNAL ERROR [%s]", _cmd);
518 if (NSNotFound == destRange.location || NSNotFound == srcRange.location)
520 #if MM_TS_PARANOIA_LOG
521 NSLog(@"INTERNAL ERROR [%s] Out of bounds", _cmd);
526 NSAttributedString *srcString = [attribString
527 attributedSubstringFromRange:srcRange];
528 [attribString replaceCharactersInRange:destRange
529 withAttributedString:srcString];
530 [self edited:(NSTextStorageEditedCharacters
531 | NSTextStorageEditedAttributes) range:destRange
532 changeInLength:([srcString length]-destRange.length)];
535 rowCache[destRow].length += [srcString length] - destRange.length;
539 NSRange emptyRange = {0,width};
540 NSAttributedString *emptyString =
541 [emptyRowString attributedSubstringFromRange:emptyRange];
542 NSDictionary *attribs = [NSDictionary dictionaryWithObjectsAndKeys:
543 font, NSFontAttributeName,
544 color, NSBackgroundColorAttributeName, nil];
546 for (i = 0; i < count; ++i, --destRow) {
549 destRange = [self charRangeForRow:destRow column:&acol cells:&acells];
550 #if MM_TS_PARANOIA_LOG
551 if (acells != width || acol != left)
552 NSLog(@"INTERNAL ERROR [%s]", _cmd);
554 if (NSNotFound == destRange.location) {
555 #if MM_TS_PARANOIA_LOG
556 NSLog(@"INTERNAL ERROR [%s] Out of bounds", _cmd);
561 [attribString replaceCharactersInRange:destRange
562 withAttributedString:emptyString];
563 [attribString setAttributes:attribs
564 range:NSMakeRange(destRange.location, width)];
566 [self edited:(NSTextStorageEditedAttributes
567 | NSTextStorageEditedCharacters) range:destRange
568 changeInLength:([emptyString length]-destRange.length)];
571 rowCache[destRow].length += [emptyString length] - destRange.length;
576 - (void)clearBlockFromRow:(int)row1 column:(int)col1 toRow:(int)row2
577 column:(int)col2 color:(NSColor *)color
579 //NSLog(@"clearBlockFromRow:%d column:%d toRow:%d column:%d color:%@",
580 // row1, col1, row2, col2, color);
581 [self lazyResize:NO];
583 if (row1 < 0 || row2 >= maxRows || col1 < 0 || col2 > maxColumns)
586 NSDictionary *attribs = [NSDictionary dictionaryWithObjectsAndKeys:
587 font, NSFontAttributeName,
588 color, NSBackgroundColorAttributeName, nil];
589 int cells = col2 - col1 + 1;
590 NSRange range, emptyRange = {0, cells};
591 NSAttributedString *emptyString =
592 [emptyRowString attributedSubstringFromRange:emptyRange];
595 for (r=row1; r<=row2; ++r) {
598 range = [self charRangeForRow:r column:&acol cells:&acells];
599 #if MM_TS_PARANOIA_LOG
600 if (acells != cells || acol != col1)
601 NSLog(@"INTERNAL ERROR [%s]", _cmd);
603 if (NSNotFound == range.location) {
604 #if MM_TS_PARANOIA_LOG
605 NSLog(@"INTERNAL ERROR [%s] Out of bounds", _cmd);
610 [attribString replaceCharactersInRange:range
611 withAttributedString:emptyString];
612 [attribString setAttributes:attribs
613 range:NSMakeRange(range.location, cells)];
615 [self edited:(NSTextStorageEditedAttributes
616 | NSTextStorageEditedCharacters) range:range
617 changeInLength:cells-range.length];
620 rowCache[r].length += cells - range.length;
627 //NSLog(@"%s", _cmd);
628 [self lazyResize:YES];
631 - (void)setDefaultColorsBackground:(NSColor *)bgColor
632 foreground:(NSColor *)fgColor
634 if (defaultBackgroundColor != bgColor) {
635 [defaultBackgroundColor release];
636 defaultBackgroundColor = bgColor ? [bgColor retain] : nil;
639 // NOTE: The default foreground color isn't actually used for anything, but
640 // other class instances might want to be able to access it so it is stored
642 if (defaultForegroundColor != fgColor) {
643 [defaultForegroundColor release];
644 defaultForegroundColor = fgColor ? [fgColor retain] : nil;
648 - (void)setFont:(NSFont*)newFont
650 if (newFont && font != newFont) {
651 [boldItalicFont release]; boldItalicFont = nil;
652 [italicFont release]; italicFont = nil;
653 [boldFont release]; boldFont = nil;
654 [font release]; font = nil;
656 // NOTE! When setting a new font we make sure that the advancement of
657 // each glyph is fixed.
659 float em = [@"m" sizeWithAttributes:
660 [NSDictionary dictionaryWithObject:newFont
661 forKey:NSFontAttributeName]].width;
662 float cellWidthMultiplier = [[NSUserDefaults standardUserDefaults]
663 floatForKey:MMCellWidthMultiplierKey];
665 // NOTE! Even though NSFontFixedAdvanceAttribute is a float, it will
666 // only render at integer sizes. Hence, we restrict the cell width to
667 // an integer here, otherwise the window width and the actual text
668 // width will not match.
669 cellSize.width = ceilf(em * cellWidthMultiplier);
671 float pointSize = [newFont pointSize];
672 NSDictionary *dict = [NSDictionary
673 dictionaryWithObject:[NSNumber numberWithFloat:cellSize.width]
674 forKey:NSFontFixedAdvanceAttribute];
676 NSFontDescriptor *desc = [newFont fontDescriptor];
677 desc = [desc fontDescriptorByAddingAttributes:dict];
678 font = [NSFont fontWithDescriptor:desc size:pointSize];
681 NSLayoutManager *lm = [[self layoutManagers] objectAtIndex:0];
683 cellSize.height = linespace + [lm defaultLineHeightForFont:font];
685 // Should never happen, set some bogus value for cell height.
686 NSLog(@"WARNING: No layout manager available in call to %s", _cmd);
687 cellSize.height = linespace + 16.0;
690 // NOTE: The font manager does not care about the 'font fixed advance'
691 // attribute, so after converting the font we have to add this
693 boldFont = [[NSFontManager sharedFontManager]
694 convertFont:font toHaveTrait:NSBoldFontMask];
695 desc = [boldFont fontDescriptor];
696 desc = [desc fontDescriptorByAddingAttributes:dict];
697 boldFont = [NSFont fontWithDescriptor:desc size:pointSize];
700 italicFont = [[NSFontManager sharedFontManager]
701 convertFont:font toHaveTrait:NSItalicFontMask];
702 desc = [italicFont fontDescriptor];
703 desc = [desc fontDescriptorByAddingAttributes:dict];
704 italicFont = [NSFont fontWithDescriptor:desc size:pointSize];
707 boldItalicFont = [[NSFontManager sharedFontManager]
708 convertFont:italicFont toHaveTrait:NSBoldFontMask];
709 desc = [boldItalicFont fontDescriptor];
710 desc = [desc fontDescriptorByAddingAttributes:dict];
711 boldItalicFont = [NSFont fontWithDescriptor:desc size:pointSize];
712 [boldItalicFont retain];
716 - (void)setWideFont:(NSFont *)newFont
719 // Use the normal font as the wide font (note that the normal font may
720 // very well include wide characters.)
721 if (font) [self setWideFont:font];
722 } else if (newFont != fontWide) {
723 [boldItalicFontWide release]; boldItalicFontWide = nil;
724 [italicFontWide release]; italicFontWide = nil;
725 [boldFontWide release]; boldFontWide = nil;
726 [fontWide release]; fontWide = nil;
728 float pointSize = [newFont pointSize];
729 NSFontDescriptor *desc = [newFont fontDescriptor];
730 NSDictionary *dictWide = [NSDictionary
731 dictionaryWithObject:[NSNumber numberWithFloat:2*cellSize.width]
732 forKey:NSFontFixedAdvanceAttribute];
734 desc = [desc fontDescriptorByAddingAttributes:dictWide];
735 fontWide = [NSFont fontWithDescriptor:desc size:pointSize];
738 boldFontWide = [[NSFontManager sharedFontManager]
739 convertFont:fontWide toHaveTrait:NSBoldFontMask];
740 desc = [boldFontWide fontDescriptor];
741 desc = [desc fontDescriptorByAddingAttributes:dictWide];
742 boldFontWide = [NSFont fontWithDescriptor:desc size:pointSize];
743 [boldFontWide retain];
745 italicFontWide = [[NSFontManager sharedFontManager]
746 convertFont:fontWide toHaveTrait:NSItalicFontMask];
747 desc = [italicFontWide fontDescriptor];
748 desc = [desc fontDescriptorByAddingAttributes:dictWide];
749 italicFontWide = [NSFont fontWithDescriptor:desc size:pointSize];
750 [italicFontWide retain];
752 boldItalicFontWide = [[NSFontManager sharedFontManager]
753 convertFont:italicFontWide toHaveTrait:NSBoldFontMask];
754 desc = [boldItalicFontWide fontDescriptor];
755 desc = [desc fontDescriptorByAddingAttributes:dictWide];
756 boldItalicFontWide = [NSFont fontWithDescriptor:desc size:pointSize];
757 [boldItalicFontWide retain];
771 - (NSColor *)defaultBackgroundColor
773 return defaultBackgroundColor;
776 - (NSColor *)defaultForegroundColor
778 return defaultForegroundColor;
783 return NSMakeSize(maxColumns*cellSize.width, maxRows*cellSize.height);
791 - (NSRect)rectForRowsInRange:(NSRange)range
793 NSRect rect = { 0, 0, 0, 0 };
794 unsigned start = range.location > maxRows ? maxRows : range.location;
795 unsigned length = range.length;
797 if (start+length > maxRows)
798 length = maxRows - start;
800 rect.origin.y = cellSize.height * start;
801 rect.size.height = cellSize.height * length;
806 - (NSRect)rectForColumnsInRange:(NSRange)range
808 NSRect rect = { 0, 0, 0, 0 };
809 unsigned start = range.location > maxColumns ? maxColumns : range.location;
810 unsigned length = range.length;
812 if (start+length > maxColumns)
813 length = maxColumns - start;
815 rect.origin.x = cellSize.width * start;
816 rect.size.width = cellSize.width * length;
821 - (unsigned)characterIndexForRow:(int)row column:(int)col
824 NSRange range = [self charRangeForRow:row column:&col cells:&cells];
825 return range.location != NSNotFound ? range.location : 0;
828 // XXX: unused at the moment
829 - (BOOL)resizeToFitSize:(NSSize)size
831 int rows = maxRows, cols = maxColumns;
833 [self fitToSize:size rows:&rows columns:&cols];
834 if (rows != maxRows || cols != maxColumns) {
835 [self setMaxRows:rows columns:cols];
839 // Return NO only if dimensions did not change.
843 - (NSSize)fitToSize:(NSSize)size
845 return [self fitToSize:size rows:NULL columns:NULL];
848 - (NSSize)fitToSize:(NSSize)size rows:(int *)rows columns:(int *)columns
850 NSSize curSize = [self size];
851 NSSize fitSize = curSize;
852 int fitRows = maxRows;
853 int fitCols = maxColumns;
855 if (size.height < curSize.height) {
856 // Remove lines until the height of the text storage fits inside
857 // 'size'. However, always make sure there are at least 3 lines in the
858 // text storage. (Why 3? It seem Vim never allows less than 3 lines.)
860 // TODO: No need to search since line height is fixed, just calculate
862 int rowCount = maxRows;
864 for (rowsToRemove = 0; rowsToRemove < maxRows-3; ++rowsToRemove) {
865 float height = cellSize.height*rowCount;
867 if (height <= size.height) {
868 fitSize.height = height;
875 fitRows -= rowsToRemove;
876 } else if (size.height > curSize.height) {
877 float fh = cellSize.height;
878 if (fh < 1.0f) fh = 1.0f;
880 fitRows = floor(size.height/fh);
881 fitSize.height = fh*fitRows;
884 if (size.width != curSize.width) {
885 float fw = cellSize.width;
886 if (fw < 1.0f) fw = 1.0f;
888 fitCols = floor(size.width/fw);
889 fitSize.width = fw*fitCols;
892 if (rows) *rows = fitRows;
893 if (columns) *columns = fitCols;
898 - (NSRect)boundingRectForCharacterAtRow:(int)row column:(int)col
901 // This properly computes the position of where Vim expects the glyph to be
902 // drawn. Had the typesetter actually computed the right position of each
903 // character and not hidden some, this code would be correct.
904 NSRect rect = NSZeroRect;
906 rect.origin.x = col*cellSize.width;
907 rect.origin.y = row*cellSize.height;
908 rect.size = cellSize;
910 // Wide character take up twice the width of a normal character.
912 NSRange r = [self charRangeForRow:row column:&col cells:&cells];
913 if (NSNotFound != r.location
914 && [attribString attribute:MMWideCharacterAttributeName
917 rect.size.width += rect.size.width;
921 // Use layout manager to compute bounding rect. This works in situations
922 // where the layout manager decides to hide glyphs (Vim assumes all glyphs
924 NSLayoutManager *lm = [[self layoutManagers] objectAtIndex:0];
925 NSTextContainer *tc = [[lm textContainers] objectAtIndex:0];
927 NSRange range = [self charRangeForRow:row column:&col cells:&cells];
928 NSRange glyphRange = [lm glyphRangeForCharacterRange:range
929 actualCharacterRange:NULL];
931 return [lm boundingRectForGlyphRange:glyphRange inTextContainer:tc];
936 - (MMRowCacheEntry *)rowCache
942 @end // MMTextStorage
947 @implementation MMTextStorage (Private)
949 - (void)lazyResize:(BOOL)force
951 // Do nothing if the dimensions are already right.
952 if (!force && actualRows == maxRows && actualColumns == maxColumns)
955 NSRange oldRange = NSMakeRange(0, [attribString length]);
957 actualRows = maxRows;
958 actualColumns = maxColumns;
959 characterEqualsColumn = YES;
963 rowCache = (MMRowCacheEntry*)calloc(actualRows, sizeof(MMRowCacheEntry));
967 if (defaultBackgroundColor) {
968 dict = [NSDictionary dictionaryWithObjectsAndKeys:
969 font, NSFontAttributeName,
970 defaultBackgroundColor, NSBackgroundColorAttributeName, nil];
972 dict = [NSDictionary dictionaryWithObjectsAndKeys:
973 font, NSFontAttributeName, nil];
976 NSMutableString *rowString = [NSMutableString string];
978 for (i = 0; i < maxColumns; ++i) {
979 [rowString appendString:@" "];
981 [rowString appendString:@"\n"];
983 [emptyRowString release];
984 emptyRowString = [[NSAttributedString alloc] initWithString:rowString
987 [attribString release];
988 attribString = [[NSMutableAttributedString alloc] init];
989 for (i=0; i<maxRows; ++i) {
991 rowCache[i].length = actualColumns + 1;
993 [attribString appendAttributedString:emptyRowString];
996 NSRange fullRange = NSMakeRange(0, [attribString length]);
997 [self edited:(NSTextStorageEditedCharacters|NSTextStorageEditedAttributes)
998 range:oldRange changeInLength:fullRange.length-oldRange.length];
1001 - (NSRange)charRangeForRow:(int)row column:(int*)pcol cells:(int*)pcells
1004 int cells = *pcells;
1006 // If no wide chars are used and if every char has length 1 (no composing
1007 // characters, no > 16 bit characters), then we can compute the range.
1008 if (characterEqualsColumn)
1009 return NSMakeRange(row*(actualColumns+1) + col, cells);
1011 NSString *string = [attribString string];
1012 NSRange r, range = { NSNotFound, 0 };
1013 unsigned idx, rowEnd;
1016 if (row < 0 || row >= actualRows || col < 0 || col >= actualColumns
1017 || col+cells > actualColumns) {
1018 #if MM_TS_PARANOIA_LOG
1019 NSLog(@"%s row=%d col=%d cells=%d is out of range (length=%d)",
1020 _cmd, row, col, cells, [string length]);
1025 #if MM_USE_ROW_CACHE
1026 // Locate the beginning of the row
1027 MMRowCacheEntry *cache = rowCache;
1029 for (i = 0; i < row; ++i, ++cache)
1030 idx += cache->length;
1032 rowEnd = idx + cache->length;
1034 // Locate the beginning of the row by scanning for EOL characters.
1036 for (i = 0; i < row; ++i) {
1037 r.length = [string length] - r.location;
1038 r = [string rangeOfString:@"\n" options:NSLiteralSearch range:r];
1039 if (NSNotFound == r.location)
1045 // Locate the column
1046 #if MM_USE_ROW_CACHE
1047 cache = &rowCache[row];
1052 idx += cache->colOffset;
1054 range.location = idx;
1056 #if 0 // Backward search seems to be broken...
1058 if (col < i - col) {
1059 // Search forward from beginning of line.
1061 } else if (actualColumns - col < col - i) {
1062 // Search backward from end of line.
1063 i = actualColumns - 1;
1064 idx += cache->length - 2;
1066 // Search from cache spot (forward or backward).
1067 idx += cache->colOffset;
1073 r = [string rangeOfComposedCharacterSequenceAtIndex:idx];
1075 // Wide chars take up two display cells.
1076 if ([attribString attribute:MMWideCharacterAttributeName
1078 effectiveRange:nil])
1084 } else if (col < i) {
1087 r = [string rangeOfComposedCharacterSequenceAtIndex:idx-1];
1091 // Wide chars take up two display cells.
1092 if ([attribString attribute:MMWideCharacterAttributeName
1094 effectiveRange:nil])
1101 cache->colOffset = idx - range.location;
1105 // Search forward from beginning of line.
1108 // Search forward from cache spot.
1109 idx += cache->colOffset;
1114 r = [string rangeOfComposedCharacterSequenceAtIndex:idx];
1116 // Wide chars take up two display cells.
1117 if ([attribString attribute:MMWideCharacterAttributeName
1119 effectiveRange:nil])
1128 cache->colOffset = idx - range.location;
1133 for (i = 0; i < col; ++i) {
1134 r = [string rangeOfComposedCharacterSequenceAtIndex:idx];
1136 // Wide chars take up two display cells.
1137 if ([attribString attribute:MMWideCharacterAttributeName
1139 effectiveRange:nil])
1146 // Count the number of characters that cover the cells.
1147 range.location = idx;
1148 for (i = 0; i < cells; ++i) {
1149 r = [string rangeOfComposedCharacterSequenceAtIndex:idx];
1151 // Wide chars take up two display cells.
1152 if ([attribString attribute:MMWideCharacterAttributeName
1154 effectiveRange:nil])
1158 range.length += r.length;
1163 #if MM_TS_PARANOIA_LOG
1164 #if MM_USE_ROW_CACHE
1165 if (range.location >= rowEnd-1) {
1166 NSLog(@"INTERNAL ERROR [%s] : row=%d col=%d cells=%d --> range=%@",
1167 _cmd, row, col, cells, NSStringFromRange(range));
1168 range.location = rowEnd - 2;
1170 } else if (NSMaxRange(range) >= rowEnd) {
1171 NSLog(@"INTERNAL ERROR [%s] : row=%d col=%d cells=%d --> range=%@",
1172 _cmd, row, col, cells, NSStringFromRange(range));
1173 range.length = rowEnd - range.location - 1;
1177 if (NSMaxRange(range) > [string length]) {
1178 NSLog(@"INTERNAL ERROR [%s] : row=%d col=%d cells=%d --> range=%@",
1179 _cmd, row, col, cells, NSStringFromRange(range));
1180 range.location = NSNotFound;
1188 - (void)fixInvalidCharactersInRange:(NSRange)range
1190 static NSCharacterSet *invalidCharacterSet = nil;
1191 NSRange invalidRange;
1194 if (!invalidCharacterSet)
1195 invalidCharacterSet = [[NSCharacterSet characterSetWithRange:
1196 NSMakeRange(0x2028, 2)] retain];
1198 // HACK! Replace characters that the text system can't handle (currently
1199 // LINE SEPARATOR U+2028 and PARAGRAPH SEPARATOR U+2029) with space.
1201 // TODO: Treat these separately inside of Vim so we don't have to bother
1203 while (range.length > 0) {
1204 invalidRange = [[attribString string]
1205 rangeOfCharacterFromSet:invalidCharacterSet
1206 options:NSLiteralSearch
1208 if (NSNotFound == invalidRange.location)
1211 [attribString replaceCharactersInRange:invalidRange withString:@" "];
1213 end = NSMaxRange(invalidRange);
1214 range.length -= end - range.location;
1215 range.location = end;
1219 @end // MMTextStorage (Private)