Improved marked text handling
[MacVim/jjgod.git] / src / MacVim / MMTextStorage.m
blobd4fabf14772a1ffcaae5ed9238d3ea577dfb972b
1 /* vi:set ts=8 sts=4 sw=4 ft=objc:
2  *
3  * VIM - Vi IMproved            by Bram Moolenaar
4  *                              MacVim GUI port by Bjorn Winckler
5  *
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.
9  */
11  * MMTextStorage
12  *
13  * Text rendering related code.
14  */
16 #import "MMTextStorage.h"
17 #import "MacVim.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;
42 @end
46 @implementation MMTextStorage
48 - (id)init
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];
57     }
59     return self;
62 - (void)dealloc
64 #if MM_USE_ROW_CACHE
65     if (rowCache) {
66         free(rowCache);
67         rowCache = NULL;
68     }
69 #endif
70     [emptyRowString release];
71     [boldItalicFontWide release];
72     [italicFontWide release];
73     [boldFontWide release];
74     [fontWide release];
75     [boldItalicFont release];
76     [italicFont release];
77     [boldFont release];
78     [font release];
79     [defaultBackgroundColor release];
80     [defaultForegroundColor release];
81     [attribString release];
82     [super dealloc];
85 - (NSString *)string
87     return [attribString string];
90 - (NSDictionary *)attributesAtIndex:(unsigned)index
91                      effectiveRange:(NSRangePointer)range
93     if (index >= [attribString length]) {
94         if (range)
95             *range = NSMakeRange(NSNotFound, 0);
97         return [NSDictionary dictionary];
98     }
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.
122 #if 0
123     [attribString setAttributes:attributes range:range];
124 #elif 1
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.
129     //
130     // TODO: This code assumes that the characters in 'range' all have the same
131     // width.
132     NSFont *newFont = [attributes objectForKey:NSFontAttributeName];
133     if (newFont) {
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])
139             return;
141         float adv = cellSize.width;
142         if ([attribString attribute:MMWideCharacterAttributeName
143                             atIndex:range.location
144                      effectiveRange:NULL])
145             adv += adv;
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];
160     } else {
161         //NSLog(@"NOT fixing font attribute!");
162         [attribString setAttributes:attributes range:range];
163     }
164 #endif
167 - (int)maxRows
169     return maxRows;
172 - (int)maxColumns
174     return maxColumns;
177 - (int)actualRows
179     return actualRows;
182 - (int)actualColumns
184     return actualColumns;
187 - (float)linespace
189     return linespace;
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
203     // MMTypesetter.
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.
217     maxRows = rows;
218     maxColumns = cols;
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))
233         return;
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");
239         return;
240     }
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;
249         else
250             theFont = fontWide;
251     } else {
252         if (flags & DRAW_BOLD)
253             theFont = flags & DRAW_ITALIC ? boldItalicFont : boldFont;
254         else if (flags & DRAW_ITALIC)
255             theFont = italicFont;
256     }
258     NSMutableDictionary *attributes =
259         [NSMutableDictionary dictionaryWithObjectsAndKeys:
260             theFont, NSFontAttributeName,
261             bg, NSBackgroundColorAttributeName,
262             fg, NSForegroundColorAttributeName,
263             sp, NSUnderlineColorAttributeName,
264             nil];
266     if (flags & DRAW_UNDERL) {
267         NSNumber *value = [NSNumber numberWithInt:(NSUnderlineStyleSingle
268                 | NSUnderlinePatternSolid)]; // | NSUnderlineByWordMask
269         [attributes setObject:value forKey:NSUnderlineStyleAttributeName];
270     }
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];
277     }
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];
298 #if MM_USE_ROW_CACHE
299     rowCache[row].length += [string length] - range.length;
300 #endif
304  * Delete 'count' lines from 'row' and insert 'count' empty lines at the bottom
305  * of the scroll region.
306  */
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)
315         return;
317     int total = 1 + bottom - row;
318     int move = total - count;
319     int width = right - left + 1;
320     int destRow = row;
321     NSRange destRange, srcRange;
322     int i;
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
327                                    cells:width];
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)];
337 #if MM_USE_ROW_CACHE
338         rowCache[destRow].length += [srcString length] - destRange.length;
339 #endif
340     }
341     
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)];
361 #if MM_USE_ROW_CACHE
362         rowCache[destRow].length += [emptyString length] - destRange.length;
363 #endif
364     }
368  * Insert 'count' empty lines at 'row' and delete 'count' lines from the bottom
369  * of the scroll region.
370  */
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)
379         return;
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;
387     int i;
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)];
400 #if MM_USE_ROW_CACHE
401         rowCache[destRow].length += [srcString length] - destRange.length;
402 #endif
403     }
404     
405     NSRange emptyRange = {0,width};
406     NSAttributedString *emptyString =
407             [emptyRowString attributedSubstringFromRange:emptyRange];
408     NSDictionary *attribs = [NSDictionary dictionaryWithObjectsAndKeys:
409             font, NSFontAttributeName,
410             color, NSBackgroundColorAttributeName, nil];
411     
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)];
424 #if MM_USE_ROW_CACHE
425         rowCache[destRow].length += [emptyString length] - destRange.length;
426 #endif
427     }
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)
438         return;
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];
447     int r;
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];
461 #if MM_USE_ROW_CACHE
462         rowCache[r].length += cells - range.length;
463 #endif
464     }
467 - (void)clearAll
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;
479     }
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
483     // here.
484     if (defaultForegroundColor != fgColor) {
485         [defaultForegroundColor release];
486         defaultForegroundColor = fgColor ? [fgColor retain] : nil;
487     }
490 - (void)setFont:(NSFont*)newFont
492     if (newFont && font != newFont) {
493         [boldItalicFont release];
494         [italicFont release];
495         [boldFont release];
496         [font 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];
519         [font retain];
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
527         // attribute again.
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];
533         [boldFont retain];
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];
540         [italicFont retain];
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];
548     }
551 - (void)setWideFont:(NSFont *)newFont
553     if (!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];
561         [fontWide 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];
571         [fontWide retain];
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];
593     }
596 - (NSFont*)font
598     return font;
601 - (NSColor *)defaultBackgroundColor
603     return defaultBackgroundColor;
606 - (NSColor *)defaultForegroundColor
608     return defaultForegroundColor;
611 - (NSSize)size
613     return NSMakeSize(maxColumns*cellSize.width, maxRows*cellSize.height);
616 - (NSSize)cellSize
618     return cellSize;
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;
633     return rect;
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;
648     return rect;
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];
665         return YES;
666     }
668     // Return NO only if dimensions did not change.
669     return NO;
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.)
688         //
689         // TODO: No need to search since line height is fixed, just calculate
690         // the new height.
691         int rowCount = maxRows;
692         int rowsToRemove;
693         for (rowsToRemove = 0; rowsToRemove < maxRows-3; ++rowsToRemove) {
694             float height = cellSize.height*rowCount;
696             if (height <= size.height) {
697                 fitSize.height = height;
698                 break;
699             }
701             --rowCount;
702         }
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;
711     }
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;
719     }
721     if (rows) *rows = fitRows;
722     if (columns) *columns = fitCols;
724     return fitSize;
727 - (NSRect)boundingRectForCharacterAtRow:(int)row column:(int)col
729 #if 1
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
743                                atIndex:r.location
744                         effectiveRange:nil])
745         rect.size.width += rect.size.width;
747     return rect;
748 #else
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
751     // are drawn).
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];
759 #endif
762 #if MM_USE_ROW_CACHE
763 - (MMRowCacheEntry *)rowCache
765     return rowCache;
767 #endif
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)
780         return;
782     NSRange oldRange = NSMakeRange(0, [attribString length]);
784     actualRows = maxRows;
785     actualColumns = maxColumns;
786     characterEqualsColumn = YES;
788 #if MM_USE_ROW_CACHE
789     free(rowCache);
790     rowCache = (MMRowCacheEntry*)calloc(actualRows, sizeof(MMRowCacheEntry));
791 #endif
793     NSDictionary *dict;
794     if (defaultBackgroundColor) {
795         dict = [NSDictionary dictionaryWithObjectsAndKeys:
796                 font, NSFontAttributeName,
797                 defaultBackgroundColor, NSBackgroundColorAttributeName, nil];
798     } else {
799         dict = [NSDictionary dictionaryWithObjectsAndKeys:
800                 font, NSFontAttributeName, nil];
801     }
802             
803     NSMutableString *rowString = [NSMutableString string];
804     int i;
805     for (i = 0; i < maxColumns; ++i) {
806         [rowString appendString:@" "];
807     }
808     [rowString appendString:@"\n"];
810     [emptyRowString release];
811     emptyRowString = [[NSAttributedString alloc] initWithString:rowString
812                                                      attributes:dict];
814     [attribString release];
815     attribString = [[NSMutableAttributedString alloc] init];
816     for (i=0; i<maxRows; ++i) {
817 #if MM_USE_ROW_CACHE
818         rowCache[i].length = actualColumns + 1;
819 #endif
820         [attribString appendAttributedString:emptyRowString];
821     }
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 };
837     unsigned idx;
838     int i;
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]);
844         return range;
845     }
847 #if MM_USE_ROW_CACHE
848     // Locate the beginning of the row
849     MMRowCacheEntry *cache = rowCache;
850     idx = 0;
851     for (i = 0; i < row; ++i, ++cache)
852         idx += cache->length;
853 #else
854     // Locate the beginning of the row by scanning for EOL characters.
855     r.location = 0;
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)
860             return range;
861         ++r.location;
862     }
863 #endif
865     // Locate the column
866 #if MM_USE_ROW_CACHE
867     cache = &rowCache[row];
869     i = cache->col;
870     if (col == i) {
871         // Cache hit
872         idx += cache->colOffset;
873     } else {
874         range.location = idx;
876         // Cache miss
877         if (col < i - col) {
878             // Search forward from beginning of line.
879             i = 0;
880         } else if (actualColumns - col < col - i) {
881             // Search backward from end of line.
882             i = actualColumns - 1;
883             idx += cache->length - 2;
884         } else {
885             // Search from cache spot (forward or backward).
886             idx += cache->colOffset;
887         }
889         if (col > i) {
890             // Forward search
891             while (col > i) {
892                 r = [string rangeOfComposedCharacterSequenceAtIndex:idx];
894                 // Wide chars take up two display cells.
895                 if ([attribString attribute:MMWideCharacterAttributeName
896                                     atIndex:idx
897                              effectiveRange:nil])
898                     ++i;
900                 idx += r.length;
901                 ++i;
902             }
903         } else if (col < i) {
904             // Backward search
905             while (col < i) {
906                 r = [string rangeOfComposedCharacterSequenceAtIndex:idx-1];
907                 idx -= r.length;
908                 --i;
910                 // Wide chars take up two display cells.
911                 if ([attribString attribute:MMWideCharacterAttributeName
912                                     atIndex:idx
913                              effectiveRange:nil])
914                     --i;
915             }
916         }
918         cache->col = i;
919         cache->colOffset = idx - range.location;
920     }
921 #else
922     idx = r.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
928                             atIndex:idx
929                      effectiveRange:nil])
930             ++i;
932         idx += r.length;
933     }
934 #endif
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
943                             atIndex:idx
944                      effectiveRange:nil])
945             ++i;
947         idx += r.length;
948         range.length += r.length;
949     }
951     return range;
954 - (void)fixInvalidCharactersInRange:(NSRange)range
956     static NSCharacterSet *invalidCharacterSet = nil;
957     NSRange invalidRange;
958     unsigned end;
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.
966     //
967     // TODO: Treat these separately inside of Vim so we don't have to bother
968     // here.
969     while (range.length > 0) {
970         invalidRange = [[attribString string]
971             rangeOfCharacterFromSet:invalidCharacterSet
972                             options:NSLiteralSearch
973                               range:range];
974         if (NSNotFound == invalidRange.location)
975             break;
977         [attribString replaceCharactersInRange:invalidRange withString:@" "];
979         end = NSMaxRange(invalidRange);
980         range.length -= end - range.location;
981         range.location = end;
982     }
985 @end // MMTextStorage (Private)