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"
39 // Enable debug log messages for situations that should never occur.
40 #define MM_TS_PARANOIA_LOG 1
44 // TODO: What does DRAW_TRANSP flag do? If the background isn't drawn when
45 // this flag is set, then sometimes the character after the cursor becomes
46 // blank. Everything seems to work fine by just ignoring this flag.
47 #define DRAW_TRANSP 0x01 /* draw with transparant bg */
48 #define DRAW_BOLD 0x02 /* draw bold text */
49 #define DRAW_UNDERL 0x04 /* draw underline text */
50 #define DRAW_UNDERC 0x08 /* draw undercurl text */
51 #define DRAW_ITALIC 0x10 /* draw italic text */
52 #define DRAW_CURSOR 0x20
55 static NSString *MMWideCharacterAttributeName = @"MMWideChar";
60 @interface MMTextStorage (Private)
61 - (void)lazyResize:(BOOL)force;
62 - (NSRange)charRangeForRow:(int)row column:(int*)col cells:(int*)cells;
63 - (void)fixInvalidCharactersInRange:(NSRange)range;
68 @implementation MMTextStorage
72 if ((self = [super init])) {
73 attribString = [[NSMutableAttributedString alloc] initWithString:@""];
74 // NOTE! It does not matter which font is set here, Vim will set its
75 // own font on startup anyway. Just set some bogus values.
76 font = [[NSFont userFixedPitchFontOfSize:0] retain];
77 cellSize.height = [font pointSize];
78 cellSize.width = [font defaultLineHeightForFont];
92 [emptyRowString release];
93 [boldItalicFontWide release];
94 [italicFontWide release];
95 [boldFontWide release];
97 [boldItalicFont release];
101 [defaultBackgroundColor release];
102 [defaultForegroundColor release];
103 [attribString release];
109 return [attribString string];
112 - (NSDictionary *)attributesAtIndex:(unsigned)index
113 effectiveRange:(NSRangePointer)range
115 if (index >= [attribString length]) {
117 *range = NSMakeRange(NSNotFound, 0);
119 return [NSDictionary dictionary];
122 return [attribString attributesAtIndex:index effectiveRange:range];
125 - (id)attribute:(NSString *)attrib atIndex:(unsigned)index
126 effectiveRange:(NSRangePointer)range
128 return [attribString attribute:attrib atIndex:index effectiveRange:range];
131 - (void)replaceCharactersInRange:(NSRange)range
132 withString:(NSString *)string
134 #if MM_TS_PARANOIA_LOG
135 NSLog(@"WARNING: calling %s on MMTextStorage is unsupported", _cmd);
137 //[attribString replaceCharactersInRange:range withString:string];
140 - (void)setAttributes:(NSDictionary *)attributes range:(NSRange)range
142 //NSLog(@"%s%@", _cmd, NSStringFromRange(range));
144 // NOTE! This method must be implemented since the text system calls it
145 // constantly to 'fix attributes', apply font substitution, etc.
147 [attribString setAttributes:attributes range:range];
149 // HACK! If the font attribute is being modified, then ensure that the new
150 // font has a fixed advancement which is either the same as the current
151 // font or twice that, depending on whether it is a 'wide' character that
152 // is being fixed or not.
154 // TODO: This code assumes that the characters in 'range' all have the same
156 NSFont *newFont = [attributes objectForKey:NSFontAttributeName];
158 // Allow disabling of font substitution via a user default. Not
159 // recommended since the typesetter hides the corresponding glyphs and
160 // the display gets messed up.
161 if ([[NSUserDefaults standardUserDefaults]
162 boolForKey:MMNoFontSubstitutionKey])
165 float adv = cellSize.width;
166 if ([attribString attribute:MMWideCharacterAttributeName
167 atIndex:range.location
168 effectiveRange:NULL])
171 // Create a new font which has the 'fixed advance attribute' set.
172 NSDictionary *dict = [NSDictionary dictionaryWithObjectsAndKeys:
173 [NSNumber numberWithFloat:adv], NSFontFixedAdvanceAttribute, nil];
174 NSFontDescriptor *desc = [newFont fontDescriptor];
175 desc = [desc fontDescriptorByAddingAttributes:dict];
176 newFont = [NSFont fontWithDescriptor:desc size:[newFont pointSize]];
178 // Now modify the 'attributes' dictionary to hold the new font.
179 NSMutableDictionary *newAttr = [NSMutableDictionary
180 dictionaryWithDictionary:attributes];
181 [newAttr setObject:newFont forKey:NSFontAttributeName];
183 [attribString setAttributes:newAttr range:range];
185 //NSLog(@"NOT fixing font attribute!");
186 [attribString setAttributes:attributes range:range];
208 return actualColumns;
216 - (void)setLinespace:(float)newLinespace
218 NSLayoutManager *lm = [[self layoutManagers] objectAtIndex:0];
220 linespace = newLinespace;
222 // NOTE: The linespace is added to the cell height in order for a multiline
223 // selection not to have white (background color) gaps between lines. Also
224 // this simplifies the code a lot because there is no need to check the
225 // linespace when calculating the size of the text view etc. When the
226 // linespace is non-zero the baseline will be adjusted as well; check
228 cellSize.height = linespace + (lm ? [lm defaultLineHeightForFont:font]
229 : [font defaultLineHeightForFont]);
232 - (void)getMaxRows:(int*)rows columns:(int*)cols
234 if (rows) *rows = maxRows;
235 if (cols) *cols = maxColumns;
238 - (void)setMaxRows:(int)rows columns:(int)cols
240 // NOTE: Just remember the new values, the actual resizing is done lazily.
245 - (void)drawString:(NSString *)string atRow:(int)row column:(int)col
246 cells:(int)cells withFlags:(int)flags
247 foregroundColor:(NSColor *)fg backgroundColor:(NSColor *)bg
248 specialColor:(NSColor *)sp
250 //NSLog(@"replaceString:atRow:%d column:%d withFlags:%d "
251 // "foreground:%@ background:%@ special:%@",
252 // row, col, flags, fg, bg, sp);
253 [self lazyResize:NO];
255 if (row < 0 || row >= maxRows || col < 0 || col >= maxColumns
256 || col+cells > maxColumns || !string || !(fg && bg && sp))
259 // Find range of characters in text storage to replace.
262 NSRange range = [self charRangeForRow:row column:&acol cells:&acells];
263 if (NSNotFound == range.location) {
264 #if MM_TS_PARANOIA_LOG
265 NSLog(@"INTERNAL ERROR [%s] Out of bounds", _cmd);
270 // Create dictionary of attributes to apply to the new characters.
271 NSFont *theFont = font;
272 if (flags & DRAW_WIDE) {
273 if (flags & DRAW_BOLD)
274 theFont = flags & DRAW_ITALIC ? boldItalicFontWide : boldFontWide;
275 else if (flags & DRAW_ITALIC)
276 theFont = italicFontWide;
280 if (flags & DRAW_BOLD)
281 theFont = flags & DRAW_ITALIC ? boldItalicFont : boldFont;
282 else if (flags & DRAW_ITALIC)
283 theFont = italicFont;
286 NSMutableDictionary *attributes =
287 [NSMutableDictionary dictionaryWithObjectsAndKeys:
288 theFont, NSFontAttributeName,
289 bg, NSBackgroundColorAttributeName,
290 fg, NSForegroundColorAttributeName,
291 sp, NSUnderlineColorAttributeName,
294 if (flags & DRAW_UNDERL) {
295 NSNumber *value = [NSNumber numberWithInt:(NSUnderlineStyleSingle
296 | NSUnderlinePatternSolid)]; // | NSUnderlineByWordMask
297 [attributes setObject:value forKey:NSUnderlineStyleAttributeName];
300 if (flags & DRAW_UNDERC) {
301 // TODO: figure out how do draw proper undercurls
302 NSNumber *value = [NSNumber numberWithInt:(NSUnderlineStyleThick
303 | NSUnderlinePatternDot)]; // | NSUnderlineByWordMask
304 [attributes setObject:value forKey:NSUnderlineStyleAttributeName];
307 // Mark these characters as wide. This attribute is subsequently checked
308 // when translating (row,col) pairs to offsets within 'attribString'.
309 if (flags & DRAW_WIDE)
310 [attributes setObject:[NSNull null]
311 forKey:MMWideCharacterAttributeName];
313 // Replace characters in text storage and apply new attributes.
314 NSRange r = NSMakeRange(range.location, [string length]);
315 [attribString replaceCharactersInRange:range withString:string];
316 [attribString setAttributes:attributes range:r];
318 unsigned changeInLength = [string length] - range.length;
319 if (acells != cells || acol != col) {
320 if (acells == cells + 1) {
321 // NOTE: A normal width character replaced a double width
322 // character. To maintain the invariant that each row covers the
323 // same amount of cells, we compensate by adding an empty column.
324 [attribString replaceCharactersInRange:NSMakeRange(NSMaxRange(r),0)
325 withAttributedString:[emptyRowString
326 attributedSubstringFromRange:NSMakeRange(0,1)]];
329 } else if (acol == col - 1) {
330 NSLog(@"acol == col - 1");
331 [attribString replaceCharactersInRange:NSMakeRange(r.location,0)
332 withAttributedString:[emptyRowString
333 attributedSubstringFromRange:NSMakeRange(0,1)]];
335 } else if (acol == col + 1) {
336 NSLog(@"acol == col + 1");
337 [attribString replaceCharactersInRange:NSMakeRange(r.location-1,1)
338 withAttributedString:[emptyRowString
339 attributedSubstringFromRange:NSMakeRange(0,2)]];
343 // NOTE: It seems that this never gets called. If it ever does,
344 // then there is another case to treat.
345 #if MM_TS_PARANOIA_LOG
346 NSLog(@"row=%d col=%d acol=%d cells=%d acells=%d", row, col, acol,
352 if ((flags & DRAW_WIDE) || [string length] != cells)
353 characterEqualsColumn = NO;
355 [self fixInvalidCharactersInRange:r];
357 [self edited:(NSTextStorageEditedCharacters|NSTextStorageEditedAttributes)
358 range:range changeInLength:changeInLength];
361 rowCache[row].length += changeInLength;
366 * Delete 'count' lines from 'row' and insert 'count' empty lines at the bottom
367 * of the scroll region.
369 - (void)deleteLinesFromRow:(int)row lineCount:(int)count
370 scrollBottom:(int)bottom left:(int)left right:(int)right
371 color:(NSColor *)color
373 //NSLog(@"deleteLinesFromRow:%d lineCount:%d color:%@", row, count, color);
374 [self lazyResize:NO];
376 if (row < 0 || row+count > maxRows)
379 int total = 1 + bottom - row;
380 int move = total - count;
381 int width = right - left + 1;
383 NSRange destRange, srcRange;
386 for (i = 0; i < move; ++i, ++destRow) {
389 destRange = [self charRangeForRow:destRow column:&acol cells:&acells];
390 #if MM_TS_PARANOIA_LOG
391 if (acells != width || acol != left)
392 NSLog(@"INTERNAL ERROR [%s]", _cmd);
395 acol = left; acells = width;
396 srcRange = [self charRangeForRow:(destRow+count) column:&acol
398 #if MM_TS_PARANOIA_LOG
399 if (acells != width || acol != left)
400 NSLog(@"INTERNAL ERROR [%s]", _cmd);
403 if (NSNotFound == destRange.location || NSNotFound == srcRange.location)
405 #if MM_TS_PARANOIA_LOG
406 NSLog(@"INTERNAL ERROR [%s] Out of bounds", _cmd);
411 NSAttributedString *srcString = [attribString
412 attributedSubstringFromRange:srcRange];
414 [attribString replaceCharactersInRange:destRange
415 withAttributedString:srcString];
416 [self edited:(NSTextStorageEditedCharacters
417 | NSTextStorageEditedAttributes) range:destRange
418 changeInLength:([srcString length]-destRange.length)];
421 rowCache[destRow].length += [srcString length] - destRange.length;
425 NSRange emptyRange = {0,width};
426 NSAttributedString *emptyString =
427 [emptyRowString attributedSubstringFromRange:emptyRange];
428 NSDictionary *attribs = [NSDictionary dictionaryWithObjectsAndKeys:
429 font, NSFontAttributeName,
430 color, NSBackgroundColorAttributeName, nil];
432 for (i = 0; i < count; ++i, ++destRow) {
435 destRange = [self charRangeForRow:destRow column:&acol cells:&acells];
436 #if MM_TS_PARANOIA_LOG
437 if (acells != width || acol != left)
438 NSLog(@"INTERNAL ERROR [%s]", _cmd);
440 if (NSNotFound == destRange.location) {
441 #if MM_TS_PARANOIA_LOG
442 NSLog(@"INTERNAL ERROR [%s] Out of bounds", _cmd);
447 [attribString replaceCharactersInRange:destRange
448 withAttributedString:emptyString];
449 [attribString setAttributes:attribs
450 range:NSMakeRange(destRange.location, width)];
452 [self edited:(NSTextStorageEditedAttributes
453 | NSTextStorageEditedCharacters) range:destRange
454 changeInLength:([emptyString length]-destRange.length)];
457 rowCache[destRow].length += [emptyString length] - destRange.length;
463 * Insert 'count' empty lines at 'row' and delete 'count' lines from the bottom
464 * of the scroll region.
466 - (void)insertLinesAtRow:(int)row lineCount:(int)count
467 scrollBottom:(int)bottom left:(int)left right:(int)right
468 color:(NSColor *)color
470 //NSLog(@"insertLinesAtRow:%d lineCount:%d color:%@", row, count, color);
471 [self lazyResize:NO];
473 if (row < 0 || row+count > maxRows)
476 int total = 1 + bottom - row;
477 int move = total - count;
478 int width = right - left + 1;
479 int destRow = bottom;
480 int srcRow = row + move - 1;
481 NSRange destRange, srcRange;
484 for (i = 0; i < move; ++i, --destRow, --srcRow) {
487 destRange = [self charRangeForRow:destRow column:&acol cells:&acells];
488 #if MM_TS_PARANOIA_LOG
489 if (acells != width || acol != left)
490 NSLog(@"INTERNAL ERROR [%s]", _cmd);
493 acol = left; acells = width;
494 srcRange = [self charRangeForRow:srcRow column:&acol cells:&acells];
495 #if MM_TS_PARANOIA_LOG
496 if (acells != width || acol != left)
497 NSLog(@"INTERNAL ERROR [%s]", _cmd);
499 if (NSNotFound == destRange.location || NSNotFound == srcRange.location)
501 #if MM_TS_PARANOIA_LOG
502 NSLog(@"INTERNAL ERROR [%s] Out of bounds", _cmd);
507 NSAttributedString *srcString = [attribString
508 attributedSubstringFromRange:srcRange];
509 [attribString replaceCharactersInRange:destRange
510 withAttributedString:srcString];
511 [self edited:(NSTextStorageEditedCharacters
512 | NSTextStorageEditedAttributes) range:destRange
513 changeInLength:([srcString length]-destRange.length)];
516 rowCache[destRow].length += [srcString length] - destRange.length;
520 NSRange emptyRange = {0,width};
521 NSAttributedString *emptyString =
522 [emptyRowString attributedSubstringFromRange:emptyRange];
523 NSDictionary *attribs = [NSDictionary dictionaryWithObjectsAndKeys:
524 font, NSFontAttributeName,
525 color, NSBackgroundColorAttributeName, nil];
527 for (i = 0; i < count; ++i, --destRow) {
530 destRange = [self charRangeForRow:destRow column:&acol cells:&acells];
531 #if MM_TS_PARANOIA_LOG
532 if (acells != width || acol != left)
533 NSLog(@"INTERNAL ERROR [%s]", _cmd);
535 if (NSNotFound == destRange.location) {
536 #if MM_TS_PARANOIA_LOG
537 NSLog(@"INTERNAL ERROR [%s] Out of bounds", _cmd);
542 [attribString replaceCharactersInRange:destRange
543 withAttributedString:emptyString];
544 [attribString setAttributes:attribs
545 range:NSMakeRange(destRange.location, width)];
547 [self edited:(NSTextStorageEditedAttributes
548 | NSTextStorageEditedCharacters) range:destRange
549 changeInLength:([emptyString length]-destRange.length)];
552 rowCache[destRow].length += [emptyString length] - destRange.length;
557 - (void)clearBlockFromRow:(int)row1 column:(int)col1 toRow:(int)row2
558 column:(int)col2 color:(NSColor *)color
560 //NSLog(@"clearBlockFromRow:%d column:%d toRow:%d column:%d color:%@",
561 // row1, col1, row2, col2, color);
562 [self lazyResize:NO];
564 if (row1 < 0 || row2 >= maxRows || col1 < 0 || col2 > maxColumns)
567 NSDictionary *attribs = [NSDictionary dictionaryWithObjectsAndKeys:
568 font, NSFontAttributeName,
569 color, NSBackgroundColorAttributeName, nil];
570 int cells = col2 - col1 + 1;
571 NSRange range, emptyRange = {0, cells};
572 NSAttributedString *emptyString =
573 [emptyRowString attributedSubstringFromRange:emptyRange];
576 for (r=row1; r<=row2; ++r) {
579 range = [self charRangeForRow:r column:&acol cells:&acells];
580 #if MM_TS_PARANOIA_LOG
581 if (acells != cells || acol != col1)
582 NSLog(@"INTERNAL ERROR [%s]", _cmd);
584 if (NSNotFound == range.location) {
585 #if MM_TS_PARANOIA_LOG
586 NSLog(@"INTERNAL ERROR [%s] Out of bounds", _cmd);
591 [attribString replaceCharactersInRange:range
592 withAttributedString:emptyString];
593 [attribString setAttributes:attribs
594 range:NSMakeRange(range.location, cells)];
596 [self edited:(NSTextStorageEditedAttributes
597 | NSTextStorageEditedCharacters) range:range
598 changeInLength:cells-range.length];
601 rowCache[r].length += cells - range.length;
608 //NSLog(@"%s", _cmd);
609 [self lazyResize:YES];
612 - (void)setDefaultColorsBackground:(NSColor *)bgColor
613 foreground:(NSColor *)fgColor
615 if (defaultBackgroundColor != bgColor) {
616 [defaultBackgroundColor release];
617 defaultBackgroundColor = bgColor ? [bgColor retain] : nil;
620 // NOTE: The default foreground color isn't actually used for anything, but
621 // other class instances might want to be able to access it so it is stored
623 if (defaultForegroundColor != fgColor) {
624 [defaultForegroundColor release];
625 defaultForegroundColor = fgColor ? [fgColor retain] : nil;
629 - (void)setFont:(NSFont*)newFont
631 if (newFont && font != newFont) {
632 [boldItalicFont release];
633 [italicFont release];
637 // NOTE! When setting a new font we make sure that the advancement of
638 // each glyph is fixed.
640 float em = [newFont widthOfString:@"m"];
641 float cellWidthMultiplier = [[NSUserDefaults standardUserDefaults]
642 floatForKey:MMCellWidthMultiplierKey];
644 // NOTE! Even though NSFontFixedAdvanceAttribute is a float, it will
645 // only render at integer sizes. Hence, we restrict the cell width to
646 // an integer here, otherwise the window width and the actual text
647 // width will not match.
648 cellSize.width = ceilf(em * cellWidthMultiplier);
650 float pointSize = [newFont pointSize];
651 NSDictionary *dict = [NSDictionary
652 dictionaryWithObject:[NSNumber numberWithFloat:cellSize.width]
653 forKey:NSFontFixedAdvanceAttribute];
655 NSFontDescriptor *desc = [newFont fontDescriptor];
656 desc = [desc fontDescriptorByAddingAttributes:dict];
657 font = [NSFont fontWithDescriptor:desc size:pointSize];
660 NSLayoutManager *lm = [[self layoutManagers] objectAtIndex:0];
661 cellSize.height = linespace + (lm ? [lm defaultLineHeightForFont:font]
662 : [font defaultLineHeightForFont]);
664 // NOTE: The font manager does not care about the 'font fixed advance'
665 // attribute, so after converting the font we have to add this
667 boldFont = [[NSFontManager sharedFontManager]
668 convertFont:font toHaveTrait:NSBoldFontMask];
669 desc = [boldFont fontDescriptor];
670 desc = [desc fontDescriptorByAddingAttributes:dict];
671 boldFont = [NSFont fontWithDescriptor:desc size:pointSize];
674 italicFont = [[NSFontManager sharedFontManager]
675 convertFont:font toHaveTrait:NSItalicFontMask];
676 desc = [italicFont fontDescriptor];
677 desc = [desc fontDescriptorByAddingAttributes:dict];
678 italicFont = [NSFont fontWithDescriptor:desc size:pointSize];
681 boldItalicFont = [[NSFontManager sharedFontManager]
682 convertFont:italicFont toHaveTrait:NSBoldFontMask];
683 desc = [boldItalicFont fontDescriptor];
684 desc = [desc fontDescriptorByAddingAttributes:dict];
685 boldItalicFont = [NSFont fontWithDescriptor:desc size:pointSize];
686 [boldItalicFont retain];
690 - (void)setWideFont:(NSFont *)newFont
693 // Use the normal font as the wide font (note that the normal font may
694 // very well include wide characters.)
695 if (font) [self setWideFont:font];
696 } else if (newFont != fontWide) {
697 [boldItalicFontWide release];
698 [italicFontWide release];
699 [boldFontWide release];
702 float pointSize = [newFont pointSize];
703 NSFontDescriptor *desc = [newFont fontDescriptor];
704 NSDictionary *dictWide = [NSDictionary
705 dictionaryWithObject:[NSNumber numberWithFloat:2*cellSize.width]
706 forKey:NSFontFixedAdvanceAttribute];
708 desc = [desc fontDescriptorByAddingAttributes:dictWide];
709 fontWide = [NSFont fontWithDescriptor:desc size:pointSize];
712 boldFontWide = [[NSFontManager sharedFontManager]
713 convertFont:fontWide toHaveTrait:NSBoldFontMask];
714 desc = [boldFontWide fontDescriptor];
715 desc = [desc fontDescriptorByAddingAttributes:dictWide];
716 boldFontWide = [NSFont fontWithDescriptor:desc size:pointSize];
717 [boldFontWide retain];
719 italicFontWide = [[NSFontManager sharedFontManager]
720 convertFont:fontWide toHaveTrait:NSItalicFontMask];
721 desc = [italicFontWide fontDescriptor];
722 desc = [desc fontDescriptorByAddingAttributes:dictWide];
723 italicFontWide = [NSFont fontWithDescriptor:desc size:pointSize];
724 [italicFontWide retain];
726 boldItalicFontWide = [[NSFontManager sharedFontManager]
727 convertFont:italicFontWide toHaveTrait:NSBoldFontMask];
728 desc = [boldItalicFontWide fontDescriptor];
729 desc = [desc fontDescriptorByAddingAttributes:dictWide];
730 boldItalicFontWide = [NSFont fontWithDescriptor:desc size:pointSize];
731 [boldItalicFontWide retain];
740 - (NSColor *)defaultBackgroundColor
742 return defaultBackgroundColor;
745 - (NSColor *)defaultForegroundColor
747 return defaultForegroundColor;
752 return NSMakeSize(maxColumns*cellSize.width, maxRows*cellSize.height);
760 - (NSRect)rectForRowsInRange:(NSRange)range
762 NSRect rect = { 0, 0, 0, 0 };
763 unsigned start = range.location > maxRows ? maxRows : range.location;
764 unsigned length = range.length;
766 if (start+length > maxRows)
767 length = maxRows - start;
769 rect.origin.y = cellSize.height * start;
770 rect.size.height = cellSize.height * length;
775 - (NSRect)rectForColumnsInRange:(NSRange)range
777 NSRect rect = { 0, 0, 0, 0 };
778 unsigned start = range.location > maxColumns ? maxColumns : range.location;
779 unsigned length = range.length;
781 if (start+length > maxColumns)
782 length = maxColumns - start;
784 rect.origin.x = cellSize.width * start;
785 rect.size.width = cellSize.width * length;
790 - (unsigned)characterIndexForRow:(int)row column:(int)col
793 NSRange range = [self charRangeForRow:row column:&col cells:&cells];
794 return range.location != NSNotFound ? range.location : 0;
797 // XXX: unused at the moment
798 - (BOOL)resizeToFitSize:(NSSize)size
800 int rows = maxRows, cols = maxColumns;
802 [self fitToSize:size rows:&rows columns:&cols];
803 if (rows != maxRows || cols != maxColumns) {
804 [self setMaxRows:rows columns:cols];
808 // Return NO only if dimensions did not change.
812 - (NSSize)fitToSize:(NSSize)size
814 return [self fitToSize:size rows:NULL columns:NULL];
817 - (NSSize)fitToSize:(NSSize)size rows:(int *)rows columns:(int *)columns
819 NSSize curSize = [self size];
820 NSSize fitSize = curSize;
821 int fitRows = maxRows;
822 int fitCols = maxColumns;
824 if (size.height < curSize.height) {
825 // Remove lines until the height of the text storage fits inside
826 // 'size'. However, always make sure there are at least 3 lines in the
827 // text storage. (Why 3? It seem Vim never allows less than 3 lines.)
829 // TODO: No need to search since line height is fixed, just calculate
831 int rowCount = maxRows;
833 for (rowsToRemove = 0; rowsToRemove < maxRows-3; ++rowsToRemove) {
834 float height = cellSize.height*rowCount;
836 if (height <= size.height) {
837 fitSize.height = height;
844 fitRows -= rowsToRemove;
845 } else if (size.height > curSize.height) {
846 float fh = cellSize.height;
847 if (fh < 1.0f) fh = 1.0f;
849 fitRows = floor(size.height/fh);
850 fitSize.height = fh*fitRows;
853 if (size.width != curSize.width) {
854 float fw = cellSize.width;
855 if (fw < 1.0f) fw = 1.0f;
857 fitCols = floor(size.width/fw);
858 fitSize.width = fw*fitCols;
861 if (rows) *rows = fitRows;
862 if (columns) *columns = fitCols;
867 - (NSRect)boundingRectForCharacterAtRow:(int)row column:(int)col
870 // This properly computes the position of where Vim expects the glyph to be
871 // drawn. Had the typesetter actually computed the right position of each
872 // character and not hidden some, this code would be correct.
873 NSRect rect = NSZeroRect;
875 rect.origin.x = col*cellSize.width;
876 rect.origin.y = row*cellSize.height;
877 rect.size = cellSize;
879 // Wide character take up twice the width of a normal character.
881 NSRange r = [self charRangeForRow:row column:&col cells:&cells];
882 if (NSNotFound != r.location
883 && [attribString attribute:MMWideCharacterAttributeName
886 rect.size.width += rect.size.width;
890 // Use layout manager to compute bounding rect. This works in situations
891 // where the layout manager decides to hide glyphs (Vim assumes all glyphs
893 NSLayoutManager *lm = [[self layoutManagers] objectAtIndex:0];
894 NSTextContainer *tc = [[lm textContainers] objectAtIndex:0];
896 NSRange range = [self charRangeForRow:row column:&col cells:&cells];
897 NSRange glyphRange = [lm glyphRangeForCharacterRange:range
898 actualCharacterRange:NULL];
900 return [lm boundingRectForGlyphRange:glyphRange inTextContainer:tc];
905 - (MMRowCacheEntry *)rowCache
911 @end // MMTextStorage
916 @implementation MMTextStorage (Private)
918 - (void)lazyResize:(BOOL)force
920 // Do nothing if the dimensions are already right.
921 if (!force && actualRows == maxRows && actualColumns == maxColumns)
924 NSRange oldRange = NSMakeRange(0, [attribString length]);
926 actualRows = maxRows;
927 actualColumns = maxColumns;
928 characterEqualsColumn = YES;
932 rowCache = (MMRowCacheEntry*)calloc(actualRows, sizeof(MMRowCacheEntry));
936 if (defaultBackgroundColor) {
937 dict = [NSDictionary dictionaryWithObjectsAndKeys:
938 font, NSFontAttributeName,
939 defaultBackgroundColor, NSBackgroundColorAttributeName, nil];
941 dict = [NSDictionary dictionaryWithObjectsAndKeys:
942 font, NSFontAttributeName, nil];
945 NSMutableString *rowString = [NSMutableString string];
947 for (i = 0; i < maxColumns; ++i) {
948 [rowString appendString:@" "];
950 [rowString appendString:@"\n"];
952 [emptyRowString release];
953 emptyRowString = [[NSAttributedString alloc] initWithString:rowString
956 [attribString release];
957 attribString = [[NSMutableAttributedString alloc] init];
958 for (i=0; i<maxRows; ++i) {
960 rowCache[i].length = actualColumns + 1;
962 [attribString appendAttributedString:emptyRowString];
965 NSRange fullRange = NSMakeRange(0, [attribString length]);
966 [self edited:(NSTextStorageEditedCharacters|NSTextStorageEditedAttributes)
967 range:oldRange changeInLength:fullRange.length-oldRange.length];
970 - (NSRange)charRangeForRow:(int)row column:(int*)pcol cells:(int*)pcells
975 // If no wide chars are used and if every char has length 1 (no composing
976 // characters, no > 16 bit characters), then we can compute the range.
977 if (characterEqualsColumn)
978 return NSMakeRange(row*(actualColumns+1) + col, cells);
980 NSString *string = [attribString string];
981 NSRange r, range = { NSNotFound, 0 };
982 unsigned idx, rowEnd;
985 if (row < 0 || row >= actualRows || col < 0 || col >= actualColumns
986 || col+cells > actualColumns) {
987 #if MM_TS_PARANOIA_LOG
988 NSLog(@"%s row=%d col=%d cells=%d is out of range (length=%d)",
989 _cmd, row, col, cells, [string length]);
995 // Locate the beginning of the row
996 MMRowCacheEntry *cache = rowCache;
998 for (i = 0; i < row; ++i, ++cache)
999 idx += cache->length;
1001 rowEnd = idx + cache->length;
1003 // Locate the beginning of the row by scanning for EOL characters.
1005 for (i = 0; i < row; ++i) {
1006 r.length = [string length] - r.location;
1007 r = [string rangeOfString:@"\n" options:NSLiteralSearch range:r];
1008 if (NSNotFound == r.location)
1014 // Locate the column
1015 #if MM_USE_ROW_CACHE
1016 cache = &rowCache[row];
1021 idx += cache->colOffset;
1023 range.location = idx;
1025 #if 0 // Backward search seems to be broken...
1027 if (col < i - col) {
1028 // Search forward from beginning of line.
1030 } else if (actualColumns - col < col - i) {
1031 // Search backward from end of line.
1032 i = actualColumns - 1;
1033 idx += cache->length - 2;
1035 // Search from cache spot (forward or backward).
1036 idx += cache->colOffset;
1042 r = [string rangeOfComposedCharacterSequenceAtIndex:idx];
1044 // Wide chars take up two display cells.
1045 if ([attribString attribute:MMWideCharacterAttributeName
1047 effectiveRange:nil])
1053 } else if (col < i) {
1056 r = [string rangeOfComposedCharacterSequenceAtIndex:idx-1];
1060 // Wide chars take up two display cells.
1061 if ([attribString attribute:MMWideCharacterAttributeName
1063 effectiveRange:nil])
1070 cache->colOffset = idx - range.location;
1074 // Search forward from beginning of line.
1077 // Search forward from cache spot.
1078 idx += cache->colOffset;
1083 r = [string rangeOfComposedCharacterSequenceAtIndex:idx];
1085 // Wide chars take up two display cells.
1086 if ([attribString attribute:MMWideCharacterAttributeName
1088 effectiveRange:nil])
1097 cache->colOffset = idx - range.location;
1102 for (i = 0; i < col; ++i) {
1103 r = [string rangeOfComposedCharacterSequenceAtIndex:idx];
1105 // Wide chars take up two display cells.
1106 if ([attribString attribute:MMWideCharacterAttributeName
1108 effectiveRange:nil])
1115 // Count the number of characters that cover the cells.
1116 range.location = idx;
1117 for (i = 0; i < cells; ++i) {
1118 r = [string rangeOfComposedCharacterSequenceAtIndex:idx];
1120 // Wide chars take up two display cells.
1121 if ([attribString attribute:MMWideCharacterAttributeName
1123 effectiveRange:nil])
1127 range.length += r.length;
1132 #if MM_TS_PARANOIA_LOG
1133 #if MM_USE_ROW_CACHE
1134 if (range.location >= rowEnd-1) {
1135 NSLog(@"INTERNAL ERROR [%s] : row=%d col=%d cells=%d --> range=%@",
1136 _cmd, row, col, cells, NSStringFromRange(range));
1137 range.location = rowEnd - 2;
1139 } else if (NSMaxRange(range) >= rowEnd) {
1140 NSLog(@"INTERNAL ERROR [%s] : row=%d col=%d cells=%d --> range=%@",
1141 _cmd, row, col, cells, NSStringFromRange(range));
1142 range.length = rowEnd - range.location - 1;
1146 if (NSMaxRange(range) > [string length]) {
1147 NSLog(@"INTERNAL ERROR [%s] : row=%d col=%d cells=%d --> range=%@",
1148 _cmd, row, col, cells, NSStringFromRange(range));
1149 range.location = NSNotFound;
1157 - (void)fixInvalidCharactersInRange:(NSRange)range
1159 static NSCharacterSet *invalidCharacterSet = nil;
1160 NSRange invalidRange;
1163 if (!invalidCharacterSet)
1164 invalidCharacterSet = [[NSCharacterSet characterSetWithRange:
1165 NSMakeRange(0x2028, 2)] retain];
1167 // HACK! Replace characters that the text system can't handle (currently
1168 // LINE SEPARATOR U+2028 and PARAGRAPH SEPARATOR U+2029) with space.
1170 // TODO: Treat these separately inside of Vim so we don't have to bother
1172 while (range.length > 0) {
1173 invalidRange = [[attribString string]
1174 rangeOfCharacterFromSet:invalidCharacterSet
1175 options:NSLiteralSearch
1177 if (NSNotFound == invalidRange.location)
1180 [attribString replaceCharactersInRange:invalidRange withString:@" "];
1182 end = NSMaxRange(invalidRange);
1183 range.length -= end - range.location;
1184 range.location = end;
1188 @end // MMTextStorage (Private)