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 #import "MMTextStorage.h"
22 // TODO: What does DRAW_TRANSP flag do? If the background isn't drawn when
23 // this flag is set, then sometimes the character after the cursor becomes
24 // blank. Everything seems to work fine by just ignoring this flag.
25 #define DRAW_TRANSP 0x01 /* draw with transparant bg */
26 #define DRAW_BOLD 0x02 /* draw bold text */
27 #define DRAW_UNDERL 0x04 /* draw underline text */
28 #define DRAW_UNDERC 0x08 /* draw undercurl text */
29 #define DRAW_ITALIC 0x10 /* draw italic text */
30 #define DRAW_CURSOR 0x20
33 static NSString *MMWideCharacterAttributeName = @"MMWideChar";
38 @interface MMTextStorage (Private)
39 - (void)lazyResize:(BOOL)force;
40 - (NSRange)charRangeForRow:(int)row column:(int)col cells:(int)cells;
41 - (void)fixInvalidCharactersInRange:(NSRange)range;
46 @implementation MMTextStorage
50 if ((self = [super init])) {
51 attribString = [[NSMutableAttributedString alloc] initWithString:@""];
52 // NOTE! It does not matter which font is set here, Vim will set its
53 // own font on startup anyway. Just set some bogus values.
54 font = [[NSFont userFixedPitchFontOfSize:0] retain];
55 cellSize.height = [font pointSize];
56 cellSize.width = [font defaultLineHeightForFont];
70 [emptyRowString release];
71 [boldItalicFontWide release];
72 [italicFontWide release];
73 [boldFontWide release];
75 [boldItalicFont release];
79 [defaultBackgroundColor release];
80 [defaultForegroundColor release];
81 [attribString release];
87 return [attribString string];
90 - (NSDictionary *)attributesAtIndex:(unsigned)index
91 effectiveRange:(NSRangePointer)range
93 if (index >= [attribString length]) {
95 *range = NSMakeRange(NSNotFound, 0);
97 return [NSDictionary dictionary];
100 return [attribString attributesAtIndex:index effectiveRange:range];
103 - (id)attribute:(NSString *)attrib atIndex:(unsigned)index
104 effectiveRange:(NSRangePointer)range
106 return [attribString attribute:attrib atIndex:index effectiveRange:range];
109 - (void)replaceCharactersInRange:(NSRange)range
110 withString:(NSString *)string
112 NSLog(@"WARNING: calling %s on MMTextStorage is unsupported", _cmd);
113 //[attribString replaceCharactersInRange:range withString:string];
116 - (void)setAttributes:(NSDictionary *)attributes range:(NSRange)range
118 //NSLog(@"%s%@", _cmd, NSStringFromRange(range));
120 // NOTE! This method must be implemented since the text system calls it
121 // constantly to 'fix attributes', apply font substitution, etc.
123 [attribString setAttributes:attributes range:range];
125 // HACK! If the font attribute is being modified, then ensure that the new
126 // font has a fixed advancement which is either the same as the current
127 // font or twice that, depending on whether it is a 'wide' character that
128 // is being fixed or not.
130 // TODO: This code assumes that the characters in 'range' all have the same
132 NSFont *newFont = [attributes objectForKey:NSFontAttributeName];
134 // Allow disabling of font substitution via a user default. Not
135 // recommended since the typesetter hides the corresponding glyphs and
136 // the display gets messed up.
137 if ([[NSUserDefaults standardUserDefaults]
138 boolForKey:MMNoFontSubstitutionKey])
141 float adv = cellSize.width;
142 if ([attribString attribute:MMWideCharacterAttributeName
143 atIndex:range.location
144 effectiveRange:NULL])
147 // Create a new font which has the 'fixed advance attribute' set.
148 NSDictionary *dict = [NSDictionary dictionaryWithObjectsAndKeys:
149 [NSNumber numberWithFloat:adv], NSFontFixedAdvanceAttribute, nil];
150 NSFontDescriptor *desc = [newFont fontDescriptor];
151 desc = [desc fontDescriptorByAddingAttributes:dict];
152 newFont = [NSFont fontWithDescriptor:desc size:[newFont pointSize]];
154 // Now modify the 'attributes' dictionary to hold the new font.
155 NSMutableDictionary *newAttr = [NSMutableDictionary
156 dictionaryWithDictionary:attributes];
157 [newAttr setObject:newFont forKey:NSFontAttributeName];
159 [attribString setAttributes:newAttr range:range];
161 //NSLog(@"NOT fixing font attribute!");
162 [attribString setAttributes:attributes range:range];
184 return actualColumns;
192 - (void)setLinespace:(float)newLinespace
194 NSLayoutManager *lm = [[self layoutManagers] objectAtIndex:0];
196 linespace = newLinespace;
198 // NOTE: The linespace is added to the cell height in order for a multiline
199 // selection not to have white (background color) gaps between lines. Also
200 // this simplifies the code a lot because there is no need to check the
201 // linespace when calculating the size of the text view etc. When the
202 // linespace is non-zero the baseline will be adjusted as well; check
204 cellSize.height = linespace + (lm ? [lm defaultLineHeightForFont:font]
205 : [font defaultLineHeightForFont]);
208 - (void)getMaxRows:(int*)rows columns:(int*)cols
210 if (rows) *rows = maxRows;
211 if (cols) *cols = maxColumns;
214 - (void)setMaxRows:(int)rows columns:(int)cols
216 // NOTE: Just remember the new values, the actual resizing is done lazily.
221 - (void)drawString:(NSString *)string atRow:(int)row column:(int)col
222 cells:(int)cells withFlags:(int)flags
223 foregroundColor:(NSColor *)fg backgroundColor:(NSColor *)bg
224 specialColor:(NSColor *)sp
226 //NSLog(@"replaceString:atRow:%d column:%d withFlags:%d "
227 // "foreground:%@ background:%@ special:%@",
228 // row, col, flags, fg, bg, sp);
229 [self lazyResize:NO];
231 if (row < 0 || row >= maxRows || col < 0 || col >= maxColumns
232 || col+cells > maxColumns || !string || !(fg && bg && sp))
235 // Find range of characters in text storage to replace.
236 NSRange range = [self charRangeForRow:row column:col cells:cells];
237 if (NSMaxRange(range) > [[attribString string] length]) {
238 NSLog(@"%s Out of bounds");
242 // Create dictionary of attributes to apply to the new characters.
243 NSFont *theFont = font;
244 if (flags & DRAW_WIDE) {
245 if (flags & DRAW_BOLD)
246 theFont = flags & DRAW_ITALIC ? boldItalicFontWide : boldFontWide;
247 else if (flags & DRAW_ITALIC)
248 theFont = italicFontWide;
252 if (flags & DRAW_BOLD)
253 theFont = flags & DRAW_ITALIC ? boldItalicFont : boldFont;
254 else if (flags & DRAW_ITALIC)
255 theFont = italicFont;
258 NSMutableDictionary *attributes =
259 [NSMutableDictionary dictionaryWithObjectsAndKeys:
260 theFont, NSFontAttributeName,
261 bg, NSBackgroundColorAttributeName,
262 fg, NSForegroundColorAttributeName,
263 sp, NSUnderlineColorAttributeName,
266 if (flags & DRAW_UNDERL) {
267 NSNumber *value = [NSNumber numberWithInt:(NSUnderlineStyleSingle
268 | NSUnderlinePatternSolid)]; // | NSUnderlineByWordMask
269 [attributes setObject:value forKey:NSUnderlineStyleAttributeName];
272 if (flags & DRAW_UNDERC) {
273 // TODO: figure out how do draw proper undercurls
274 NSNumber *value = [NSNumber numberWithInt:(NSUnderlineStyleThick
275 | NSUnderlinePatternDot)]; // | NSUnderlineByWordMask
276 [attributes setObject:value forKey:NSUnderlineStyleAttributeName];
279 // Mark these characters as wide. This attribute is subsequently checked
280 // when translating (row,col) pairs to offsets within 'attribString'.
281 if (flags & DRAW_WIDE)
282 [attributes setObject:[NSNull null]
283 forKey:MMWideCharacterAttributeName];
285 // Replace characters in text storage and apply new attributes.
286 NSRange r = NSMakeRange(range.location, [string length]);
287 [attribString replaceCharactersInRange:range withString:string];
288 [attribString setAttributes:attributes range:r];
290 if ((flags & DRAW_WIDE) || [string length] != cells)
291 characterEqualsColumn = NO;
293 [self fixInvalidCharactersInRange:r];
295 [self edited:(NSTextStorageEditedCharacters|NSTextStorageEditedAttributes)
296 range:range changeInLength:[string length]-range.length];
299 rowCache[row].length += [string length] - range.length;
304 * Delete 'count' lines from 'row' and insert 'count' empty lines at the bottom
305 * of the scroll region.
307 - (void)deleteLinesFromRow:(int)row lineCount:(int)count
308 scrollBottom:(int)bottom left:(int)left right:(int)right
309 color:(NSColor *)color
311 //NSLog(@"deleteLinesFromRow:%d lineCount:%d color:%@", row, count, color);
312 [self lazyResize:NO];
314 if (row < 0 || row+count > maxRows)
317 int total = 1 + bottom - row;
318 int move = total - count;
319 int width = right - left + 1;
321 NSRange destRange, srcRange;
324 for (i = 0; i < move; ++i, ++destRow) {
325 destRange = [self charRangeForRow:destRow column:left cells:width];
326 srcRange = [self charRangeForRow:(destRow+count) column:left
328 NSAttributedString *srcString = [attribString
329 attributedSubstringFromRange:srcRange];
331 [attribString replaceCharactersInRange:destRange
332 withAttributedString:srcString];
333 [self edited:(NSTextStorageEditedCharacters
334 | NSTextStorageEditedAttributes) range:destRange
335 changeInLength:([srcString length]-destRange.length)];
338 rowCache[destRow].length += [srcString length] - destRange.length;
342 NSRange emptyRange = {0,width};
343 NSAttributedString *emptyString =
344 [emptyRowString attributedSubstringFromRange:emptyRange];
345 NSDictionary *attribs = [NSDictionary dictionaryWithObjectsAndKeys:
346 font, NSFontAttributeName,
347 color, NSBackgroundColorAttributeName, nil];
349 for (i = 0; i < count; ++i, ++destRow) {
350 destRange = [self charRangeForRow:destRow column:left cells:width];
352 [attribString replaceCharactersInRange:destRange
353 withAttributedString:emptyString];
354 [attribString setAttributes:attribs
355 range:NSMakeRange(destRange.location, width)];
357 [self edited:(NSTextStorageEditedAttributes
358 | NSTextStorageEditedCharacters) range:destRange
359 changeInLength:([emptyString length]-destRange.length)];
362 rowCache[destRow].length += [emptyString length] - destRange.length;
368 * Insert 'count' empty lines at 'row' and delete 'count' lines from the bottom
369 * of the scroll region.
371 - (void)insertLinesAtRow:(int)row lineCount:(int)count
372 scrollBottom:(int)bottom left:(int)left right:(int)right
373 color:(NSColor *)color
375 //NSLog(@"insertLinesAtRow:%d lineCount:%d color:%@", row, count, color);
376 [self lazyResize:NO];
378 if (row < 0 || row+count > maxRows)
381 int total = 1 + bottom - row;
382 int move = total - count;
383 int width = right - left + 1;
384 int destRow = bottom;
385 int srcRow = row + move - 1;
386 NSRange destRange, srcRange;
389 for (i = 0; i < move; ++i, --destRow, --srcRow) {
390 destRange = [self charRangeForRow:destRow column:left cells:width];
391 srcRange = [self charRangeForRow:srcRow column:left cells:width];
392 NSAttributedString *srcString = [attribString
393 attributedSubstringFromRange:srcRange];
394 [attribString replaceCharactersInRange:destRange
395 withAttributedString:srcString];
396 [self edited:(NSTextStorageEditedCharacters
397 | NSTextStorageEditedAttributes) range:destRange
398 changeInLength:([srcString length]-destRange.length)];
401 rowCache[destRow].length += [srcString length] - destRange.length;
405 NSRange emptyRange = {0,width};
406 NSAttributedString *emptyString =
407 [emptyRowString attributedSubstringFromRange:emptyRange];
408 NSDictionary *attribs = [NSDictionary dictionaryWithObjectsAndKeys:
409 font, NSFontAttributeName,
410 color, NSBackgroundColorAttributeName, nil];
412 for (i = 0; i < count; ++i, --destRow) {
413 destRange = [self charRangeForRow:destRow column:left cells:width];
415 [attribString replaceCharactersInRange:destRange
416 withAttributedString:emptyString];
417 [attribString setAttributes:attribs
418 range:NSMakeRange(destRange.location, width)];
420 [self edited:(NSTextStorageEditedAttributes
421 | NSTextStorageEditedCharacters) range:destRange
422 changeInLength:([emptyString length]-destRange.length)];
425 rowCache[destRow].length += [emptyString length] - destRange.length;
430 - (void)clearBlockFromRow:(int)row1 column:(int)col1 toRow:(int)row2
431 column:(int)col2 color:(NSColor *)color
433 //NSLog(@"clearBlockFromRow:%d column:%d toRow:%d column:%d color:%@",
434 // row1, col1, row2, col2, color);
435 [self lazyResize:NO];
437 if (row1 < 0 || row2 >= maxRows || col1 < 0 || col2 > maxColumns)
440 NSDictionary *attribs = [NSDictionary dictionaryWithObjectsAndKeys:
441 font, NSFontAttributeName,
442 color, NSBackgroundColorAttributeName, nil];
443 int cells = col2 - col1 + 1;
444 NSRange range, emptyRange = {0, cells};
445 NSAttributedString *emptyString =
446 [emptyRowString attributedSubstringFromRange:emptyRange];
449 for (r=row1; r<=row2; ++r) {
450 range = [self charRangeForRow:r column:col1 cells:cells];
452 [attribString replaceCharactersInRange:range
453 withAttributedString:emptyString];
454 [attribString setAttributes:attribs
455 range:NSMakeRange(range.location, cells)];
457 [self edited:(NSTextStorageEditedAttributes
458 | NSTextStorageEditedCharacters) range:range
459 changeInLength:cells-range.length];
462 rowCache[r].length += cells - range.length;
469 //NSLog(@"%s", _cmd);
470 [self lazyResize:YES];
473 - (void)setDefaultColorsBackground:(NSColor *)bgColor
474 foreground:(NSColor *)fgColor
476 if (defaultBackgroundColor != bgColor) {
477 [defaultBackgroundColor release];
478 defaultBackgroundColor = bgColor ? [bgColor retain] : nil;
481 // NOTE: The default foreground color isn't actually used for anything, but
482 // other class instances might want to be able to access it so it is stored
484 if (defaultForegroundColor != fgColor) {
485 [defaultForegroundColor release];
486 defaultForegroundColor = fgColor ? [fgColor retain] : nil;
490 - (void)setFont:(NSFont*)newFont
492 if (newFont && font != newFont) {
493 [boldItalicFont release];
494 [italicFont release];
498 // NOTE! When setting a new font we make sure that the advancement of
499 // each glyph is fixed.
501 float em = [newFont widthOfString:@"m"];
502 float cellWidthMultiplier = [[NSUserDefaults standardUserDefaults]
503 floatForKey:MMCellWidthMultiplierKey];
505 // NOTE! Even though NSFontFixedAdvanceAttribute is a float, it will
506 // only render at integer sizes. Hence, we restrict the cell width to
507 // an integer here, otherwise the window width and the actual text
508 // width will not match.
509 cellSize.width = ceilf(em * cellWidthMultiplier);
511 float pointSize = [newFont pointSize];
512 NSDictionary *dict = [NSDictionary
513 dictionaryWithObject:[NSNumber numberWithFloat:cellSize.width]
514 forKey:NSFontFixedAdvanceAttribute];
516 NSFontDescriptor *desc = [newFont fontDescriptor];
517 desc = [desc fontDescriptorByAddingAttributes:dict];
518 font = [NSFont fontWithDescriptor:desc size:pointSize];
521 NSLayoutManager *lm = [[self layoutManagers] objectAtIndex:0];
522 cellSize.height = linespace + (lm ? [lm defaultLineHeightForFont:font]
523 : [font defaultLineHeightForFont]);
525 // NOTE: The font manager does not care about the 'font fixed advance'
526 // attribute, so after converting the font we have to add this
528 boldFont = [[NSFontManager sharedFontManager]
529 convertFont:font toHaveTrait:NSBoldFontMask];
530 desc = [boldFont fontDescriptor];
531 desc = [desc fontDescriptorByAddingAttributes:dict];
532 boldFont = [NSFont fontWithDescriptor:desc size:pointSize];
535 italicFont = [[NSFontManager sharedFontManager]
536 convertFont:font toHaveTrait:NSItalicFontMask];
537 desc = [italicFont fontDescriptor];
538 desc = [desc fontDescriptorByAddingAttributes:dict];
539 italicFont = [NSFont fontWithDescriptor:desc size:pointSize];
542 boldItalicFont = [[NSFontManager sharedFontManager]
543 convertFont:italicFont toHaveTrait:NSBoldFontMask];
544 desc = [boldItalicFont fontDescriptor];
545 desc = [desc fontDescriptorByAddingAttributes:dict];
546 boldItalicFont = [NSFont fontWithDescriptor:desc size:pointSize];
547 [boldItalicFont retain];
551 - (void)setWideFont:(NSFont *)newFont
554 // Use the normal font as the wide font (note that the normal font may
555 // very well include wide characters.)
556 if (font) [self setWideFont:font];
557 } else if (newFont != fontWide) {
558 [boldItalicFontWide release];
559 [italicFontWide release];
560 [boldFontWide release];
563 float pointSize = [newFont pointSize];
564 NSFontDescriptor *desc = [newFont fontDescriptor];
565 NSDictionary *dictWide = [NSDictionary
566 dictionaryWithObject:[NSNumber numberWithFloat:2*cellSize.width]
567 forKey:NSFontFixedAdvanceAttribute];
569 desc = [desc fontDescriptorByAddingAttributes:dictWide];
570 fontWide = [NSFont fontWithDescriptor:desc size:pointSize];
573 boldFontWide = [[NSFontManager sharedFontManager]
574 convertFont:fontWide toHaveTrait:NSBoldFontMask];
575 desc = [boldFontWide fontDescriptor];
576 desc = [desc fontDescriptorByAddingAttributes:dictWide];
577 boldFontWide = [NSFont fontWithDescriptor:desc size:pointSize];
578 [boldFontWide retain];
580 italicFontWide = [[NSFontManager sharedFontManager]
581 convertFont:fontWide toHaveTrait:NSItalicFontMask];
582 desc = [italicFontWide fontDescriptor];
583 desc = [desc fontDescriptorByAddingAttributes:dictWide];
584 italicFontWide = [NSFont fontWithDescriptor:desc size:pointSize];
585 [italicFontWide retain];
587 boldItalicFontWide = [[NSFontManager sharedFontManager]
588 convertFont:italicFontWide toHaveTrait:NSBoldFontMask];
589 desc = [boldItalicFontWide fontDescriptor];
590 desc = [desc fontDescriptorByAddingAttributes:dictWide];
591 boldItalicFontWide = [NSFont fontWithDescriptor:desc size:pointSize];
592 [boldItalicFontWide retain];
601 - (NSColor *)defaultBackgroundColor
603 return defaultBackgroundColor;
606 - (NSColor *)defaultForegroundColor
608 return defaultForegroundColor;
613 return NSMakeSize(maxColumns*cellSize.width, maxRows*cellSize.height);
621 - (NSRect)rectForRowsInRange:(NSRange)range
623 NSRect rect = { 0, 0, 0, 0 };
624 unsigned start = range.location > maxRows ? maxRows : range.location;
625 unsigned length = range.length;
627 if (start+length > maxRows)
628 length = maxRows - start;
630 rect.origin.y = cellSize.height * start;
631 rect.size.height = cellSize.height * length;
636 - (NSRect)rectForColumnsInRange:(NSRange)range
638 NSRect rect = { 0, 0, 0, 0 };
639 unsigned start = range.location > maxColumns ? maxColumns : range.location;
640 unsigned length = range.length;
642 if (start+length > maxColumns)
643 length = maxColumns - start;
645 rect.origin.x = cellSize.width * start;
646 rect.size.width = cellSize.width * length;
651 - (unsigned)characterIndexForRow:(int)row column:(int)col
653 NSRange range = [self charRangeForRow:row column:col cells:1];
654 return range.location != NSNotFound ? range.location : 0;
657 // XXX: unused at the moment
658 - (BOOL)resizeToFitSize:(NSSize)size
660 int rows = maxRows, cols = maxColumns;
662 [self fitToSize:size rows:&rows columns:&cols];
663 if (rows != maxRows || cols != maxColumns) {
664 [self setMaxRows:rows columns:cols];
668 // Return NO only if dimensions did not change.
672 - (NSSize)fitToSize:(NSSize)size
674 return [self fitToSize:size rows:NULL columns:NULL];
677 - (NSSize)fitToSize:(NSSize)size rows:(int *)rows columns:(int *)columns
679 NSSize curSize = [self size];
680 NSSize fitSize = curSize;
681 int fitRows = maxRows;
682 int fitCols = maxColumns;
684 if (size.height < curSize.height) {
685 // Remove lines until the height of the text storage fits inside
686 // 'size'. However, always make sure there are at least 3 lines in the
687 // text storage. (Why 3? It seem Vim never allows less than 3 lines.)
689 // TODO: No need to search since line height is fixed, just calculate
691 int rowCount = maxRows;
693 for (rowsToRemove = 0; rowsToRemove < maxRows-3; ++rowsToRemove) {
694 float height = cellSize.height*rowCount;
696 if (height <= size.height) {
697 fitSize.height = height;
704 fitRows -= rowsToRemove;
705 } else if (size.height > curSize.height) {
706 float fh = cellSize.height;
707 if (fh < 1.0f) fh = 1.0f;
709 fitRows = floor(size.height/fh);
710 fitSize.height = fh*fitRows;
713 if (size.width != curSize.width) {
714 float fw = cellSize.width;
715 if (fw < 1.0f) fw = 1.0f;
717 fitCols = floor(size.width/fw);
718 fitSize.width = fw*fitCols;
721 if (rows) *rows = fitRows;
722 if (columns) *columns = fitCols;
727 - (NSRect)boundingRectForCharacterAtRow:(int)row column:(int)col
730 // This properly computes the position of where Vim expects the glyph to be
731 // drawn. Had the typesetter actually computed the right position of each
732 // character and not hidden some, this code would be correct.
733 NSRect rect = NSZeroRect;
735 rect.origin.x = col*cellSize.width;
736 rect.origin.y = row*cellSize.height;
737 rect.size = cellSize;
739 // Wide character take up twice the width of a normal character.
740 NSRange r = [self charRangeForRow:row column:col cells:1];
741 if (NSNotFound != r.location
742 && [attribString attribute:MMWideCharacterAttributeName
745 rect.size.width += rect.size.width;
749 // Use layout manager to compute bounding rect. This works in situations
750 // where the layout manager decides to hide glyphs (Vim assumes all glyphs
752 NSLayoutManager *lm = [[self layoutManagers] objectAtIndex:0];
753 NSTextContainer *tc = [[lm textContainers] objectAtIndex:0];
754 NSRange range = [self charRangeForRow:row column:col cells:1];
755 NSRange glyphRange = [lm glyphRangeForCharacterRange:range
756 actualCharacterRange:NULL];
758 return [lm boundingRectForGlyphRange:glyphRange inTextContainer:tc];
763 - (MMRowCacheEntry *)rowCache
769 @end // MMTextStorage
774 @implementation MMTextStorage (Private)
776 - (void)lazyResize:(BOOL)force
778 // Do nothing if the dimensions are already right.
779 if (!force && actualRows == maxRows && actualColumns == maxColumns)
782 NSRange oldRange = NSMakeRange(0, [attribString length]);
784 actualRows = maxRows;
785 actualColumns = maxColumns;
786 characterEqualsColumn = YES;
790 rowCache = (MMRowCacheEntry*)calloc(actualRows, sizeof(MMRowCacheEntry));
794 if (defaultBackgroundColor) {
795 dict = [NSDictionary dictionaryWithObjectsAndKeys:
796 font, NSFontAttributeName,
797 defaultBackgroundColor, NSBackgroundColorAttributeName, nil];
799 dict = [NSDictionary dictionaryWithObjectsAndKeys:
800 font, NSFontAttributeName, nil];
803 NSMutableString *rowString = [NSMutableString string];
805 for (i = 0; i < maxColumns; ++i) {
806 [rowString appendString:@" "];
808 [rowString appendString:@"\n"];
810 [emptyRowString release];
811 emptyRowString = [[NSAttributedString alloc] initWithString:rowString
814 [attribString release];
815 attribString = [[NSMutableAttributedString alloc] init];
816 for (i=0; i<maxRows; ++i) {
818 rowCache[i].length = actualColumns + 1;
820 [attribString appendAttributedString:emptyRowString];
823 NSRange fullRange = NSMakeRange(0, [attribString length]);
824 [self edited:(NSTextStorageEditedCharacters|NSTextStorageEditedAttributes)
825 range:oldRange changeInLength:fullRange.length-oldRange.length];
828 - (NSRange)charRangeForRow:(int)row column:(int)col cells:(int)cells
830 // If no wide chars are used and if every char has length 1 (no composing
831 // characters, no > 16 bit characters), then we can compute the range.
832 if (characterEqualsColumn)
833 return NSMakeRange(row*(actualColumns+1) + col, cells);
835 NSString *string = [attribString string];
836 NSRange r, range = { NSNotFound, 0 };
840 if (row < 0 || row >= actualRows || col < 0 || col >= actualColumns
841 || col+cells > actualColumns) {
842 NSLog(@"%s row=%d col=%d cells=%d is out of range (length=%d)",
843 _cmd, row, col, cells, [string length]);
848 // Locate the beginning of the row
849 MMRowCacheEntry *cache = rowCache;
851 for (i = 0; i < row; ++i, ++cache)
852 idx += cache->length;
854 // Locate the beginning of the row by scanning for EOL characters.
856 for (i = 0; i < row; ++i) {
857 r.length = [string length] - r.location;
858 r = [string rangeOfString:@"\n" options:NSLiteralSearch range:r];
859 if (NSNotFound == r.location)
867 cache = &rowCache[row];
872 idx += cache->colOffset;
874 range.location = idx;
878 // Search forward from beginning of line.
880 } else if (actualColumns - col < col - i) {
881 // Search backward from end of line.
882 i = actualColumns - 1;
883 idx += cache->length - 2;
885 // Search from cache spot (forward or backward).
886 idx += cache->colOffset;
892 r = [string rangeOfComposedCharacterSequenceAtIndex:idx];
894 // Wide chars take up two display cells.
895 if ([attribString attribute:MMWideCharacterAttributeName
903 } else if (col < i) {
906 r = [string rangeOfComposedCharacterSequenceAtIndex:idx-1];
910 // Wide chars take up two display cells.
911 if ([attribString attribute:MMWideCharacterAttributeName
919 cache->colOffset = idx - range.location;
923 for (i = 0; i < col; ++i) {
924 r = [string rangeOfComposedCharacterSequenceAtIndex:idx];
926 // Wide chars take up two display cells.
927 if ([attribString attribute:MMWideCharacterAttributeName
936 // Count the number of characters that cover the cells.
937 range.location = idx;
938 for (i = 0; i < cells; ++i) {
939 r = [string rangeOfComposedCharacterSequenceAtIndex:idx];
941 // Wide chars take up two display cells.
942 if ([attribString attribute:MMWideCharacterAttributeName
948 range.length += r.length;
954 - (void)fixInvalidCharactersInRange:(NSRange)range
956 static NSCharacterSet *invalidCharacterSet = nil;
957 NSRange invalidRange;
960 if (!invalidCharacterSet)
961 invalidCharacterSet = [[NSCharacterSet characterSetWithRange:
962 NSMakeRange(0x2028, 2)] retain];
964 // HACK! Replace characters that the text system can't handle (currently
965 // LINE SEPARATOR U+2028 and PARAGRAPH SEPARATOR U+2029) with space.
967 // TODO: Treat these separately inside of Vim so we don't have to bother
969 while (range.length > 0) {
970 invalidRange = [[attribString string]
971 rangeOfCharacterFromSet:invalidCharacterSet
972 options:NSLiteralSearch
974 if (NSNotFound == invalidRange.location)
977 [attribString replaceCharactersInRange:invalidRange withString:@" "];
979 end = NSMaxRange(invalidRange);
980 range.length -= end - range.location;
981 range.location = end;
985 @end // MMTextStorage (Private)