Fix cursor blinking in insert mode.
[MacVim/jjgod.git] / src / MacVim / MMTextStorage.m
blobc3deef9ee9628fb1a9af5634c69d0047b430c7c9
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     if (row < 0 || row >= maxRows || col < 0 || col >= maxColumns
203             || col+[string length] > maxColumns) {
204         //NSLog(@"[%s] WARNING : out of range, row=%d (%d) col=%d (%d) "
205         //        "length=%d (%d)", _cmd, row, maxRows, col, maxColumns,
206         //        [string length], [attribString length]);
207         return;
208     }
210     // NOTE: If 'string' was initialized with bad data it might be nil; this
211     // may be due to 'enc' being set to an unsupported value, so don't print an
212     // error message or stdout will most likely get flooded.
213     if (!string) return;
215     if (!(fg && bg && sp)) {
216         NSLog(@"[%s] WARNING: background, foreground or special color not "
217                 "specified", _cmd);
218         return;
219     }
221     NSRange range = NSMakeRange(col+row*(maxColumns+1), [string length]);
222     [attribString replaceCharactersInRange:range withString:string];
224     NSFont *theFont = font;
225     if (flags & DRAW_BOLD)
226         theFont = flags & DRAW_ITALIC ? boldItalicFont : boldFont;
227     else if (flags & DRAW_ITALIC)
228         theFont = italicFont;
230     NSDictionary *attributes;
231     
232     if (flags & DRAW_UNDERC) {
233         // move the undercurl down a bit so it is visible
234         attributes = [NSDictionary dictionaryWithObjectsAndKeys:
235             theFont, NSFontAttributeName,
236             bg, NSBackgroundColorAttributeName,
237             fg, NSForegroundColorAttributeName,
238             sp, NSUnderlineColorAttributeName,
239             [NSNumber numberWithFloat:2],NSBaselineOffsetAttributeName,
240             nil];
241     } else if (flags & DRAW_TRANSP) {
242         // Don't include background color when DRAW_TRANSP is set.
243         //
244         // NOTE: I'm not sure what the point of this is, but if DRAW_TRANSP
245         // calls are ignored, then cursor blinking in insert mode doesn't work.
246         attributes = [NSDictionary dictionaryWithObjectsAndKeys:
247             theFont, NSFontAttributeName,
248             fg, NSForegroundColorAttributeName,
249             sp, NSUnderlineColorAttributeName,
250             nil];
251     } else {
252         attributes = [NSDictionary dictionaryWithObjectsAndKeys:
253             theFont, NSFontAttributeName,
254             bg, NSBackgroundColorAttributeName,
255             fg, NSForegroundColorAttributeName,
256             sp, NSUnderlineColorAttributeName,
257             nil];
258     }
259     [attribString setAttributes:attributes range:range];
261     if (flags & DRAW_UNDERL) {
262         NSNumber *value = [NSNumber numberWithInt:(NSUnderlineStyleSingle
263                 | NSUnderlinePatternSolid)]; // | NSUnderlineByWordMask
264         [attribString addAttribute:NSUnderlineStyleAttributeName
265                 value:value range:range];
266     }
268     // TODO: figure out how do draw proper undercurls
269     if (flags & DRAW_UNDERC) {
270         NSNumber *value = [NSNumber numberWithInt:(NSUnderlineStyleThick
271                 | NSUnderlinePatternDot)]; // | NSUnderlineByWordMask
272         [attribString addAttribute:NSUnderlineStyleAttributeName
273                 value:value range:range];
274     }
276     [self edited:(NSTextStorageEditedCharacters|NSTextStorageEditedAttributes)
277            range:range changeInLength:0];
281  * Delete 'count' lines from 'row' and insert 'count' empty lines at the bottom
282  * of the scroll region.
283  */
284 - (void)deleteLinesFromRow:(int)row lineCount:(int)count
285               scrollBottom:(int)bottom left:(int)left right:(int)right
286                      color:(NSColor *)color
288     //NSLog(@"deleteLinesFromRow:%d lineCount:%d color:%@", row, count, color);
289     [self lazyResize:NO];
291     if (row < 0 || row+count > maxRows) {
292         //NSLog(@"[%s] WARNING : out of range, row=%d (%d) count=%d", _cmd, row,
293         //        maxRows, count);
294         return;
295     }
297     int total = 1 + bottom - row;
298     int move = total - count;
299     int width = right - left + 1;
300     NSRange destRange = { row*(maxColumns+1) + left, width };
301     NSRange srcRange = { (row+count)*(maxColumns+1) + left, width };
302     int i;
304     if (width != maxColumns) {      // if this is the case, then left must be 0
305         for (i = 0; i < move; ++i) {
306             NSAttributedString *srcString = [attribString
307                     attributedSubstringFromRange:srcRange];
308             [attribString replaceCharactersInRange:destRange
309                               withAttributedString:srcString];
310             [self edited:(NSTextStorageEditedCharacters
311                     | NSTextStorageEditedAttributes)
312                     range:destRange changeInLength:0];
313             destRange.location += maxColumns+1;
314             srcRange.location += maxColumns+1;
315         }
316         
317         NSRange emptyRange = {0,width};
318         NSAttributedString *emptyString =
319                 [emptyRowString attributedSubstringFromRange: emptyRange];
320         NSDictionary *attribs = [NSDictionary dictionaryWithObjectsAndKeys:
321                 font, NSFontAttributeName,
322                 color, NSBackgroundColorAttributeName, nil];
324         for (i = 0; i < count; ++i) {
325             [attribString replaceCharactersInRange:destRange
326                               withAttributedString:emptyString];
327             [attribString setAttributes:attribs range:destRange];
328             [self edited:(NSTextStorageEditedAttributes
329                     | NSTextStorageEditedCharacters) range:destRange
330                                             changeInLength:0];
331             destRange.location += maxColumns+1;
332         }
333     } else {
334         NSRange delRange = {row*(maxColumns+1), count*(maxColumns+1)};
335         [attribString deleteCharactersInRange: delRange];
336         destRange.location += move*(maxColumns+1);
337         
338         NSDictionary *attribs = [NSDictionary dictionaryWithObjectsAndKeys:
339                 font, NSFontAttributeName,
340                 color, NSBackgroundColorAttributeName, nil];
341         destRange.length = maxColumns;
342         for (i = 0; i < count; ++i) {
343             [attribString insertAttributedString:emptyRowString
344                     atIndex:destRange.location];
345             [attribString setAttributes:attribs range:destRange];
346             destRange.location += maxColumns+1;
347         }
348         NSRange editedRange = {row*(maxColumns+1),total*(maxColumns+1)};
349         [self edited:(NSTextStorageEditedAttributes
350                 | NSTextStorageEditedCharacters) range:editedRange
351                                          changeInLength:0];
352     }
356  * Insert 'count' empty lines at 'row' and delete 'count' lines from the bottom
357  * of the scroll region.
358  */
359 - (void)insertLinesAtRow:(int)row lineCount:(int)count
360             scrollBottom:(int)bottom left:(int)left right:(int)right
361                    color:(NSColor *)color
363     //NSLog(@"insertLinesAtRow:%d lineCount:%d color:%@", row, count, color);
364     [self lazyResize:NO];
366     if (row < 0 || row+count > maxRows) {
367         //NSLog(@"[%s] WARNING : out of range, row=%d (%d) count=%d", _cmd, row,
368         //        maxRows, count);
369         return;
370     }
372     int total = 1 + bottom - row;
373     int move = total - count;
374     int width = right - left + 1;
375     NSRange destRange = { bottom*(maxColumns+1) + left, width };
376     NSRange srcRange = { (row+move-1)*(maxColumns+1) + left, width };
377     int i;
379     if (width != maxColumns) {      // if this is the case, then left must be 0
380         for (i = 0; i < move; ++i) {
381             NSAttributedString *srcString = [attribString
382                     attributedSubstringFromRange:srcRange];
383             [attribString replaceCharactersInRange:destRange
384                               withAttributedString:srcString];
385             [self edited:(NSTextStorageEditedCharacters
386                     | NSTextStorageEditedAttributes)
387                     range:destRange changeInLength:0];
388             destRange.location -= maxColumns+1;
389             srcRange.location -= maxColumns+1;
390         }
391         
392         NSRange emptyRange = {0,width};
393         NSAttributedString *emptyString =
394                 [emptyRowString attributedSubstringFromRange:emptyRange];
395         NSDictionary *attribs = [NSDictionary dictionaryWithObjectsAndKeys:
396                 font, NSFontAttributeName,
397                 color, NSBackgroundColorAttributeName, nil];
398         
399         for (i = 0; i < count; ++i) {
400             [attribString replaceCharactersInRange:destRange
401                               withAttributedString:emptyString];
402             [attribString setAttributes:attribs range:destRange];
403             [self edited:(NSTextStorageEditedAttributes
404                     | NSTextStorageEditedCharacters) range:destRange
405                                             changeInLength:0];
406             destRange.location -= maxColumns+1;
407         }
408     } else {
409         NSRange delRange = {(row+move)*(maxColumns+1),count*(maxColumns+1)};
410         [attribString deleteCharactersInRange: delRange];
412         NSDictionary *attribs = [NSDictionary dictionaryWithObjectsAndKeys:
413                 font, NSFontAttributeName,
414                 color, NSBackgroundColorAttributeName, nil];
415         
416         destRange.location = row*(maxColumns+1);
417         for (i = 0; i < count; ++i) {
418             [attribString insertAttributedString:emptyRowString
419                             atIndex:destRange.location];
420             [attribString setAttributes:attribs range:destRange];
421         }
422         NSRange editedRange = {row*(maxColumns+1),total*(maxColumns+1)};
423         [self edited:(NSTextStorageEditedAttributes
424                 | NSTextStorageEditedCharacters) range:editedRange
425                                         changeInLength:0];
426     }
429 - (void)clearBlockFromRow:(int)row1 column:(int)col1 toRow:(int)row2
430                    column:(int)col2 color:(NSColor *)color
432     //NSLog(@"clearBlockFromRow:%d column:%d toRow:%d column:%d color:%@",
433     //        row1, col1, row2, col2, color);
434     [self lazyResize:NO];
436     if (row1 < 0 || row2 >= maxRows || col1 < 0 || col2 > maxColumns) {
437         //NSLog(@"[%s] WARNING : out of range, row1=%d row2=%d (%d) col1=%d "
438         //        "col2=%d (%d)", _cmd, row1, row2, maxRows, col1, col2,
439         //        maxColumns);
440         return;
441     }
443     NSDictionary *attribs = [NSDictionary dictionaryWithObjectsAndKeys:
444             font, NSFontAttributeName,
445             color, NSBackgroundColorAttributeName, nil];
447     NSRange range = { row1*(maxColumns+1) + col1, col2-col1+1 };
448     
449     NSRange emptyRange = {0,col2-col1+1};
450     NSAttributedString *emptyString =
451             [emptyRowString attributedSubstringFromRange:emptyRange];
452     int r;
453     for (r=row1; r<=row2; ++r) {
454         [attribString replaceCharactersInRange:range
455                           withAttributedString:emptyString];
456         [attribString setAttributes:attribs range:range];
457         [self edited:(NSTextStorageEditedAttributes
458                 | NSTextStorageEditedCharacters) range:range
459                                         changeInLength:0];
460         range.location += maxColumns+1;
461     }
464 - (void)clearAll
466     //NSLog(@"%s%@", _cmd, color);
467     [self lazyResize:YES];
470 - (void)setDefaultColorsBackground:(NSColor *)bgColor
471                         foreground:(NSColor *)fgColor
473     //NSLog(@"setDefaultColorsBackground:%@ foreground:%@", bgColor, fgColor);
475     if (defaultBackgroundColor != bgColor) {
476         [defaultBackgroundColor release];
477         defaultBackgroundColor = bgColor ? [bgColor retain] : nil;
478     }
480     // NOTE: The default foreground color isn't actually used for anything, but
481     // other class instances might want to be able to access it so it is stored
482     // here.
483     if (defaultForegroundColor != fgColor) {
484         [defaultForegroundColor release];
485         defaultForegroundColor = fgColor ? [fgColor retain] : nil;
486     }
489 - (void)setFont:(NSFont*)newFont
491     if (newFont && font != newFont) {
492         [font release];
494         // NOTE! When setting a new font we make sure that the advancement of
495         // each glyph is fixed. 
497         float em = [newFont widthOfString:@"m"];
498         float cellWidthMultiplier = [[NSUserDefaults standardUserDefaults]
499                 floatForKey:MMCellWidthMultiplierKey];
501         // NOTE! Even though NSFontFixedAdvanceAttribute is a float, it will
502         // only render at integer sizes.  Hence, we restrict the cell width to
503         // an integer here, otherwise the window width and the actual text
504         // width will not match.
505         cellSize.width = ceilf(em * cellWidthMultiplier);
507         float pointSize = [newFont pointSize];
508         NSDictionary *dict = [NSDictionary
509             dictionaryWithObject:[NSNumber numberWithFloat:cellSize.width]
510                           forKey:NSFontFixedAdvanceAttribute];
512         NSFontDescriptor *desc = [newFont fontDescriptor];
513         desc = [desc fontDescriptorByAddingAttributes:dict];
514         font = [NSFont fontWithDescriptor:desc size:pointSize];
515         [font retain];
517         NSLayoutManager *lm = [[self layoutManagers] objectAtIndex:0];
518         cellSize.height = linespace + (lm ? [lm defaultLineHeightForFont:font]
519                                           : [font defaultLineHeightForFont]);
521         // NOTE: The font manager does not care about the 'font fixed advance'
522         // attribute, so after converting the font we have to add this
523         // attribute again.
524         boldFont = [[NSFontManager sharedFontManager]
525             convertFont:font toHaveTrait:NSBoldFontMask];
526         desc = [boldFont fontDescriptor];
527         desc = [desc fontDescriptorByAddingAttributes:dict];
528         boldFont = [NSFont fontWithDescriptor:desc size:pointSize];
529         [boldFont retain];
531         italicFont = [[NSFontManager sharedFontManager]
532             convertFont:font toHaveTrait:NSItalicFontMask];
533         desc = [italicFont fontDescriptor];
534         desc = [desc fontDescriptorByAddingAttributes:dict];
535         italicFont = [NSFont fontWithDescriptor:desc size:pointSize];
536         [italicFont retain];
538         boldItalicFont = [[NSFontManager sharedFontManager]
539             convertFont:italicFont toHaveTrait:NSBoldFontMask];
540         desc = [boldItalicFont fontDescriptor];
541         desc = [desc fontDescriptorByAddingAttributes:dict];
542         boldItalicFont = [NSFont fontWithDescriptor:desc size:pointSize];
543         [boldItalicFont retain];
544     }
547 - (NSFont*)font
549     return font;
552 - (NSColor *)defaultBackgroundColor
554     return defaultBackgroundColor;
557 - (NSColor *)defaultForegroundColor
559     return defaultForegroundColor;
562 - (NSSize)size
564     return NSMakeSize(maxColumns*cellSize.width, maxRows*cellSize.height);
567 - (NSSize)cellSize
569     return cellSize;
572 - (NSRect)rectForRowsInRange:(NSRange)range
574     NSRect rect = { 0, 0, 0, 0 };
575     unsigned start = range.location > maxRows ? maxRows : range.location;
576     unsigned length = range.length;
578     if (start+length > maxRows)
579         length = maxRows - start;
581     rect.origin.y = cellSize.height * start;
582     rect.size.height = cellSize.height * length;
584     return rect;
587 - (NSRect)rectForColumnsInRange:(NSRange)range
589     NSRect rect = { 0, 0, 0, 0 };
590     unsigned start = range.location > maxColumns ? maxColumns : range.location;
591     unsigned length = range.length;
593     if (start+length > maxColumns)
594         length = maxColumns - start;
596     rect.origin.x = cellSize.width * start;
597     rect.size.width = cellSize.width * length;
599     return rect;
602 - (unsigned)characterIndexForRow:(int)row column:(int)col
604     // Ensure the offset returned is valid.
605     // This code also works if maxRows and/or maxColumns is 0.
606     if (row >= maxRows) row = maxRows-1;
607     if (row < 0) row = 0;
608     if (col >= maxColumns) col = maxColumns-1;
609     if (col < 0) col = 0;
611     return (unsigned)(col + row*(maxColumns+1));
614 - (BOOL)resizeToFitSize:(NSSize)size
616     int rows = maxRows, cols = maxColumns;
618     [self fitToSize:size rows:&rows columns:&cols];
619     if (rows != maxRows || cols != maxColumns) {
620         [self setMaxRows:rows columns:cols];
621         return YES;
622     }
624     // Return NO only if dimensions did not change.
625     return NO;
628 - (NSSize)fitToSize:(NSSize)size
630     return [self fitToSize:size rows:NULL columns:NULL];
633 - (NSSize)fitToSize:(NSSize)size rows:(int *)rows columns:(int *)columns
635     NSSize curSize = [self size];
636     NSSize fitSize = curSize;
637     int fitRows = maxRows;
638     int fitCols = maxColumns;
640     if (size.height < curSize.height) {
641         // Remove lines until the height of the text storage fits inside
642         // 'size'.  However, always make sure there are at least 3 lines in the
643         // text storage.  (Why 3? It seem Vim never allows less than 3 lines.)
644         //
645         // TODO: No need to search since line height is fixed, just calculate
646         // the new height.
647         int rowCount = maxRows;
648         int rowsToRemove;
649         for (rowsToRemove = 0; rowsToRemove < maxRows-3; ++rowsToRemove) {
650             float height = cellSize.height*rowCount;
652             if (height <= size.height) {
653                 fitSize.height = height;
654                 break;
655             }
657             --rowCount;
658         }
660         fitRows -= rowsToRemove;
661     } else if (size.height > curSize.height) {
662         float fh = cellSize.height;
663         if (fh < 1.0f) fh = 1.0f;
665         fitRows = floor(size.height/fh);
666         fitSize.height = fh*fitRows;
667     }
669     if (size.width != curSize.width) {
670         float fw = cellSize.width;
671         if (fw < 1.0f) fw = 1.0f;
673         fitCols = floor(size.width/fw);
674         fitSize.width = fw*fitCols;
675     }
677     if (rows) *rows = fitRows;
678     if (columns) *columns = fitCols;
680     return fitSize;
683 @end // MMTextStorage
688 @implementation MMTextStorage (Private)
689 - (void)lazyResize:(BOOL)force
691     int i;
693     // Do nothing if the dimensions are already right.
694     if (!force && actualRows == maxRows && actualColumns == maxColumns)
695         return;
697     NSRange oldRange = NSMakeRange(0, actualRows*(actualColumns+1));
699     actualRows = maxRows;
700     actualColumns = maxColumns;
702     NSDictionary *dict;
703     if (defaultBackgroundColor) {
704         dict = [NSDictionary dictionaryWithObjectsAndKeys:
705                 font, NSFontAttributeName,
706                 defaultBackgroundColor, NSBackgroundColorAttributeName, nil];
707     } else {
708         dict = [NSDictionary dictionaryWithObjectsAndKeys:
709                 font, NSFontAttributeName, nil];
710     }
711             
712     NSMutableString *rowString = [NSMutableString string];
713     for (i = 0; i < maxColumns; ++i) {
714         [rowString appendString:@" "];
715     }
716     [rowString appendString:@"\n"];
718     [emptyRowString release];
719     emptyRowString = [[NSAttributedString alloc] initWithString:rowString
720                                                      attributes:dict];
722     [attribString release];
723     attribString = [[NSMutableAttributedString alloc] init];
724     for (i=0; i<maxRows; ++i) {
725         [attribString appendAttributedString:emptyRowString];
726     }
728     NSRange fullRange = NSMakeRange(0, [attribString length]);
729     [self edited:(NSTextStorageEditedCharacters|NSTextStorageEditedAttributes)
730            range:oldRange changeInLength:fullRange.length-oldRange.length];
733 @end // MMTextStorage (Private)