Prepare for 64 bit
[MacVim.git] / src / MacVim / MMTextStorage.m
blob0c6408dc9bb1212def1fb25f789de6baa871aacc
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  *
15  * Note that:
16  * - There are exactly 'actualRows' number of rows
17  * - Each row is terminated by an EOL character ('\n')
18  * - Each row must cover exactly 'actualColumns' display cells
19  * - The attribute "MMWideChar" denotes a character that covers two cells, a
20  *   character without this attribute covers one cell
21  * - Unicode line (U+2028) and paragraph (U+2029) terminators are considered
22  *   invalid and are replaced by spaces
23  * - Spaces are used to fill out blank spaces
24  *
25  * In order to locate a (row,col) pair it is in general necessary to search one
26  * character at a time.  To speed things up we cache the length of each row, as
27  * well as the offset of the last column searched within each row.
28  *
29  * If each character in the text storage has length 1 and is not wide, then
30  * there is no need to search for a (row, col) pair since it can easily be
31  * computed.
32  */
34 #import "MMTextStorage.h"
35 #import "MacVim.h"
36 #import "Miscellaneous.h"
40 // Enable debug log messages for situations that should never occur.
41 #define MM_TS_PARANOIA_LOG 1
45 // TODO: What does DRAW_TRANSP flag do?  If the background isn't drawn when
46 // this flag is set, then sometimes the character after the cursor becomes
47 // blank.  Everything seems to work fine by just ignoring this flag.
48 #define DRAW_TRANSP               0x01    /* draw with transparant bg */
49 #define DRAW_BOLD                 0x02    /* draw bold text */
50 #define DRAW_UNDERL               0x04    /* draw underline text */
51 #define DRAW_UNDERC               0x08    /* draw undercurl text */
52 #define DRAW_ITALIC               0x10    /* draw italic text */
53 #define DRAW_CURSOR               0x20
56 static NSString *MMWideCharacterAttributeName = @"MMWideChar";
61 @interface MMTextStorage (Private)
62 - (void)lazyResize:(BOOL)force;
63 - (NSRange)charRangeForRow:(int)row column:(int*)col cells:(int*)cells;
64 - (void)fixInvalidCharactersInRange:(NSRange)range;
65 @end
69 @implementation MMTextStorage
71 - (id)init
73     if ((self = [super init])) {
74         attribString = [[NSMutableAttributedString alloc] initWithString:@""];
75         // NOTE!  It does not matter which font is set here, Vim will set its
76         // own font on startup anyway.  Just set some bogus values.
77         font = [[NSFont userFixedPitchFontOfSize:0] retain];
78         cellSize.height = 16.0;
79         cellSize.width = 6.0;
80     }
82     return self;
85 - (void)dealloc
87     ASLogDebug(@"");
89 #if MM_USE_ROW_CACHE
90     if (rowCache) {
91         free(rowCache);
92         rowCache = NULL;
93     }
94 #endif
95     [emptyRowString release];  emptyRowString = nil;
96     [boldItalicFontWide release];  boldItalicFontWide = nil;
97     [italicFontWide release];  italicFontWide = nil;
98     [boldFontWide release];  boldFontWide = nil;
99     [fontWide release];  fontWide = nil;
100     [boldItalicFont release];  boldItalicFont = nil;
101     [italicFont release];  italicFont = nil;
102     [boldFont release];  boldFont = nil;
103     [font release];  font = nil;
104     [defaultBackgroundColor release];  defaultBackgroundColor = nil;
105     [defaultForegroundColor release];  defaultForegroundColor = nil;
106     [attribString release];  attribString = nil;
107     [super dealloc];
110 - (NSString *)string
112     return [attribString string];
115 - (NSDictionary *)attributesAtIndex:(NSUInteger)index
116                      effectiveRange:(NSRangePointer)range
118     if (index >= [attribString length]) {
119         if (range)
120             *range = NSMakeRange(NSNotFound, 0);
122         return [NSDictionary dictionary];
123     }
125     return [attribString attributesAtIndex:index effectiveRange:range];
128 - (id)attribute:(NSString *)attrib atIndex:(unsigned)index
129         effectiveRange:(NSRangePointer)range
131     return [attribString attribute:attrib atIndex:index effectiveRange:range];
134 - (void)replaceCharactersInRange:(NSRange)range
135                       withString:(NSString *)string
137 #if MM_TS_PARANOIA_LOG
138     ASLogWarn(@"Calling %s on MMTextStorage is unsupported", _cmd);
139 #endif
140     //[attribString replaceCharactersInRange:range withString:string];
143 - (void)setAttributes:(NSDictionary *)attributes range:(NSRange)range
145     // NOTE!  This method must be implemented since the text system calls it
146     // constantly to 'fix attributes', apply font substitution, etc.
147 #if 0
148     [attribString setAttributes:attributes range:range];
149 #elif 1
150     // HACK! If the font attribute is being modified, then ensure that the new
151     // font has a fixed advancement which is either the same as the current
152     // font or twice that, depending on whether it is a 'wide' character that
153     // is being fixed or not.
154     //
155     // TODO: This code assumes that the characters in 'range' all have the same
156     // width.
157     NSFont *newFont = [attributes objectForKey:NSFontAttributeName];
158     if (newFont) {
159         // Allow disabling of font substitution via a user default.  Not
160         // recommended since the typesetter hides the corresponding glyphs and
161         // the display gets messed up.
162         if ([[NSUserDefaults standardUserDefaults]
163                 boolForKey:MMNoFontSubstitutionKey])
164             return;
166         float adv = cellSize.width;
167         if ([attribString attribute:MMWideCharacterAttributeName
168                             atIndex:range.location
169                      effectiveRange:NULL])
170             adv += adv;
172         // Create a new font which has the 'fixed advance attribute' set.
173         NSDictionary *dict = [NSDictionary dictionaryWithObjectsAndKeys:
174             [NSNumber numberWithFloat:adv], NSFontFixedAdvanceAttribute, nil];
175         NSFontDescriptor *desc = [newFont fontDescriptor];
176         desc = [desc fontDescriptorByAddingAttributes:dict];
177         newFont = [NSFont fontWithDescriptor:desc size:[newFont pointSize]];
179         // Now modify the 'attributes' dictionary to hold the new font.
180         NSMutableDictionary *newAttr = [NSMutableDictionary
181             dictionaryWithDictionary:attributes];
182         [newAttr setObject:newFont forKey:NSFontAttributeName];
184         [attribString setAttributes:newAttr range:range];
185     } else {
186         [attribString setAttributes:attributes range:range];
187     }
188 #endif
191 - (int)maxRows
193     return maxRows;
196 - (int)maxColumns
198     return maxColumns;
201 - (int)actualRows
203     return actualRows;
206 - (int)actualColumns
208     return actualColumns;
211 - (float)linespace
213     return linespace;
216 - (void)setLinespace:(float)newLinespace
218     NSLayoutManager *lm = [[self layoutManagers] objectAtIndex:0];
219     if (!lm) {
220         ASLogWarn(@"No layout manager available");
221         return;
222     }
224     linespace = newLinespace;
226     // NOTE: The linespace is added to the cell height in order for a multiline
227     // selection not to have white (background color) gaps between lines.  Also
228     // this simplifies the code a lot because there is no need to check the
229     // linespace when calculating the size of the text view etc.  When the
230     // linespace is non-zero the baseline will be adjusted as well; check
231     // MMTypesetter.
232     cellSize.height = linespace + [lm defaultLineHeightForFont:font];
235 - (void)getMaxRows:(int*)rows columns:(int*)cols
237     if (rows) *rows = maxRows;
238     if (cols) *cols = maxColumns;
241 - (void)setMaxRows:(int)rows columns:(int)cols
243     // NOTE: Just remember the new values, the actual resizing is done lazily.
244     maxRows = rows;
245     maxColumns = cols;
248 - (void)drawString:(NSString *)string atRow:(int)row column:(int)col
249              cells:(int)cells withFlags:(int)flags
250    foregroundColor:(NSColor *)fg backgroundColor:(NSColor *)bg
251       specialColor:(NSColor *)sp
253     [self lazyResize:NO];
255     if (row < 0 || row >= maxRows || col < 0 || col >= maxColumns
256             || col+cells > maxColumns || !string || !(fg && bg && sp))
257         return;
259     BOOL hasControlChars = [string rangeOfCharacterFromSet:
260             [NSCharacterSet controlCharacterSet]].location != NSNotFound;
261     if (hasControlChars) {
262         // HACK! If a string for some reason contains control characters, then
263         // draw blanks instead (otherwise charRangeForRow::: fails).
264         NSRange subRange = { 0, cells };
265         flags &= ~DRAW_WIDE;
266         string = [[emptyRowString string] substringWithRange:subRange];
267     }
269     // Find range of characters in text storage to replace.
270     int acol = col;
271     int acells = cells;
272     NSRange range = [self charRangeForRow:row column:&acol cells:&acells];
273     if (NSNotFound == range.location) {
274 #if MM_TS_PARANOIA_LOG
275         ASLogErr(@"INTERNAL ERROR: Out of bounds");
276 #endif
277         return;
278     }
280     // Create dictionary of attributes to apply to the new characters.
281     NSFont *theFont = font;
282     if (flags & DRAW_WIDE) {
283         if (flags & DRAW_BOLD)
284             theFont = flags & DRAW_ITALIC ? boldItalicFontWide : boldFontWide;
285         else if (flags & DRAW_ITALIC)
286             theFont = italicFontWide;
287         else
288             theFont = fontWide;
289     } else {
290         if (flags & DRAW_BOLD)
291             theFont = flags & DRAW_ITALIC ? boldItalicFont : boldFont;
292         else if (flags & DRAW_ITALIC)
293             theFont = italicFont;
294     }
296     NSMutableDictionary *attributes =
297         [NSMutableDictionary dictionaryWithObjectsAndKeys:
298             theFont, NSFontAttributeName,
299             bg, NSBackgroundColorAttributeName,
300             fg, NSForegroundColorAttributeName,
301             sp, NSUnderlineColorAttributeName,
302             [NSNumber numberWithInt:0], NSLigatureAttributeName,
303             nil];
305     if (flags & DRAW_UNDERL) {
306         NSNumber *value = [NSNumber numberWithInt:(NSUnderlineStyleSingle
307                 | NSUnderlinePatternSolid)]; // | NSUnderlineByWordMask
308         [attributes setObject:value forKey:NSUnderlineStyleAttributeName];
309     }
311     if (flags & DRAW_UNDERC) {
312         // TODO: figure out how do draw proper undercurls
313         NSNumber *value = [NSNumber numberWithInt:(NSUnderlineStyleThick
314                 | NSUnderlinePatternDot)]; // | NSUnderlineByWordMask
315         [attributes setObject:value forKey:NSUnderlineStyleAttributeName];
316     }
318     // Mark these characters as wide.  This attribute is subsequently checked
319     // when translating (row,col) pairs to offsets within 'attribString'.
320     if (flags & DRAW_WIDE)
321         [attributes setObject:[NSNull null]
322                        forKey:MMWideCharacterAttributeName];
324     // Replace characters in text storage and apply new attributes.
325     NSRange r = NSMakeRange(range.location, [string length]);
326     [attribString replaceCharactersInRange:range withString:string];
327     [attribString setAttributes:attributes range:r];
329     unsigned changeInLength = [string length] - range.length;
330     if (acells != cells || acol != col) {
331         if (acells == cells + 1) {
332             // NOTE: A normal width character replaced a double width
333             // character.  To maintain the invariant that each row covers the
334             // same amount of cells, we compensate by adding an empty column.
335             [attribString replaceCharactersInRange:NSMakeRange(NSMaxRange(r),0)
336                 withAttributedString:[emptyRowString
337                     attributedSubstringFromRange:NSMakeRange(0,1)]];
338             ++changeInLength;
339 #if 0
340         } else if (acol == col - 1) {
341             [attribString replaceCharactersInRange:NSMakeRange(r.location,0)
342                 withAttributedString:[emptyRowString
343                     attributedSubstringFromRange:NSMakeRange(0,1)]];
344             ++changeInLength;
345         } else if (acol == col + 1) {
346             [attribString replaceCharactersInRange:NSMakeRange(r.location-1,1)
347                 withAttributedString:[emptyRowString
348                     attributedSubstringFromRange:NSMakeRange(0,2)]];
349             ++changeInLength;
350 #endif
351         } else {
352             // NOTE: It seems that this never gets called.  If it ever does,
353             // then there is another case to treat.
354 #if MM_TS_PARANOIA_LOG
355             ASLogWarn(@"row=%d col=%d acol=%d cells=%d acells=%d", row, col,
356                       acol, cells, acells);
357 #endif
358         }
359     }
361     if ((flags & DRAW_WIDE) || [string length] != cells)
362         characterEqualsColumn = NO;
364     [self fixInvalidCharactersInRange:r];
366     [self edited:(NSTextStorageEditedCharacters|NSTextStorageEditedAttributes)
367            range:range changeInLength:changeInLength];
369 #if MM_USE_ROW_CACHE
370     rowCache[row].length += changeInLength;
371 #endif
375  * Delete 'count' lines from 'row' and insert 'count' empty lines at the bottom
376  * of the scroll region.
377  */
378 - (void)deleteLinesFromRow:(int)row lineCount:(int)count
379               scrollBottom:(int)bottom left:(int)left right:(int)right
380                      color:(NSColor *)color
382     [self lazyResize:NO];
384     if (row < 0 || row+count > maxRows || bottom > maxRows || left < 0
385             || right > maxColumns)
386         return;
388     int total = 1 + bottom - row;
389     int move = total - count;
390     int width = right - left + 1;
391     int destRow = row;
392     NSRange destRange, srcRange;
393     int i;
395     for (i = 0; i < move; ++i, ++destRow) {
396         int acol = left;
397         int acells = width;
398         destRange = [self charRangeForRow:destRow column:&acol cells:&acells];
399 #if MM_TS_PARANOIA_LOG
400         if (acells != width || acol != left)
401             ASLogErr(@"INTERNAL ERROR");
402 #endif
404         acol = left; acells = width;
405         srcRange = [self charRangeForRow:(destRow+count) column:&acol
406                                    cells:&acells];
407 #if MM_TS_PARANOIA_LOG
408         if (acells != width || acol != left)
409             ASLogErr(@"INTERNAL ERROR");
410 #endif
412         if (NSNotFound == destRange.location || NSNotFound == srcRange.location)
413         {
414 #if MM_TS_PARANOIA_LOG
415             ASLogErr(@"INTERNAL ERROR: Out of bounds");
416 #endif
417             return;
418         }
420         NSAttributedString *srcString = [attribString
421                 attributedSubstringFromRange:srcRange];
423         [attribString replaceCharactersInRange:destRange
424                           withAttributedString:srcString];
425         [self edited:(NSTextStorageEditedCharacters
426                 | NSTextStorageEditedAttributes) range:destRange
427                 changeInLength:([srcString length]-destRange.length)];
429 #if MM_USE_ROW_CACHE
430         rowCache[destRow].length += [srcString length] - destRange.length;
431 #endif
432     }
433     
434     NSRange emptyRange = {0,width};
435     NSAttributedString *emptyString =
436             [emptyRowString attributedSubstringFromRange:emptyRange];
437     NSDictionary *attribs = [NSDictionary dictionaryWithObjectsAndKeys:
438             font, NSFontAttributeName,
439             color, NSBackgroundColorAttributeName, nil];
441     for (i = 0; i < count; ++i, ++destRow) {
442         int acol = left;
443         int acells = width;
444         destRange = [self charRangeForRow:destRow column:&acol cells:&acells];
445 #if MM_TS_PARANOIA_LOG
446         if (acells != width || acol != left)
447             ASLogErr(@"INTERNAL ERROR");
448 #endif
449         if (NSNotFound == destRange.location) {
450 #if MM_TS_PARANOIA_LOG
451             ASLogErr(@"INTERNAL ERROR: Out of bounds");
452 #endif
453             return;
454         }
456         [attribString replaceCharactersInRange:destRange
457                           withAttributedString:emptyString];
458         [attribString setAttributes:attribs
459                               range:NSMakeRange(destRange.location, width)];
461         [self edited:(NSTextStorageEditedAttributes
462                 | NSTextStorageEditedCharacters) range:destRange
463                 changeInLength:([emptyString length]-destRange.length)];
465 #if MM_USE_ROW_CACHE
466         rowCache[destRow].length += [emptyString length] - destRange.length;
467 #endif
468     }
472  * Insert 'count' empty lines at 'row' and delete 'count' lines from the bottom
473  * of the scroll region.
474  */
475 - (void)insertLinesAtRow:(int)row lineCount:(int)count
476             scrollBottom:(int)bottom left:(int)left right:(int)right
477                    color:(NSColor *)color
479     [self lazyResize:NO];
481     if (row < 0 || row+count > maxRows || bottom > maxRows || left < 0
482             || right > maxColumns)
483         return;
485     int total = 1 + bottom - row;
486     int move = total - count;
487     int width = right - left + 1;
488     int destRow = bottom;
489     int srcRow = row + move - 1;
490     NSRange destRange, srcRange;
491     int i;
493     for (i = 0; i < move; ++i, --destRow, --srcRow) {
494         int acol = left;
495         int acells = width;
496         destRange = [self charRangeForRow:destRow column:&acol cells:&acells];
497 #if MM_TS_PARANOIA_LOG
498         if (acells != width || acol != left)
499             ASLogErr(@"INTERNAL ERROR");
500 #endif
502         acol = left; acells = width;
503         srcRange = [self charRangeForRow:srcRow column:&acol cells:&acells];
504 #if MM_TS_PARANOIA_LOG
505         if (acells != width || acol != left)
506             ASLogErr(@"INTERNAL ERROR");
507 #endif
508         if (NSNotFound == destRange.location || NSNotFound == srcRange.location)
509         {
510 #if MM_TS_PARANOIA_LOG
511             ASLogErr(@"INTERNAL ERROR: Out of bounds");
512 #endif
513             return;
514         }
516         NSAttributedString *srcString = [attribString
517                 attributedSubstringFromRange:srcRange];
518         [attribString replaceCharactersInRange:destRange
519                           withAttributedString:srcString];
520         [self edited:(NSTextStorageEditedCharacters
521                 | NSTextStorageEditedAttributes) range:destRange
522                 changeInLength:([srcString length]-destRange.length)];
524 #if MM_USE_ROW_CACHE
525         rowCache[destRow].length += [srcString length] - destRange.length;
526 #endif
527     }
528     
529     NSRange emptyRange = {0,width};
530     NSAttributedString *emptyString =
531             [emptyRowString attributedSubstringFromRange:emptyRange];
532     NSDictionary *attribs = [NSDictionary dictionaryWithObjectsAndKeys:
533             font, NSFontAttributeName,
534             color, NSBackgroundColorAttributeName, nil];
535     
536     for (i = 0; i < count; ++i, --destRow) {
537         int acol = left;
538         int acells = width;
539         destRange = [self charRangeForRow:destRow column:&acol cells:&acells];
540 #if MM_TS_PARANOIA_LOG
541         if (acells != width || acol != left)
542             ASLogErr(@"INTERNAL ERROR");
543 #endif
544         if (NSNotFound == destRange.location) {
545 #if MM_TS_PARANOIA_LOG
546             ASLogErr(@"INTERNAL ERROR: Out of bounds");
547 #endif
548             return;
549         }
551         [attribString replaceCharactersInRange:destRange
552                           withAttributedString:emptyString];
553         [attribString setAttributes:attribs
554                               range:NSMakeRange(destRange.location, width)];
556         [self edited:(NSTextStorageEditedAttributes
557                 | NSTextStorageEditedCharacters) range:destRange
558                 changeInLength:([emptyString length]-destRange.length)];
560 #if MM_USE_ROW_CACHE
561         rowCache[destRow].length += [emptyString length] - destRange.length;
562 #endif
563     }
566 - (void)clearBlockFromRow:(int)row1 column:(int)col1 toRow:(int)row2
567                    column:(int)col2 color:(NSColor *)color
569     [self lazyResize:NO];
571     if (row1 < 0 || row2 >= maxRows || col1 < 0 || col2 > maxColumns)
572         return;
574     NSDictionary *attribs = [NSDictionary dictionaryWithObjectsAndKeys:
575             font, NSFontAttributeName,
576             color, NSBackgroundColorAttributeName, nil];
577     int cells = col2 - col1 + 1;
578     NSRange range, emptyRange = {0, cells};
579     NSAttributedString *emptyString =
580             [emptyRowString attributedSubstringFromRange:emptyRange];
581     int r;
583     for (r=row1; r<=row2; ++r) {
584         int acol = col1;
585         int acells = cells;
586         range = [self charRangeForRow:r column:&acol cells:&acells];
587 #if MM_TS_PARANOIA_LOG
588         if (acells != cells || acol != col1)
589             ASLogErr(@"INTERNAL ERROR");
590 #endif
591         if (NSNotFound == range.location) {
592 #if MM_TS_PARANOIA_LOG
593             ASLogErr(@"INTERNAL ERROR: Out of bounds");
594 #endif
595             return;
596         }
598         [attribString replaceCharactersInRange:range
599                           withAttributedString:emptyString];
600         [attribString setAttributes:attribs
601                               range:NSMakeRange(range.location, cells)];
603         [self edited:(NSTextStorageEditedAttributes
604                 | NSTextStorageEditedCharacters) range:range
605                                         changeInLength:cells-range.length];
607 #if MM_USE_ROW_CACHE
608         rowCache[r].length += cells - range.length;
609 #endif
610     }
613 - (void)clearAll
615     [self lazyResize:YES];
618 - (void)setDefaultColorsBackground:(NSColor *)bgColor
619                         foreground:(NSColor *)fgColor
621     if (defaultBackgroundColor != bgColor) {
622         [defaultBackgroundColor release];
623         defaultBackgroundColor = bgColor ? [bgColor retain] : nil;
624     }
626     // NOTE: The default foreground color isn't actually used for anything, but
627     // other class instances might want to be able to access it so it is stored
628     // here.
629     if (defaultForegroundColor != fgColor) {
630         [defaultForegroundColor release];
631         defaultForegroundColor = fgColor ? [fgColor retain] : nil;
632     }
635 - (void)setFont:(NSFont*)newFont
637     if (newFont && font != newFont) {
638         [boldItalicFont release];  boldItalicFont = nil;
639         [italicFont release];  italicFont = nil;
640         [boldFont release];  boldFont = nil;
641         [font release];  font = nil;
643         // NOTE! When setting a new font we make sure that the advancement of
644         // each glyph is fixed.
646         float em = [@"m" sizeWithAttributes:
647                 [NSDictionary dictionaryWithObject:newFont
648                                             forKey:NSFontAttributeName]].width;
649         float cellWidthMultiplier = [[NSUserDefaults standardUserDefaults]
650                 floatForKey:MMCellWidthMultiplierKey];
652         // NOTE! Even though NSFontFixedAdvanceAttribute is a float, it will
653         // only render at integer sizes.  Hence, we restrict the cell width to
654         // an integer here, otherwise the window width and the actual text
655         // width will not match.
656         cellSize.width = ceilf(em * cellWidthMultiplier);
658         float pointSize = [newFont pointSize];
659         NSDictionary *dict = [NSDictionary
660             dictionaryWithObject:[NSNumber numberWithFloat:cellSize.width]
661                           forKey:NSFontFixedAdvanceAttribute];
663         NSFontDescriptor *desc = [newFont fontDescriptor];
664         desc = [desc fontDescriptorByAddingAttributes:dict];
665         font = [NSFont fontWithDescriptor:desc size:pointSize];
666         [font retain];
668         NSLayoutManager *lm = [[self layoutManagers] objectAtIndex:0];
669         if (lm) {
670             cellSize.height = linespace + [lm defaultLineHeightForFont:font];
671         } else {
672             // Should never happen, set some bogus value for cell height.
673             ASLogWarn(@"No layout manager available");
674             cellSize.height = linespace + 16.0;
675         }
677         // NOTE: The font manager does not care about the 'font fixed advance'
678         // attribute, so after converting the font we have to add this
679         // attribute again.
680         boldFont = [[NSFontManager sharedFontManager]
681             convertFont:font toHaveTrait:NSBoldFontMask];
682         desc = [boldFont fontDescriptor];
683         desc = [desc fontDescriptorByAddingAttributes:dict];
684         boldFont = [NSFont fontWithDescriptor:desc size:pointSize];
685         [boldFont retain];
687         italicFont = [[NSFontManager sharedFontManager]
688             convertFont:font toHaveTrait:NSItalicFontMask];
689         desc = [italicFont fontDescriptor];
690         desc = [desc fontDescriptorByAddingAttributes:dict];
691         italicFont = [NSFont fontWithDescriptor:desc size:pointSize];
692         [italicFont retain];
694         boldItalicFont = [[NSFontManager sharedFontManager]
695             convertFont:italicFont toHaveTrait:NSBoldFontMask];
696         desc = [boldItalicFont fontDescriptor];
697         desc = [desc fontDescriptorByAddingAttributes:dict];
698         boldItalicFont = [NSFont fontWithDescriptor:desc size:pointSize];
699         [boldItalicFont retain];
700     }
703 - (void)setWideFont:(NSFont *)newFont
705     if (!newFont) {
706         // Use the normal font as the wide font (note that the normal font may
707         // very well include wide characters.)
708         if (font) [self setWideFont:font];
709     } else if (newFont != fontWide) {
710         [boldItalicFontWide release];  boldItalicFontWide = nil;
711         [italicFontWide release];  italicFontWide = nil;
712         [boldFontWide release];  boldFontWide = nil;
713         [fontWide release];  fontWide = nil;
715         float pointSize = [newFont pointSize];
716         NSFontDescriptor *desc = [newFont fontDescriptor];
717         NSDictionary *dictWide = [NSDictionary
718             dictionaryWithObject:[NSNumber numberWithFloat:2*cellSize.width]
719                           forKey:NSFontFixedAdvanceAttribute];
721         desc = [desc fontDescriptorByAddingAttributes:dictWide];
722         fontWide = [NSFont fontWithDescriptor:desc size:pointSize];
723         [fontWide retain];
725         boldFontWide = [[NSFontManager sharedFontManager]
726             convertFont:fontWide toHaveTrait:NSBoldFontMask];
727         desc = [boldFontWide fontDescriptor];
728         desc = [desc fontDescriptorByAddingAttributes:dictWide];
729         boldFontWide = [NSFont fontWithDescriptor:desc size:pointSize];
730         [boldFontWide retain];
732         italicFontWide = [[NSFontManager sharedFontManager]
733             convertFont:fontWide toHaveTrait:NSItalicFontMask];
734         desc = [italicFontWide fontDescriptor];
735         desc = [desc fontDescriptorByAddingAttributes:dictWide];
736         italicFontWide = [NSFont fontWithDescriptor:desc size:pointSize];
737         [italicFontWide retain];
739         boldItalicFontWide = [[NSFontManager sharedFontManager]
740             convertFont:italicFontWide toHaveTrait:NSBoldFontMask];
741         desc = [boldItalicFontWide fontDescriptor];
742         desc = [desc fontDescriptorByAddingAttributes:dictWide];
743         boldItalicFontWide = [NSFont fontWithDescriptor:desc size:pointSize];
744         [boldItalicFontWide retain];
745     }
748 - (NSFont*)font
750     return font;
753 - (NSFont*)fontWide
755     return fontWide;
758 - (NSColor *)defaultBackgroundColor
760     return defaultBackgroundColor;
763 - (NSColor *)defaultForegroundColor
765     return defaultForegroundColor;
768 - (NSSize)size
770     return NSMakeSize(maxColumns*cellSize.width, maxRows*cellSize.height);
773 - (NSSize)cellSize
775     return cellSize;
778 - (NSRect)rectForRowsInRange:(NSRange)range
780     NSRect rect = { {0, 0}, {0, 0} };
781     unsigned start = range.location > maxRows ? maxRows : range.location;
782     unsigned length = range.length;
784     if (start+length > maxRows)
785         length = maxRows - start;
787     rect.origin.y = cellSize.height * start;
788     rect.size.height = cellSize.height * length;
790     return rect;
793 - (NSRect)rectForColumnsInRange:(NSRange)range
795     NSRect rect = { {0, 0}, {0, 0} };
796     unsigned start = range.location > maxColumns ? maxColumns : range.location;
797     unsigned length = range.length;
799     if (start+length > maxColumns)
800         length = maxColumns - start;
802     rect.origin.x = cellSize.width * start;
803     rect.size.width = cellSize.width * length;
805     return rect;
808 - (NSUInteger)characterIndexForRow:(int)row column:(int)col
810     int cells = 1;
811     NSRange range = [self charRangeForRow:row column:&col cells:&cells];
812     return range.location != NSNotFound ? range.location : 0;
815 // XXX: unused at the moment
816 - (BOOL)resizeToFitSize:(NSSize)size
818     int rows = maxRows, cols = maxColumns;
820     [self fitToSize:size rows:&rows columns:&cols];
821     if (rows != maxRows || cols != maxColumns) {
822         [self setMaxRows:rows columns:cols];
823         return YES;
824     }
826     // Return NO only if dimensions did not change.
827     return NO;
830 - (NSSize)fitToSize:(NSSize)size
832     return [self fitToSize:size rows:NULL columns:NULL];
835 - (NSSize)fitToSize:(NSSize)size rows:(int *)rows columns:(int *)columns
837     NSSize curSize = [self size];
838     NSSize fitSize = curSize;
839     int fitRows = maxRows;
840     int fitCols = maxColumns;
842     if (size.height < curSize.height) {
843         // Remove lines until the height of the text storage fits inside
844         // 'size'.  However, always make sure there are at least 3 lines in the
845         // text storage.  (Why 3? It seem Vim never allows less than 3 lines.)
846         //
847         // TODO: No need to search since line height is fixed, just calculate
848         // the new height.
849         int rowCount = maxRows;
850         int rowsToRemove;
851         for (rowsToRemove = 0; rowsToRemove < maxRows-3; ++rowsToRemove) {
852             float height = cellSize.height*rowCount;
854             if (height <= size.height) {
855                 fitSize.height = height;
856                 break;
857             }
859             --rowCount;
860         }
862         fitRows -= rowsToRemove;
863     } else if (size.height > curSize.height) {
864         float fh = cellSize.height;
865         if (fh < 1.0f) fh = 1.0f;
867         fitRows = floor(size.height/fh);
868         fitSize.height = fh*fitRows;
869     }
871     if (size.width != curSize.width) {
872         float fw = cellSize.width;
873         if (fw < 1.0f) fw = 1.0f;
875         fitCols = floor(size.width/fw);
876         fitSize.width = fw*fitCols;
877     }
879     if (rows) *rows = fitRows;
880     if (columns) *columns = fitCols;
882     return fitSize;
885 - (NSRect)boundingRectForCharacterAtRow:(int)row column:(int)col
887 #if 1
888     // This properly computes the position of where Vim expects the glyph to be
889     // drawn.  Had the typesetter actually computed the right position of each
890     // character and not hidden some, this code would be correct.
891     NSRect rect = NSZeroRect;
893     rect.origin.x = col*cellSize.width;
894     rect.origin.y = row*cellSize.height;
895     rect.size = cellSize;
897     // Wide character take up twice the width of a normal character.
898     int cells = 1;
899     NSRange r = [self charRangeForRow:row column:&col cells:&cells];
900     if (NSNotFound != r.location
901             && [attribString attribute:MMWideCharacterAttributeName
902                                atIndex:r.location
903                         effectiveRange:nil])
904         rect.size.width += rect.size.width;
906     return rect;
907 #else
908     // Use layout manager to compute bounding rect.  This works in situations
909     // where the layout manager decides to hide glyphs (Vim assumes all glyphs
910     // are drawn).
911     NSLayoutManager *lm = [[self layoutManagers] objectAtIndex:0];
912     NSTextContainer *tc = [[lm textContainers] objectAtIndex:0];
913     int cells = 1;
914     NSRange range = [self charRangeForRow:row column:&col cells:&cells];
915     NSRange glyphRange = [lm glyphRangeForCharacterRange:range
916                                     actualCharacterRange:NULL];
918     return [lm boundingRectForGlyphRange:glyphRange inTextContainer:tc];
919 #endif
922 #if MM_USE_ROW_CACHE
923 - (MMRowCacheEntry *)rowCache
925     return rowCache;
927 #endif
929 @end // MMTextStorage
934 @implementation MMTextStorage (Private)
936 - (void)lazyResize:(BOOL)force
938     // Do nothing if the dimensions are already right.
939     if (!force && actualRows == maxRows && actualColumns == maxColumns)
940         return;
942     NSRange oldRange = NSMakeRange(0, [attribString length]);
944     actualRows = maxRows;
945     actualColumns = maxColumns;
946     characterEqualsColumn = YES;
948 #if MM_USE_ROW_CACHE
949     free(rowCache);
950     rowCache = (MMRowCacheEntry*)calloc(actualRows, sizeof(MMRowCacheEntry));
951 #endif
953     NSDictionary *dict;
954     if (defaultBackgroundColor) {
955         dict = [NSDictionary dictionaryWithObjectsAndKeys:
956                 font, NSFontAttributeName,
957                 defaultBackgroundColor, NSBackgroundColorAttributeName, nil];
958     } else {
959         dict = [NSDictionary dictionaryWithObjectsAndKeys:
960                 font, NSFontAttributeName, nil];
961     }
962             
963     NSMutableString *rowString = [NSMutableString string];
964     int i;
965     for (i = 0; i < maxColumns; ++i) {
966         [rowString appendString:@" "];
967     }
968     [rowString appendString:@"\n"];
970     [emptyRowString release];
971     emptyRowString = [[NSAttributedString alloc] initWithString:rowString
972                                                      attributes:dict];
974     [attribString release];
975     attribString = [[NSMutableAttributedString alloc] init];
976     for (i=0; i<maxRows; ++i) {
977 #if MM_USE_ROW_CACHE
978         rowCache[i].length = actualColumns + 1;
979 #endif
980         [attribString appendAttributedString:emptyRowString];
981     }
983     NSRange fullRange = NSMakeRange(0, [attribString length]);
984     [self edited:(NSTextStorageEditedCharacters|NSTextStorageEditedAttributes)
985            range:oldRange changeInLength:fullRange.length-oldRange.length];
988 - (NSRange)charRangeForRow:(int)row column:(int*)pcol cells:(int*)pcells
990     int col = *pcol;
991     int cells = *pcells;
993     // If no wide chars are used and if every char has length 1 (no composing
994     // characters, no > 16 bit characters), then we can compute the range.
995     if (characterEqualsColumn)
996         return NSMakeRange(row*(actualColumns+1) + col, cells);
998     NSString *string = [attribString string];
999     unsigned stringLen = [string length];
1000     NSRange r, range = { NSNotFound, 0 };
1001     unsigned idx;
1002     int i;
1004     if (row < 0 || row >= actualRows || col < 0 || col >= actualColumns
1005             || col+cells > actualColumns) {
1006 #if MM_TS_PARANOIA_LOG
1007         ASLogErr(@"row=%d col=%d cells=%d is out of range (length=%d)",
1008                  row, col, cells, stringLen);
1009 #endif
1010         return range;
1011     }
1013 #if MM_USE_ROW_CACHE
1014     // Locate the beginning of the row
1015     MMRowCacheEntry *cache = rowCache;
1016     idx = 0;
1017     for (i = 0; i < row; ++i, ++cache)
1018         idx += cache->length;
1020     int rowEnd = idx + cache->length;
1021 #else
1022     // Locate the beginning of the row by scanning for EOL characters.
1023     r.location = 0;
1024     for (i = 0; i < row; ++i) {
1025         r.length = stringLen - r.location;
1026         r = [string rangeOfString:@"\n" options:NSLiteralSearch range:r];
1027         if (NSNotFound == r.location)
1028             return range;
1029         ++r.location;
1030     }
1031 #endif
1033     // Locate the column
1034 #if MM_USE_ROW_CACHE
1035     cache = &rowCache[row];
1037     i = cache->col;
1038     if (col == i) {
1039         // Cache hit
1040         idx += cache->colOffset;
1041     } else {
1042         range.location = idx;
1044 #if 0  // Backward search seems to be broken...
1045         // Cache miss
1046         if (col < i - col) {
1047             // Search forward from beginning of line.
1048             i = 0;
1049         } else if (actualColumns - col < col - i) {
1050             // Search backward from end of line.
1051             i = actualColumns - 1;
1052             idx += cache->length - 2;
1053         } else {
1054             // Search from cache spot (forward or backward).
1055             idx += cache->colOffset;
1056         }
1058         if (col > i) {
1059             // Forward search
1060             while (col > i) {
1061                 if (idx >= stringLen)
1062                     return NSMakeRange(NSNotFound, 0);
1063                 r = [string rangeOfComposedCharacterSequenceAtIndex:idx];
1065                 // Wide chars take up two display cells.
1066                 if ([attribString attribute:MMWideCharacterAttributeName
1067                                     atIndex:idx
1068                              effectiveRange:nil])
1069                     ++i;
1071                 idx += r.length;
1072                 ++i;
1073             }
1074         } else if (col < i) {
1075             // Backward search
1076             while (col < i) {
1077                 if (idx-1 >= stringLen)
1078                     return NSMakeRange(NSNotFound, 0);
1079                 r = [string rangeOfComposedCharacterSequenceAtIndex:idx-1];
1080                 idx -= r.length;
1081                 --i;
1083                 // Wide chars take up two display cells.
1084                 if ([attribString attribute:MMWideCharacterAttributeName
1085                                     atIndex:idx
1086                              effectiveRange:nil])
1087                     --i;
1088             }
1089         }
1091         *pcol = i;
1092         cache->col = i;
1093         cache->colOffset = idx - range.location;
1094 #else
1095         // Cache miss
1096         if (col < i) {
1097             // Search forward from beginning of line.
1098             i = 0;
1099         } else {
1100             // Search forward from cache spot.
1101             idx += cache->colOffset;
1102         }
1104         // Forward search
1105         while (col > i) {
1106             if (idx >= stringLen)
1107                 return NSMakeRange(NSNotFound, 0);
1108             r = [string rangeOfComposedCharacterSequenceAtIndex:idx];
1110             // Wide chars take up two display cells.
1111             if ([attribString attribute:MMWideCharacterAttributeName
1112                                 atIndex:idx
1113                          effectiveRange:nil])
1114                 ++i;
1116             idx += r.length;
1117             ++i;
1118         }
1120         *pcol = i;
1121         cache->col = i;
1122         cache->colOffset = idx - range.location;
1123 #endif
1124     }
1125 #else
1126     idx = r.location;
1127     for (i = 0; i < col; ++i) {
1128         if (idx >= stringLen)
1129             return NSMakeRange(NSNotFound, 0);
1130         r = [string rangeOfComposedCharacterSequenceAtIndex:idx];
1132         // Wide chars take up two display cells.
1133         if ([attribString attribute:MMWideCharacterAttributeName
1134                             atIndex:idx
1135                      effectiveRange:nil])
1136             ++i;
1138         idx += r.length;
1139     }
1140 #endif
1142     // Count the number of characters that cover the cells.
1143     range.location = idx;
1144     for (i = 0; i < cells; ++i) {
1145         if (idx >= stringLen)
1146             return NSMakeRange(NSNotFound, 0);
1147         r = [string rangeOfComposedCharacterSequenceAtIndex:idx];
1149         // Wide chars take up two display cells.
1150         if ([attribString attribute:MMWideCharacterAttributeName
1151                             atIndex:idx
1152                      effectiveRange:nil])
1153             ++i;
1155         idx += r.length;
1156         range.length += r.length;
1157     }
1159     *pcells = i;
1161 #if MM_TS_PARANOIA_LOG
1162 #if MM_USE_ROW_CACHE
1163     if (range.location >= rowEnd-1) {
1164         ASLogErr(@"INTERNAL ERROR: row=%d col=%d cells=%d --> range=%@",
1165                  row, col, cells, NSStringFromRange(range));
1166         range.location = rowEnd - 2;
1167         range.length = 1;
1168     } else if (NSMaxRange(range) >= rowEnd) {
1169         ASLogErr(@"INTERNAL ERROR: row=%d col=%d cells=%d --> range=%@",
1170                  row, col, cells, NSStringFromRange(range));
1171         range.length = rowEnd - range.location - 1;
1172     }
1173 #endif
1175     if (NSMaxRange(range) > stringLen) {
1176         ASLogErr(@"INTERNAL ERROR: row=%d col=%d cells=%d --> range=%@",
1177                  row, col, cells, NSStringFromRange(range));
1178         range.location = NSNotFound;
1179         range.length = 0;
1180     }
1181 #endif
1183     return range;
1186 - (void)fixInvalidCharactersInRange:(NSRange)range
1188     static NSCharacterSet *invalidCharacterSet = nil;
1189     NSRange invalidRange;
1190     unsigned end;
1192     if (!invalidCharacterSet)
1193         invalidCharacterSet = [[NSCharacterSet characterSetWithRange:
1194             NSMakeRange(0x2028, 2)] retain];
1196     // HACK! Replace characters that the text system can't handle (currently
1197     // LINE SEPARATOR U+2028 and PARAGRAPH SEPARATOR U+2029) with space.
1198     //
1199     // TODO: Treat these separately inside of Vim so we don't have to bother
1200     // here.
1201     while (range.length > 0) {
1202         invalidRange = [[attribString string]
1203             rangeOfCharacterFromSet:invalidCharacterSet
1204                             options:NSLiteralSearch
1205                               range:range];
1206         if (NSNotFound == invalidRange.location)
1207             break;
1209         [attribString replaceCharactersInRange:invalidRange withString:@" "];
1211         end = NSMaxRange(invalidRange);
1212         range.length -= end - range.location;
1213         range.location = end;
1214     }
1217 @end // MMTextStorage (Private)