Cache last menu which was searched for a menu item (improves search times)
[MacVim/jjgod.git] / MMTextStorage.m
blobaf82156e72b10fb8ccf584da43753c807a40102a
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 #import "MMTextStorage.h"
12 #import "MacVim.h"
17 // TODO: support DRAW_TRANSP flag
18 #define DRAW_TRANSP               0x01    /* draw with transparant bg */
19 #define DRAW_BOLD                 0x02    /* draw bold text */
20 #define DRAW_UNDERL               0x04    /* draw underline text */
21 #define DRAW_UNDERC               0x08    /* draw undercurl text */
22 #define DRAW_ITALIC               0x10    /* draw italic text */
27 @interface MMTextStorage (Private)
28 - (void)lazyResize:(BOOL)force;
29 @end
33 @implementation MMTextStorage
35 - (id)init
37     if ((self = [super init])) {
38         attribString = [[NSMutableAttributedString alloc] initWithString:@""];
39         // NOTE!  It does not matter which font is set here, Vim will set its
40         // own font on startup anyway.  Just set some bogus values.
41         font = [[NSFont userFixedPitchFontOfSize:0] retain];
42         boldFont = [font retain];
43         italicFont = [font retain];
44         boldItalicFont = [font retain];
45         cellSize.height = [font pointSize];
46         cellSize.width = [font defaultLineHeightForFont];
47     }
49     return self;
52 - (void)dealloc
54     //NSLog(@"%@ %s", [self className], _cmd);
56     [emptyRowString release];
57     [boldItalicFont release];
58     [italicFont release];
59     [boldFont release];
60     [font release];
61     [defaultBackgroundColor release];
62     [defaultForegroundColor release];
63     [attribString release];
64     [super dealloc];
67 - (NSString *)string
69     //NSLog(@"%s : attribString=%@", _cmd, attribString);
70     return [attribString string];
73 - (NSDictionary *)attributesAtIndex:(unsigned)index
74                      effectiveRange:(NSRangePointer)range
76     //NSLog(@"%s", _cmd);
77     if (index>=[attribString length]) {
78         //NSLog(@"%sWARNING: index (%d) out of bounds", _cmd, index);
79         if (range) {
80             *range = NSMakeRange(NSNotFound, 0);
81         }
82         return [NSDictionary dictionary];
83     }
85     return [attribString attributesAtIndex:index effectiveRange:range];
88 - (void)replaceCharactersInRange:(NSRange)range
89                       withString:(NSString *)string
91     //NSLog(@"replaceCharactersInRange:(%d,%d) withString:%@", range.location,
92     //        range.length, string);
93     NSLog(@"WARNING: calling %s on MMTextStorage is unsupported", _cmd);
94     //[attribString replaceCharactersInRange:range withString:string];
97 - (void)setAttributes:(NSDictionary *)attributes range:(NSRange)range
99     // NOTE!  This method must be implemented since the text system calls it
100     // constantly to 'fix attributes', apply font substitution, etc.
101 #if 0
102     [attribString setAttributes:attributes range:range];
103 #else
104     // HACK! If the font attribute is being modified, then ensure that the new
105     // font has a fixed advancement which is either the same as the current
106     // font or twice that, depending on whether it is a 'wide' character that
107     // is being fixed or not.  This code really only works if 'range' has
108     // length 1 or 2.
109     NSFont *newFont = [attributes objectForKey:NSFontAttributeName];
110     if (newFont) {
111         float adv = cellSize.width;
112         if ([attribString length] > range.location+1) {
113             // If the first char is followed by zero-width space, then it is a
114             // 'wide' character, so double the advancement.
115             NSString *string = [attribString string];
116             if ([string characterAtIndex:range.location+1] == 0x200b)
117                 adv += adv;
118         }
120         // Create a new font which has the 'fixed advance attribute' set.
121         NSDictionary *dict = [NSDictionary dictionaryWithObjectsAndKeys:
122             [NSNumber numberWithFloat:adv], NSFontFixedAdvanceAttribute, nil];
123         NSFontDescriptor *desc = [newFont fontDescriptor];
124         desc = [desc fontDescriptorByAddingAttributes:dict];
125         newFont = [NSFont fontWithDescriptor:desc size:[newFont pointSize]];
127         // Now modify the 'attributes' dictionary to hold the new font.
128         NSMutableDictionary *newAttr = [NSMutableDictionary
129             dictionaryWithDictionary:attributes];
130         [newAttr setObject:newFont forKey:NSFontAttributeName];
132         [attribString setAttributes:newAttr range:range];
133     } else {
134         [attribString setAttributes:attributes range:range];
135     }
136 #endif
139 - (int)maxRows
141     return maxRows;
144 - (int)maxColumns
146     return maxColumns;
149 - (int)actualRows
151     return actualRows;
154 - (int)actualColumns
156     return actualColumns;
159 - (float)linespace
161     return linespace;
164 - (void)setLinespace:(float)newLinespace
166     NSLayoutManager *lm = [[self layoutManagers] objectAtIndex:0];
168     linespace = newLinespace;
170     // NOTE: The linespace is added to the cell height in order for a multiline
171     // selection not to have white (background color) gaps between lines.  Also
172     // this simplifies the code a lot because there is no need to check the
173     // linespace when calculating the size of the text view etc.  When the
174     // linespace is non-zero the baseline will be adjusted as well; check
175     // MMTypesetter.
176     cellSize.height = linespace + (lm ? [lm defaultLineHeightForFont:font]
177                                       : [font defaultLineHeightForFont]);
180 - (void)getMaxRows:(int*)rows columns:(int*)cols
182     if (rows) *rows = maxRows;
183     if (cols) *cols = maxColumns;
186 - (void)setMaxRows:(int)rows columns:(int)cols
188     // NOTE: Just remember the new values, the actual resizing is done lazily.
189     maxRows = rows;
190     maxColumns = cols;
193 - (void)replaceString:(NSString *)string atRow:(int)row column:(int)col
194             withFlags:(int)flags foregroundColor:(NSColor *)fg
195       backgroundColor:(NSColor *)bg specialColor:(NSColor *)sp
197     //NSLog(@"replaceString:atRow:%d column:%d withFlags:%d "
198     //          "foreground:%@ background:%@ special:%@",
199     //          row, col, flags, fg, bg, sp);
200     [self lazyResize:NO];
202     // TODO: support DRAW_TRANSP
203     if (flags & DRAW_TRANSP)
204         return;
206     if (row < 0 || row >= maxRows || col < 0 || col >= maxColumns
207             || col+[string length] > maxColumns) {
208         //NSLog(@"[%s] WARNING : out of range, row=%d (%d) col=%d (%d) "
209         //        "length=%d (%d)", _cmd, row, maxRows, col, maxColumns,
210         //        [string length], [attribString length]);
211         return;
212     }
214     // NOTE: If 'string' was initialized with bad data it might be nil; this
215     // may be due to 'enc' being set to an unsupported value, so don't print an
216     // error message or stdout will most likely get flooded.
217     if (!string) return;
219     if (!(fg && bg && sp)) {
220         NSLog(@"[%s] WARNING: background, foreground or special color not "
221                 "specified", _cmd);
222         return;
223     }
225     NSRange range = NSMakeRange(col+row*(maxColumns+1), [string length]);
226     [attribString replaceCharactersInRange:range withString:string];
228     NSFont *theFont = font;
229     if (flags & DRAW_BOLD)
230         theFont = flags & DRAW_ITALIC ? boldItalicFont : boldFont;
231     else if (flags & DRAW_ITALIC)
232         theFont = italicFont;
234     NSDictionary *attributes;
235     
236     if (flags & DRAW_UNDERC) {
237         // move the undercurl down a bit so it is visible
238         attributes = [NSDictionary dictionaryWithObjectsAndKeys:
239             theFont, NSFontAttributeName,
240             bg, NSBackgroundColorAttributeName,
241             fg, NSForegroundColorAttributeName,
242             sp, NSUnderlineColorAttributeName,
243             [NSNumber numberWithFloat:2],NSBaselineOffsetAttributeName,
244             nil];
245     } else {
246         attributes = [NSDictionary dictionaryWithObjectsAndKeys:
247             theFont, NSFontAttributeName,
248             bg, NSBackgroundColorAttributeName,
249             fg, NSForegroundColorAttributeName,
250             sp, NSUnderlineColorAttributeName,
251             nil];
252     }
253     [attribString setAttributes:attributes range:range];
255     if (flags & DRAW_UNDERL) {
256         NSNumber *value = [NSNumber numberWithInt:(NSUnderlineStyleSingle
257                 | NSUnderlinePatternSolid)]; // | NSUnderlineByWordMask
258         [attribString addAttribute:NSUnderlineStyleAttributeName
259                 value:value range:range];
260     }
262     // TODO: figure out how do draw proper undercurls
263     if (flags & DRAW_UNDERC) {
264         NSNumber *value = [NSNumber numberWithInt:(NSUnderlineStyleThick
265                 | NSUnderlinePatternDot)]; // | NSUnderlineByWordMask
266         [attribString addAttribute:NSUnderlineStyleAttributeName
267                 value:value range:range];
268     }
270     [self edited:(NSTextStorageEditedCharacters|NSTextStorageEditedAttributes)
271            range:range changeInLength:0];
275  * Delete 'count' lines from 'row' and insert 'count' empty lines at the bottom
276  * of the scroll region.
277  */
278 - (void)deleteLinesFromRow:(int)row lineCount:(int)count
279               scrollBottom:(int)bottom left:(int)left right:(int)right
280                      color:(NSColor *)color
282     //NSLog(@"deleteLinesFromRow:%d lineCount:%d color:%@", row, count, color);
283     [self lazyResize:NO];
285     if (row < 0 || row+count > maxRows) {
286         //NSLog(@"[%s] WARNING : out of range, row=%d (%d) count=%d", _cmd, row,
287         //        maxRows, count);
288         return;
289     }
291     int total = 1 + bottom - row;
292     int move = total - count;
293     int width = right - left + 1;
294     NSRange destRange = { row*(maxColumns+1) + left, width };
295     NSRange srcRange = { (row+count)*(maxColumns+1) + left, width };
296     int i;
298     if (width != maxColumns) {      // if this is the case, then left must be 0
299         for (i = 0; i < move; ++i) {
300             NSAttributedString *srcString = [attribString
301                     attributedSubstringFromRange:srcRange];
302             [attribString replaceCharactersInRange:destRange
303                               withAttributedString:srcString];
304             [self edited:(NSTextStorageEditedCharacters
305                     | NSTextStorageEditedAttributes)
306                     range:destRange changeInLength:0];
307             destRange.location += maxColumns+1;
308             srcRange.location += maxColumns+1;
309         }
310         
311         NSRange emptyRange = {0,width};
312         NSAttributedString *emptyString =
313                 [emptyRowString attributedSubstringFromRange: emptyRange];
314         NSDictionary *attribs = [NSDictionary dictionaryWithObjectsAndKeys:
315                 font, NSFontAttributeName,
316                 color, NSBackgroundColorAttributeName, nil];
318         for (i = 0; i < count; ++i) {
319             [attribString replaceCharactersInRange:destRange
320                               withAttributedString:emptyString];
321             [attribString setAttributes:attribs range:destRange];
322             [self edited:(NSTextStorageEditedAttributes
323                     | NSTextStorageEditedCharacters) range:destRange
324                                             changeInLength:0];
325             destRange.location += maxColumns+1;
326         }
327     } else {
328         NSRange delRange = {row*(maxColumns+1), count*(maxColumns+1)};
329         [attribString deleteCharactersInRange: delRange];
330         destRange.location += move*(maxColumns+1);
331         
332         NSDictionary *attribs = [NSDictionary dictionaryWithObjectsAndKeys:
333                 font, NSFontAttributeName,
334                 color, NSBackgroundColorAttributeName, nil];
335         destRange.length = maxColumns;
336         for (i = 0; i < count; ++i) {
337             [attribString insertAttributedString:emptyRowString
338                     atIndex:destRange.location];
339             [attribString setAttributes:attribs range:destRange];
340             destRange.location += maxColumns+1;
341         }
342         NSRange editedRange = {row*(maxColumns+1),total*(maxColumns+1)};
343         [self edited:(NSTextStorageEditedAttributes
344                 | NSTextStorageEditedCharacters) range:editedRange
345                                          changeInLength:0];
346     }
350  * Insert 'count' empty lines at 'row' and delete 'count' lines from the bottom
351  * of the scroll region.
352  */
353 - (void)insertLinesAtRow:(int)row lineCount:(int)count
354             scrollBottom:(int)bottom left:(int)left right:(int)right
355                    color:(NSColor *)color
357     //NSLog(@"insertLinesAtRow:%d lineCount:%d color:%@", row, count, color);
358     [self lazyResize:NO];
360     if (row < 0 || row+count > maxRows) {
361         //NSLog(@"[%s] WARNING : out of range, row=%d (%d) count=%d", _cmd, row,
362         //        maxRows, count);
363         return;
364     }
366     int total = 1 + bottom - row;
367     int move = total - count;
368     int width = right - left + 1;
369     NSRange destRange = { bottom*(maxColumns+1) + left, width };
370     NSRange srcRange = { (row+move-1)*(maxColumns+1) + left, width };
371     int i;
373     if (width != maxColumns) {      // if this is the case, then left must be 0
374         for (i = 0; i < move; ++i) {
375             NSAttributedString *srcString = [attribString
376                     attributedSubstringFromRange:srcRange];
377             [attribString replaceCharactersInRange:destRange
378                               withAttributedString:srcString];
379             [self edited:(NSTextStorageEditedCharacters
380                     | NSTextStorageEditedAttributes)
381                     range:destRange changeInLength:0];
382             destRange.location -= maxColumns+1;
383             srcRange.location -= maxColumns+1;
384         }
385         
386         NSRange emptyRange = {0,width};
387         NSAttributedString *emptyString =
388                 [emptyRowString attributedSubstringFromRange:emptyRange];
389         NSDictionary *attribs = [NSDictionary dictionaryWithObjectsAndKeys:
390                 font, NSFontAttributeName,
391                 color, NSBackgroundColorAttributeName, nil];
392         
393         for (i = 0; i < count; ++i) {
394             [attribString replaceCharactersInRange:destRange
395                               withAttributedString:emptyString];
396             [attribString setAttributes:attribs range:destRange];
397             [self edited:(NSTextStorageEditedAttributes
398                     | NSTextStorageEditedCharacters) range:destRange
399                                             changeInLength:0];
400             destRange.location -= maxColumns+1;
401         }
402     } else {
403         NSRange delRange = {(row+move)*(maxColumns+1),count*(maxColumns+1)};
404         [attribString deleteCharactersInRange: delRange];
406         NSDictionary *attribs = [NSDictionary dictionaryWithObjectsAndKeys:
407                 font, NSFontAttributeName,
408                 color, NSBackgroundColorAttributeName, nil];
409         
410         destRange.location = row*(maxColumns+1);
411         for (i = 0; i < count; ++i) {
412             [attribString insertAttributedString:emptyRowString
413                             atIndex:destRange.location];
414             [attribString setAttributes:attribs range:destRange];
415         }
416         NSRange editedRange = {row*(maxColumns+1),total*(maxColumns+1)};
417         [self edited:(NSTextStorageEditedAttributes
418                 | NSTextStorageEditedCharacters) range:editedRange
419                                         changeInLength:0];
420     }
423 - (void)clearBlockFromRow:(int)row1 column:(int)col1 toRow:(int)row2
424                    column:(int)col2 color:(NSColor *)color
426     //NSLog(@"clearBlockFromRow:%d column:%d toRow:%d column:%d color:%@",
427     //        row1, col1, row2, col2, color);
428     [self lazyResize:NO];
430     if (row1 < 0 || row2 >= maxRows || col1 < 0 || col2 > maxColumns) {
431         //NSLog(@"[%s] WARNING : out of range, row1=%d row2=%d (%d) col1=%d "
432         //        "col2=%d (%d)", _cmd, row1, row2, maxRows, col1, col2,
433         //        maxColumns);
434         return;
435     }
437     NSDictionary *attribs = [NSDictionary dictionaryWithObjectsAndKeys:
438             font, NSFontAttributeName,
439             color, NSBackgroundColorAttributeName, nil];
441     NSRange range = { row1*(maxColumns+1) + col1, col2-col1+1 };
442     
443     NSRange emptyRange = {0,col2-col1+1};
444     NSAttributedString *emptyString =
445             [emptyRowString attributedSubstringFromRange:emptyRange];
446     int r;
447     for (r=row1; r<=row2; ++r) {
448         [attribString replaceCharactersInRange:range
449                           withAttributedString:emptyString];
450         [attribString setAttributes:attribs range:range];
451         [self edited:(NSTextStorageEditedAttributes
452                 | NSTextStorageEditedCharacters) range:range
453                                         changeInLength:0];
454         range.location += maxColumns+1;
455     }
458 - (void)clearAll
460     //NSLog(@"%s%@", _cmd, color);
461     [self lazyResize:YES];
464 - (void)setDefaultColorsBackground:(NSColor *)bgColor
465                         foreground:(NSColor *)fgColor
467     //NSLog(@"setDefaultColorsBackground:%@ foreground:%@", bgColor, fgColor);
469     if (defaultBackgroundColor != bgColor) {
470         [defaultBackgroundColor release];
471         defaultBackgroundColor = bgColor ? [bgColor retain] : nil;
472     }
474     // NOTE: The default foreground color isn't actually used for anything, but
475     // other class instances might want to be able to access it so it is stored
476     // here.
477     if (defaultForegroundColor != fgColor) {
478         [defaultForegroundColor release];
479         defaultForegroundColor = fgColor ? [fgColor retain] : nil;
480     }
483 - (void)setFont:(NSFont*)newFont
485     if (newFont && font != newFont) {
486         [font release];
488         // NOTE! When setting a new font we make sure that the advancement of
489         // each glyph is fixed. 
491         float em = [newFont widthOfString:@"m"];
492         float cellWidthMultiplier = [[NSUserDefaults standardUserDefaults]
493                 floatForKey:MMCellWidthMultiplierKey];
495         // NOTE! Even though NSFontFixedAdvanceAttribute is a float, it will
496         // only render at integer sizes.  Hence, we restrict the cell width to
497         // an integer here, otherwise the window width and the actual text
498         // width will not match.
499         cellSize.width = ceilf(em * cellWidthMultiplier);
501         float pointSize = [newFont pointSize];
502         NSDictionary *dict = [NSDictionary
503             dictionaryWithObject:[NSNumber numberWithFloat:cellSize.width]
504                           forKey:NSFontFixedAdvanceAttribute];
506         NSFontDescriptor *desc = [newFont fontDescriptor];
507         desc = [desc fontDescriptorByAddingAttributes:dict];
508         font = [NSFont fontWithDescriptor:desc size:pointSize];
509         [font retain];
511         NSLayoutManager *lm = [[self layoutManagers] objectAtIndex:0];
512         cellSize.height = linespace + (lm ? [lm defaultLineHeightForFont:font]
513                                           : [font defaultLineHeightForFont]);
515         // NOTE: The font manager does not care about the 'font fixed advance'
516         // attribute, so after converting the font we have to add this
517         // attribute again.
518         boldFont = [[NSFontManager sharedFontManager]
519             convertFont:font toHaveTrait:NSBoldFontMask];
520         desc = [boldFont fontDescriptor];
521         desc = [desc fontDescriptorByAddingAttributes:dict];
522         boldFont = [NSFont fontWithDescriptor:desc size:pointSize];
523         [boldFont retain];
525         italicFont = [[NSFontManager sharedFontManager]
526             convertFont:font toHaveTrait:NSItalicFontMask];
527         desc = [italicFont fontDescriptor];
528         desc = [desc fontDescriptorByAddingAttributes:dict];
529         italicFont = [NSFont fontWithDescriptor:desc size:pointSize];
530         [italicFont retain];
532         boldItalicFont = [[NSFontManager sharedFontManager]
533             convertFont:italicFont toHaveTrait:NSBoldFontMask];
534         desc = [boldItalicFont fontDescriptor];
535         desc = [desc fontDescriptorByAddingAttributes:dict];
536         boldItalicFont = [NSFont fontWithDescriptor:desc size:pointSize];
537         [boldItalicFont retain];
538     }
541 - (NSFont*)font
543     return font;
546 - (NSColor *)defaultBackgroundColor
548     return defaultBackgroundColor;
551 - (NSColor *)defaultForegroundColor
553     return defaultForegroundColor;
556 - (NSSize)size
558     return NSMakeSize(maxColumns*cellSize.width, maxRows*cellSize.height);
561 - (NSSize)cellSize
563     return cellSize;
566 - (NSRect)rectForRowsInRange:(NSRange)range
568     NSRect rect = { 0, 0, 0, 0 };
569     unsigned start = range.location > maxRows ? maxRows : range.location;
570     unsigned length = range.length;
572     if (start+length > maxRows)
573         length = maxRows - start;
575     rect.origin.y = cellSize.height * start;
576     rect.size.height = cellSize.height * length;
578     return rect;
581 - (NSRect)rectForColumnsInRange:(NSRange)range
583     NSRect rect = { 0, 0, 0, 0 };
584     unsigned start = range.location > maxColumns ? maxColumns : range.location;
585     unsigned length = range.length;
587     if (start+length > maxColumns)
588         length = maxColumns - start;
590     rect.origin.x = cellSize.width * start;
591     rect.size.width = cellSize.width * length;
593     return rect;
596 - (unsigned)characterIndexForRow:(int)row column:(int)col
598     // Ensure the offset returned is valid.
599     // This code also works if maxRows and/or maxColumns is 0.
600     if (row >= maxRows) row = maxRows-1;
601     if (row < 0) row = 0;
602     if (col >= maxColumns) col = maxColumns-1;
603     if (col < 0) col = 0;
605     return (unsigned)(col + row*(maxColumns+1));
608 - (BOOL)resizeToFitSize:(NSSize)size
610     int rows = maxRows, cols = maxColumns;
612     [self fitToSize:size rows:&rows columns:&cols];
613     if (rows != maxRows || cols != maxColumns) {
614         [self setMaxRows:rows columns:cols];
615         return YES;
616     }
618     // Return NO only if dimensions did not change.
619     return NO;
622 - (NSSize)fitToSize:(NSSize)size
624     return [self fitToSize:size rows:NULL columns:NULL];
627 - (NSSize)fitToSize:(NSSize)size rows:(int *)rows columns:(int *)columns
629     NSSize curSize = [self size];
630     NSSize fitSize = curSize;
631     int fitRows = maxRows;
632     int fitCols = maxColumns;
634     if (size.height < curSize.height) {
635         // Remove lines until the height of the text storage fits inside
636         // 'size'.  However, always make sure there are at least 3 lines in the
637         // text storage.  (Why 3? It seem Vim never allows less than 3 lines.)
638         //
639         // TODO: No need to search since line height is fixed, just calculate
640         // the new height.
641         int rowCount = maxRows;
642         int rowsToRemove;
643         for (rowsToRemove = 0; rowsToRemove < maxRows-3; ++rowsToRemove) {
644             float height = cellSize.height*rowCount;
646             if (height <= size.height) {
647                 fitSize.height = height;
648                 break;
649             }
651             --rowCount;
652         }
654         fitRows -= rowsToRemove;
655     } else if (size.height > curSize.height) {
656         float fh = cellSize.height;
657         if (fh < 1.0f) fh = 1.0f;
659         fitRows = floor(size.height/fh);
660         fitSize.height = fh*fitRows;
661     }
663     if (size.width != curSize.width) {
664         float fw = cellSize.width;
665         if (fw < 1.0f) fw = 1.0f;
667         fitCols = floor(size.width/fw);
668         fitSize.width = fw*fitCols;
669     }
671     if (rows) *rows = fitRows;
672     if (columns) *columns = fitCols;
674     return fitSize;
677 @end // MMTextStorage
682 @implementation MMTextStorage (Private)
683 - (void)lazyResize:(BOOL)force
685     int i;
687     // Do nothing if the dimensions are already right.
688     if (!force && actualRows == maxRows && actualColumns == maxColumns)
689         return;
691     NSRange oldRange = NSMakeRange(0, actualRows*(actualColumns+1));
693     actualRows = maxRows;
694     actualColumns = maxColumns;
696     NSDictionary *dict;
697     if (defaultBackgroundColor) {
698         dict = [NSDictionary dictionaryWithObjectsAndKeys:
699                 font, NSFontAttributeName,
700                 defaultBackgroundColor, NSBackgroundColorAttributeName, nil];
701     } else {
702         dict = [NSDictionary dictionaryWithObjectsAndKeys:
703                 font, NSFontAttributeName, nil];
704     }
705             
706     NSMutableString *rowString = [NSMutableString string];
707     for (i = 0; i < maxColumns; ++i) {
708         [rowString appendString:@" "];
709     }
710     [rowString appendString:@"\n"];
712     [emptyRowString release];
713     emptyRowString = [[NSAttributedString alloc] initWithString:rowString
714                                                      attributes:dict];
716     [attribString release];
717     attribString = [[NSMutableAttributedString alloc] init];
718     for (i=0; i<maxRows; ++i) {
719         [attribString appendAttributedString:emptyRowString];
720     }
722     NSRange fullRange = NSMakeRange(0, [attribString length]);
723     [self edited:(NSTextStorageEditedCharacters|NSTextStorageEditedAttributes)
724            range:oldRange changeInLength:fullRange.length-oldRange.length];
727 @end // MMTextStorage (Private)