Fix placement of auxiliary IM window for Core Text
[MacVim.git] / src / MacVim / MMTextStorage.m
blob98483144545b2358fddc7d5d97e1b04320781f77
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
54 #define DRAW_WIDE                 0x40    /* draw wide text */
57 static NSString *MMWideCharacterAttributeName = @"MMWideChar";
62 @interface MMTextStorage (Private)
63 - (void)lazyResize:(BOOL)force;
64 - (NSRange)charRangeForRow:(int)row column:(int*)col cells:(int*)cells;
65 - (void)fixInvalidCharactersInRange:(NSRange)range;
66 @end
70 @implementation MMTextStorage
72 - (id)init
74     if ((self = [super init])) {
75         attribString = [[NSMutableAttributedString alloc] initWithString:@""];
76         // NOTE!  It does not matter which font is set here, Vim will set its
77         // own font on startup anyway.  Just set some bogus values.
78         font = [[NSFont userFixedPitchFontOfSize:0] retain];
79         cellSize.height = 16.0;
80         cellSize.width = 6.0;
81     }
83     return self;
86 - (void)dealloc
88     ASLogDebug(@"");
90 #if MM_USE_ROW_CACHE
91     if (rowCache) {
92         free(rowCache);
93         rowCache = NULL;
94     }
95 #endif
96     [emptyRowString release];  emptyRowString = nil;
97     [boldItalicFontWide release];  boldItalicFontWide = nil;
98     [italicFontWide release];  italicFontWide = nil;
99     [boldFontWide release];  boldFontWide = nil;
100     [fontWide release];  fontWide = nil;
101     [boldItalicFont release];  boldItalicFont = nil;
102     [italicFont release];  italicFont = nil;
103     [boldFont release];  boldFont = nil;
104     [font release];  font = nil;
105     [defaultBackgroundColor release];  defaultBackgroundColor = nil;
106     [defaultForegroundColor release];  defaultForegroundColor = nil;
107     [attribString release];  attribString = nil;
108     [super dealloc];
111 - (NSString *)string
113     return [attribString string];
116 - (NSDictionary *)attributesAtIndex:(NSUInteger)index
117                      effectiveRange:(NSRangePointer)range
119     return [attribString attributesAtIndex:index effectiveRange:range];
122 - (void)replaceCharactersInRange:(NSRange)range
123                       withString:(NSString *)string
125 #if MM_TS_PARANOIA_LOG
126     ASLogWarn(@"Calling %s on MMTextStorage is unsupported", _cmd);
127 #endif
128     //[attribString replaceCharactersInRange:range withString:string];
131 - (void)setAttributes:(NSDictionary *)attributes range:(NSRange)range
133     // NOTE!  This method must be implemented since the text system calls it
134     // constantly to 'fix attributes', apply font substitution, etc.
135 #if 0
136     [attribString setAttributes:attributes range:range];
137 #elif 1
138     // HACK! If the font attribute is being modified, then ensure that the new
139     // font has a fixed advancement which is either the same as the current
140     // font or twice that, depending on whether it is a 'wide' character that
141     // is being fixed or not.
142     //
143     // TODO: This code assumes that the characters in 'range' all have the same
144     // width.
145     NSFont *newFont = [attributes objectForKey:NSFontAttributeName];
146     if (newFont) {
147         // Allow disabling of font substitution via a user default.  Not
148         // recommended since the typesetter hides the corresponding glyphs and
149         // the display gets messed up.
150         if ([[NSUserDefaults standardUserDefaults]
151                 boolForKey:MMNoFontSubstitutionKey])
152             return;
154         float adv = cellSize.width;
155         if ([attribString attribute:MMWideCharacterAttributeName
156                             atIndex:range.location
157                      effectiveRange:NULL])
158             adv += adv;
160         // Create a new font which has the 'fixed advance attribute' set.
161         NSDictionary *dict = [NSDictionary dictionaryWithObjectsAndKeys:
162             [NSNumber numberWithFloat:adv], NSFontFixedAdvanceAttribute, nil];
163         NSFontDescriptor *desc = [newFont fontDescriptor];
164         desc = [desc fontDescriptorByAddingAttributes:dict];
165         newFont = [NSFont fontWithDescriptor:desc size:[newFont pointSize]];
167         // Now modify the 'attributes' dictionary to hold the new font.
168         NSMutableDictionary *newAttr = [NSMutableDictionary
169             dictionaryWithDictionary:attributes];
170         [newAttr setObject:newFont forKey:NSFontAttributeName];
172         [attribString setAttributes:newAttr range:range];
173     } else {
174         [attribString setAttributes:attributes range:range];
175     }
176 #endif
179 - (int)maxRows
181     return maxRows;
184 - (int)maxColumns
186     return maxColumns;
189 - (int)actualRows
191     return actualRows;
194 - (int)actualColumns
196     return actualColumns;
199 - (float)linespace
201     return linespace;
204 - (void)setLinespace:(float)newLinespace
206     NSLayoutManager *lm = [[self layoutManagers] objectAtIndex:0];
207     if (!lm) {
208         ASLogWarn(@"No layout manager available");
209         return;
210     }
212     linespace = newLinespace;
214     // NOTE: The linespace is added to the cell height in order for a multiline
215     // selection not to have white (background color) gaps between lines.  Also
216     // this simplifies the code a lot because there is no need to check the
217     // linespace when calculating the size of the text view etc.  When the
218     // linespace is non-zero the baseline will be adjusted as well; check
219     // MMTypesetter.
220     cellSize.height = linespace + [lm defaultLineHeightForFont:font];
223 - (void)getMaxRows:(int*)rows columns:(int*)cols
225     if (rows) *rows = maxRows;
226     if (cols) *cols = maxColumns;
229 - (void)setMaxRows:(int)rows columns:(int)cols
231     // NOTE: Just remember the new values, the actual resizing is done lazily.
232     maxRows = rows;
233     maxColumns = cols;
236 - (void)drawString:(NSString *)string atRow:(int)row column:(int)col
237              cells:(int)cells withFlags:(int)flags
238    foregroundColor:(NSColor *)fg backgroundColor:(NSColor *)bg
239       specialColor:(NSColor *)sp
241     [self lazyResize:NO];
243     if (row < 0 || row >= maxRows || col < 0 || col >= maxColumns
244             || col+cells > maxColumns || !string || !(fg && bg && sp))
245         return;
247     BOOL hasControlChars = [string rangeOfCharacterFromSet:
248             [NSCharacterSet controlCharacterSet]].location != NSNotFound;
249     if (hasControlChars) {
250         // HACK! If a string for some reason contains control characters, then
251         // draw blanks instead (otherwise charRangeForRow::: fails).
252         NSRange subRange = { 0, cells };
253         flags &= ~DRAW_WIDE;
254         string = [[emptyRowString string] substringWithRange:subRange];
255     }
257     // Find range of characters in text storage to replace.
258     int acol = col;
259     int acells = cells;
260     NSRange range = [self charRangeForRow:row column:&acol cells:&acells];
261     if (NSNotFound == range.location) {
262 #if MM_TS_PARANOIA_LOG
263         ASLogErr(@"INTERNAL ERROR: Out of bounds");
264 #endif
265         return;
266     }
268     // Create dictionary of attributes to apply to the new characters.
269     NSFont *theFont = font;
270     if (flags & DRAW_WIDE) {
271         if (flags & DRAW_BOLD)
272             theFont = flags & DRAW_ITALIC ? boldItalicFontWide : boldFontWide;
273         else if (flags & DRAW_ITALIC)
274             theFont = italicFontWide;
275         else
276             theFont = fontWide;
277     } else {
278         if (flags & DRAW_BOLD)
279             theFont = flags & DRAW_ITALIC ? boldItalicFont : boldFont;
280         else if (flags & DRAW_ITALIC)
281             theFont = italicFont;
282     }
284     NSMutableDictionary *attributes =
285         [NSMutableDictionary dictionaryWithObjectsAndKeys:
286             theFont, NSFontAttributeName,
287             bg, NSBackgroundColorAttributeName,
288             fg, NSForegroundColorAttributeName,
289             sp, NSUnderlineColorAttributeName,
290             [NSNumber numberWithInt:0], NSLigatureAttributeName,
291             nil];
293     if (flags & DRAW_UNDERL) {
294         NSNumber *value = [NSNumber numberWithInt:(NSUnderlineStyleSingle
295                 | NSUnderlinePatternSolid)]; // | NSUnderlineByWordMask
296         [attributes setObject:value forKey:NSUnderlineStyleAttributeName];
297     }
299     if (flags & DRAW_UNDERC) {
300         // TODO: figure out how do draw proper undercurls
301         NSNumber *value = [NSNumber numberWithInt:(NSUnderlineStyleThick
302                 | NSUnderlinePatternDot)]; // | NSUnderlineByWordMask
303         [attributes setObject:value forKey:NSUnderlineStyleAttributeName];
304     }
306     // Mark these characters as wide.  This attribute is subsequently checked
307     // when translating (row,col) pairs to offsets within 'attribString'.
308     if (flags & DRAW_WIDE)
309         [attributes setObject:[NSNull null]
310                        forKey:MMWideCharacterAttributeName];
312     // Replace characters in text storage and apply new attributes.
313     NSRange r = NSMakeRange(range.location, [string length]);
314     [attribString replaceCharactersInRange:range withString:string];
315     [attribString setAttributes:attributes range:r];
317     NSInteger changeInLength = [string length] - range.length;
318     if (acells != cells || acol != col) {
319         if (acells == cells + 1) {
320             // NOTE: A normal width character replaced a double width
321             // character.  To maintain the invariant that each row covers the
322             // same amount of cells, we compensate by adding an empty column.
323             [attribString replaceCharactersInRange:NSMakeRange(NSMaxRange(r),0)
324                 withAttributedString:[emptyRowString
325                     attributedSubstringFromRange:NSMakeRange(0,1)]];
326             ++changeInLength;
327 #if 0
328         } else if (acol == col - 1) {
329             [attribString replaceCharactersInRange:NSMakeRange(r.location,0)
330                 withAttributedString:[emptyRowString
331                     attributedSubstringFromRange:NSMakeRange(0,1)]];
332             ++changeInLength;
333         } else if (acol == col + 1) {
334             [attribString replaceCharactersInRange:NSMakeRange(r.location-1,1)
335                 withAttributedString:[emptyRowString
336                     attributedSubstringFromRange:NSMakeRange(0,2)]];
337             ++changeInLength;
338 #endif
339         } else {
340             // NOTE: It seems that this never gets called.  If it ever does,
341             // then there is another case to treat.
342 #if MM_TS_PARANOIA_LOG
343             ASLogWarn(@"row=%d col=%d acol=%d cells=%d acells=%d", row, col,
344                       acol, cells, acells);
345 #endif
346         }
347     }
349     if ((flags & DRAW_WIDE) || [string length] != cells)
350         characterEqualsColumn = NO;
352     [self fixInvalidCharactersInRange:r];
354 #if 0
355     ASLogDebug(@"length=%d row=%d col=%d cells=%d replaceRange=%@ change=%d",
356             [string length], row, col, cells,
357             NSStringFromRange(r), changeInLength);
358 #endif
359     [self edited:(NSTextStorageEditedCharacters|NSTextStorageEditedAttributes)
360            range:range changeInLength:changeInLength];
362 #if MM_USE_ROW_CACHE
363     rowCache[row].length += changeInLength;
364 #endif
368  * Delete 'count' lines from 'row' and insert 'count' empty lines at the bottom
369  * of the scroll region.
370  */
371 - (void)deleteLinesFromRow:(int)row lineCount:(int)count
372               scrollBottom:(int)bottom left:(int)left right:(int)right
373                      color:(NSColor *)color
375     [self lazyResize:NO];
377     if (row < 0 || row+count > maxRows || bottom > maxRows || left < 0
378             || right > maxColumns)
379         return;
381     int total = 1 + bottom - row;
382     int move = total - count;
383     int width = right - left + 1;
384     int destRow = row;
385     NSRange destRange, srcRange;
386     int i;
388     for (i = 0; i < move; ++i, ++destRow) {
389         int acol = left;
390         int acells = width;
391         destRange = [self charRangeForRow:destRow column:&acol cells:&acells];
392 #if MM_TS_PARANOIA_LOG
393         if (acells != width || acol != left)
394             ASLogErr(@"INTERNAL ERROR");
395 #endif
397         acol = left; acells = width;
398         srcRange = [self charRangeForRow:(destRow+count) column:&acol
399                                    cells:&acells];
400 #if MM_TS_PARANOIA_LOG
401         if (acells != width || acol != left)
402             ASLogErr(@"INTERNAL ERROR");
403 #endif
405         if (NSNotFound == destRange.location || NSNotFound == srcRange.location)
406         {
407 #if MM_TS_PARANOIA_LOG
408             ASLogErr(@"INTERNAL ERROR: Out of bounds");
409 #endif
410             return;
411         }
413         NSAttributedString *srcString = [attribString
414                 attributedSubstringFromRange:srcRange];
416         [attribString replaceCharactersInRange:destRange
417                           withAttributedString:srcString];
418         [self edited:(NSTextStorageEditedCharacters
419                 | NSTextStorageEditedAttributes) range:destRange
420                 changeInLength:([srcString length]-destRange.length)];
422 #if MM_USE_ROW_CACHE
423         rowCache[destRow].length += [srcString length] - destRange.length;
424 #endif
425     }
426     
427     NSRange emptyRange = {0,width};
428     NSAttributedString *emptyString =
429             [emptyRowString attributedSubstringFromRange:emptyRange];
430     NSDictionary *attribs = [NSDictionary dictionaryWithObjectsAndKeys:
431             font, NSFontAttributeName,
432             color, NSBackgroundColorAttributeName, nil];
434     for (i = 0; i < count; ++i, ++destRow) {
435         int acol = left;
436         int acells = width;
437         destRange = [self charRangeForRow:destRow column:&acol cells:&acells];
438 #if MM_TS_PARANOIA_LOG
439         if (acells != width || acol != left)
440             ASLogErr(@"INTERNAL ERROR");
441 #endif
442         if (NSNotFound == destRange.location) {
443 #if MM_TS_PARANOIA_LOG
444             ASLogErr(@"INTERNAL ERROR: Out of bounds");
445 #endif
446             return;
447         }
449         [attribString replaceCharactersInRange:destRange
450                           withAttributedString:emptyString];
451         [attribString setAttributes:attribs
452                               range:NSMakeRange(destRange.location, width)];
454         [self edited:(NSTextStorageEditedAttributes
455                 | NSTextStorageEditedCharacters) range:destRange
456                 changeInLength:([emptyString length]-destRange.length)];
458 #if MM_USE_ROW_CACHE
459         rowCache[destRow].length += [emptyString length] - destRange.length;
460 #endif
461     }
465  * Insert 'count' empty lines at 'row' and delete 'count' lines from the bottom
466  * of the scroll region.
467  */
468 - (void)insertLinesAtRow:(int)row lineCount:(int)count
469             scrollBottom:(int)bottom left:(int)left right:(int)right
470                    color:(NSColor *)color
472     [self lazyResize:NO];
474     if (row < 0 || row+count > maxRows || bottom > maxRows || left < 0
475             || right > maxColumns)
476         return;
478     int total = 1 + bottom - row;
479     int move = total - count;
480     int width = right - left + 1;
481     int destRow = bottom;
482     int srcRow = row + move - 1;
483     NSRange destRange, srcRange;
484     int i;
486     for (i = 0; i < move; ++i, --destRow, --srcRow) {
487         int acol = left;
488         int acells = width;
489         destRange = [self charRangeForRow:destRow column:&acol cells:&acells];
490 #if MM_TS_PARANOIA_LOG
491         if (acells != width || acol != left)
492             ASLogErr(@"INTERNAL ERROR");
493 #endif
495         acol = left; acells = width;
496         srcRange = [self charRangeForRow:srcRow column:&acol cells:&acells];
497 #if MM_TS_PARANOIA_LOG
498         if (acells != width || acol != left)
499             ASLogErr(@"INTERNAL ERROR");
500 #endif
501         if (NSNotFound == destRange.location || NSNotFound == srcRange.location)
502         {
503 #if MM_TS_PARANOIA_LOG
504             ASLogErr(@"INTERNAL ERROR: Out of bounds");
505 #endif
506             return;
507         }
509         NSAttributedString *srcString = [attribString
510                 attributedSubstringFromRange:srcRange];
511         [attribString replaceCharactersInRange:destRange
512                           withAttributedString:srcString];
513         [self edited:(NSTextStorageEditedCharacters
514                 | NSTextStorageEditedAttributes) range:destRange
515                 changeInLength:([srcString length]-destRange.length)];
517 #if MM_USE_ROW_CACHE
518         rowCache[destRow].length += [srcString length] - destRange.length;
519 #endif
520     }
521     
522     NSRange emptyRange = {0,width};
523     NSAttributedString *emptyString =
524             [emptyRowString attributedSubstringFromRange:emptyRange];
525     NSDictionary *attribs = [NSDictionary dictionaryWithObjectsAndKeys:
526             font, NSFontAttributeName,
527             color, NSBackgroundColorAttributeName, nil];
528     
529     for (i = 0; i < count; ++i, --destRow) {
530         int acol = left;
531         int acells = width;
532         destRange = [self charRangeForRow:destRow column:&acol cells:&acells];
533 #if MM_TS_PARANOIA_LOG
534         if (acells != width || acol != left)
535             ASLogErr(@"INTERNAL ERROR");
536 #endif
537         if (NSNotFound == destRange.location) {
538 #if MM_TS_PARANOIA_LOG
539             ASLogErr(@"INTERNAL ERROR: Out of bounds");
540 #endif
541             return;
542         }
544         [attribString replaceCharactersInRange:destRange
545                           withAttributedString:emptyString];
546         [attribString setAttributes:attribs
547                               range:NSMakeRange(destRange.location, width)];
549         [self edited:(NSTextStorageEditedAttributes
550                 | NSTextStorageEditedCharacters) range:destRange
551                 changeInLength:([emptyString length]-destRange.length)];
553 #if MM_USE_ROW_CACHE
554         rowCache[destRow].length += [emptyString length] - destRange.length;
555 #endif
556     }
559 - (void)clearBlockFromRow:(int)row1 column:(int)col1 toRow:(int)row2
560                    column:(int)col2 color:(NSColor *)color
562     [self lazyResize:NO];
564     if (row1 < 0 || row2 >= maxRows || col1 < 0 || col2 > maxColumns)
565         return;
567     NSDictionary *attribs = [NSDictionary dictionaryWithObjectsAndKeys:
568             font, NSFontAttributeName,
569             color, NSBackgroundColorAttributeName, nil];
570     int cells = col2 - col1 + 1;
571     NSRange range, emptyRange = {0, cells};
572     NSAttributedString *emptyString =
573             [emptyRowString attributedSubstringFromRange:emptyRange];
574     int r;
576     for (r=row1; r<=row2; ++r) {
577         int acol = col1;
578         int acells = cells;
579         range = [self charRangeForRow:r column:&acol cells:&acells];
580 #if MM_TS_PARANOIA_LOG
581         if (acells != cells || acol != col1)
582             ASLogErr(@"INTERNAL ERROR");
583 #endif
584         if (NSNotFound == range.location) {
585 #if MM_TS_PARANOIA_LOG
586             ASLogErr(@"INTERNAL ERROR: Out of bounds");
587 #endif
588             return;
589         }
591         [attribString replaceCharactersInRange:range
592                           withAttributedString:emptyString];
593         [attribString setAttributes:attribs
594                               range:NSMakeRange(range.location, cells)];
596         [self edited:(NSTextStorageEditedAttributes
597                 | NSTextStorageEditedCharacters) range:range
598                                         changeInLength:cells-range.length];
600 #if MM_USE_ROW_CACHE
601         rowCache[r].length += cells - range.length;
602 #endif
603     }
606 - (void)clearAll
608     [self lazyResize:YES];
611 - (void)setDefaultColorsBackground:(NSColor *)bgColor
612                         foreground:(NSColor *)fgColor
614     if (defaultBackgroundColor != bgColor) {
615         [defaultBackgroundColor release];
616         defaultBackgroundColor = bgColor ? [bgColor retain] : nil;
617     }
619     // NOTE: The default foreground color isn't actually used for anything, but
620     // other class instances might want to be able to access it so it is stored
621     // here.
622     if (defaultForegroundColor != fgColor) {
623         [defaultForegroundColor release];
624         defaultForegroundColor = fgColor ? [fgColor retain] : nil;
625     }
628 - (void)setFont:(NSFont*)newFont
630     if (newFont && font != newFont) {
631         [boldItalicFont release];  boldItalicFont = nil;
632         [italicFont release];  italicFont = nil;
633         [boldFont release];  boldFont = nil;
634         [font release];  font = nil;
636         // NOTE! When setting a new font we make sure that the advancement of
637         // each glyph is fixed.
639         float em = [@"m" sizeWithAttributes:
640                 [NSDictionary dictionaryWithObject:newFont
641                                             forKey:NSFontAttributeName]].width;
642         float cellWidthMultiplier = [[NSUserDefaults standardUserDefaults]
643                 floatForKey:MMCellWidthMultiplierKey];
645         // NOTE! Even though NSFontFixedAdvanceAttribute is a float, it will
646         // only render at integer sizes.  Hence, we restrict the cell width to
647         // an integer here, otherwise the window width and the actual text
648         // width will not match.
649         cellSize.width = ceilf(em * cellWidthMultiplier);
651         float pointSize = [newFont pointSize];
652         NSDictionary *dict = [NSDictionary
653             dictionaryWithObject:[NSNumber numberWithFloat:cellSize.width]
654                           forKey:NSFontFixedAdvanceAttribute];
656         NSFontDescriptor *desc = [newFont fontDescriptor];
657         desc = [desc fontDescriptorByAddingAttributes:dict];
658         font = [NSFont fontWithDescriptor:desc size:pointSize];
659         [font retain];
661         NSLayoutManager *lm = [[self layoutManagers] objectAtIndex:0];
662         if (lm) {
663             cellSize.height = linespace + [lm defaultLineHeightForFont:font];
664         } else {
665             // Should never happen, set some bogus value for cell height.
666             ASLogWarn(@"No layout manager available");
667             cellSize.height = linespace + 16.0;
668         }
670         // NOTE: The font manager does not care about the 'font fixed advance'
671         // attribute, so after converting the font we have to add this
672         // attribute again.
673         boldFont = [[NSFontManager sharedFontManager]
674             convertFont:font toHaveTrait:NSBoldFontMask];
675         desc = [boldFont fontDescriptor];
676         desc = [desc fontDescriptorByAddingAttributes:dict];
677         boldFont = [NSFont fontWithDescriptor:desc size:pointSize];
678         [boldFont retain];
680         italicFont = [[NSFontManager sharedFontManager]
681             convertFont:font toHaveTrait:NSItalicFontMask];
682         desc = [italicFont fontDescriptor];
683         desc = [desc fontDescriptorByAddingAttributes:dict];
684         italicFont = [NSFont fontWithDescriptor:desc size:pointSize];
685         [italicFont retain];
687         boldItalicFont = [[NSFontManager sharedFontManager]
688             convertFont:italicFont toHaveTrait:NSBoldFontMask];
689         desc = [boldItalicFont fontDescriptor];
690         desc = [desc fontDescriptorByAddingAttributes:dict];
691         boldItalicFont = [NSFont fontWithDescriptor:desc size:pointSize];
692         [boldItalicFont retain];
693     }
696 - (void)setWideFont:(NSFont *)newFont
698     if (!newFont) {
699         // Use the normal font as the wide font (note that the normal font may
700         // very well include wide characters.)
701         if (font) [self setWideFont:font];
702     } else if (newFont != fontWide) {
703         [boldItalicFontWide release];  boldItalicFontWide = nil;
704         [italicFontWide release];  italicFontWide = nil;
705         [boldFontWide release];  boldFontWide = nil;
706         [fontWide release];  fontWide = nil;
708         float pointSize = [newFont pointSize];
709         NSFontDescriptor *desc = [newFont fontDescriptor];
710         NSDictionary *dictWide = [NSDictionary
711             dictionaryWithObject:[NSNumber numberWithFloat:2*cellSize.width]
712                           forKey:NSFontFixedAdvanceAttribute];
714         desc = [desc fontDescriptorByAddingAttributes:dictWide];
715         fontWide = [NSFont fontWithDescriptor:desc size:pointSize];
716         [fontWide retain];
718         boldFontWide = [[NSFontManager sharedFontManager]
719             convertFont:fontWide toHaveTrait:NSBoldFontMask];
720         desc = [boldFontWide fontDescriptor];
721         desc = [desc fontDescriptorByAddingAttributes:dictWide];
722         boldFontWide = [NSFont fontWithDescriptor:desc size:pointSize];
723         [boldFontWide retain];
725         italicFontWide = [[NSFontManager sharedFontManager]
726             convertFont:fontWide toHaveTrait:NSItalicFontMask];
727         desc = [italicFontWide fontDescriptor];
728         desc = [desc fontDescriptorByAddingAttributes:dictWide];
729         italicFontWide = [NSFont fontWithDescriptor:desc size:pointSize];
730         [italicFontWide retain];
732         boldItalicFontWide = [[NSFontManager sharedFontManager]
733             convertFont:italicFontWide toHaveTrait:NSBoldFontMask];
734         desc = [boldItalicFontWide fontDescriptor];
735         desc = [desc fontDescriptorByAddingAttributes:dictWide];
736         boldItalicFontWide = [NSFont fontWithDescriptor:desc size:pointSize];
737         [boldItalicFontWide retain];
738     }
741 - (NSFont*)font
743     return font;
746 - (NSFont*)fontWide
748     return fontWide;
751 - (NSColor *)defaultBackgroundColor
753     return defaultBackgroundColor;
756 - (NSColor *)defaultForegroundColor
758     return defaultForegroundColor;
761 - (NSSize)size
763     return NSMakeSize(maxColumns*cellSize.width, maxRows*cellSize.height);
766 - (NSSize)cellSize
768     return cellSize;
771 - (NSRect)rectForRowsInRange:(NSRange)range
773     NSRect rect = { {0, 0}, {0, 0} };
774     unsigned start = range.location > maxRows ? maxRows : range.location;
775     unsigned length = range.length;
777     if (start+length > maxRows)
778         length = maxRows - start;
780     rect.origin.y = cellSize.height * start;
781     rect.size.height = cellSize.height * length;
783     return rect;
786 - (NSRect)rectForColumnsInRange:(NSRange)range
788     NSRect rect = { {0, 0}, {0, 0} };
789     unsigned start = range.location > maxColumns ? maxColumns : range.location;
790     unsigned length = range.length;
792     if (start+length > maxColumns)
793         length = maxColumns - start;
795     rect.origin.x = cellSize.width * start;
796     rect.size.width = cellSize.width * length;
798     return rect;
801 - (NSUInteger)characterIndexForRow:(int)row column:(int)col
803     int cells = 1;
804     NSRange range = [self charRangeForRow:row column:&col cells:&cells];
805     return range.location != NSNotFound ? range.location : 0;
808 // XXX: unused at the moment
809 - (BOOL)resizeToFitSize:(NSSize)size
811     int rows = maxRows, cols = maxColumns;
813     [self fitToSize:size rows:&rows columns:&cols];
814     if (rows != maxRows || cols != maxColumns) {
815         [self setMaxRows:rows columns:cols];
816         return YES;
817     }
819     // Return NO only if dimensions did not change.
820     return NO;
823 - (NSSize)fitToSize:(NSSize)size
825     return [self fitToSize:size rows:NULL columns:NULL];
828 - (NSSize)fitToSize:(NSSize)size rows:(int *)rows columns:(int *)columns
830     NSSize curSize = [self size];
831     NSSize fitSize = curSize;
832     int fitRows = maxRows;
833     int fitCols = maxColumns;
835     if (size.height < curSize.height) {
836         // Remove lines until the height of the text storage fits inside
837         // 'size'.  However, always make sure there are at least 3 lines in the
838         // text storage.  (Why 3? It seem Vim never allows less than 3 lines.)
839         //
840         // TODO: No need to search since line height is fixed, just calculate
841         // the new height.
842         int rowCount = maxRows;
843         int rowsToRemove;
844         for (rowsToRemove = 0; rowsToRemove < maxRows-3; ++rowsToRemove) {
845             float height = cellSize.height*rowCount;
847             if (height <= size.height) {
848                 fitSize.height = height;
849                 break;
850             }
852             --rowCount;
853         }
855         fitRows -= rowsToRemove;
856     } else if (size.height > curSize.height) {
857         float fh = cellSize.height;
858         if (fh < 1.0f) fh = 1.0f;
860         fitRows = floor(size.height/fh);
861         fitSize.height = fh*fitRows;
862     }
864     if (size.width != curSize.width) {
865         float fw = cellSize.width;
866         if (fw < 1.0f) fw = 1.0f;
868         fitCols = floor(size.width/fw);
869         fitSize.width = fw*fitCols;
870     }
872     if (rows) *rows = fitRows;
873     if (columns) *columns = fitCols;
875     return fitSize;
878 - (NSRect)boundingRectForCharacterAtRow:(int)row column:(int)col
880 #if 1
881     // This properly computes the position of where Vim expects the glyph to be
882     // drawn.  Had the typesetter actually computed the right position of each
883     // character and not hidden some, this code would be correct.
884     NSRect rect = NSZeroRect;
886     rect.origin.x = col*cellSize.width;
887     rect.origin.y = row*cellSize.height;
888     rect.size = cellSize;
890     // Wide character take up twice the width of a normal character.
891     int cells = 1;
892     NSRange r = [self charRangeForRow:row column:&col cells:&cells];
893     if (NSNotFound != r.location
894             && [attribString attribute:MMWideCharacterAttributeName
895                                atIndex:r.location
896                         effectiveRange:nil])
897         rect.size.width += rect.size.width;
899     return rect;
900 #else
901     // Use layout manager to compute bounding rect.  This works in situations
902     // where the layout manager decides to hide glyphs (Vim assumes all glyphs
903     // are drawn).
904     NSLayoutManager *lm = [[self layoutManagers] objectAtIndex:0];
905     NSTextContainer *tc = [[lm textContainers] objectAtIndex:0];
906     int cells = 1;
907     NSRange range = [self charRangeForRow:row column:&col cells:&cells];
908     NSRange glyphRange = [lm glyphRangeForCharacterRange:range
909                                     actualCharacterRange:NULL];
911     return [lm boundingRectForGlyphRange:glyphRange inTextContainer:tc];
912 #endif
915 #if MM_USE_ROW_CACHE
916 - (MMRowCacheEntry *)rowCache
918     return rowCache;
920 #endif
922 @end // MMTextStorage
927 @implementation MMTextStorage (Private)
929 - (void)lazyResize:(BOOL)force
931     // Do nothing if the dimensions are already right.
932     if (!force && actualRows == maxRows && actualColumns == maxColumns)
933         return;
935     NSRange oldRange = NSMakeRange(0, [attribString length]);
937     actualRows = maxRows;
938     actualColumns = maxColumns;
939     characterEqualsColumn = YES;
941 #if MM_USE_ROW_CACHE
942     free(rowCache);
943     rowCache = (MMRowCacheEntry*)calloc(actualRows, sizeof(MMRowCacheEntry));
944 #endif
946     NSDictionary *dict;
947     if (defaultBackgroundColor) {
948         dict = [NSDictionary dictionaryWithObjectsAndKeys:
949                 font, NSFontAttributeName,
950                 defaultBackgroundColor, NSBackgroundColorAttributeName, nil];
951     } else {
952         dict = [NSDictionary dictionaryWithObjectsAndKeys:
953                 font, NSFontAttributeName, nil];
954     }
955             
956     NSMutableString *rowString = [NSMutableString string];
957     int i;
958     for (i = 0; i < maxColumns; ++i) {
959         [rowString appendString:@" "];
960     }
961     [rowString appendString:@"\n"];
963     [emptyRowString release];
964     emptyRowString = [[NSAttributedString alloc] initWithString:rowString
965                                                      attributes:dict];
967     [attribString release];
968     attribString = [[NSMutableAttributedString alloc] init];
969     for (i=0; i<maxRows; ++i) {
970 #if MM_USE_ROW_CACHE
971         rowCache[i].length = actualColumns + 1;
972 #endif
973         [attribString appendAttributedString:emptyRowString];
974     }
976     NSRange fullRange = NSMakeRange(0, [attribString length]);
977     [self edited:(NSTextStorageEditedCharacters|NSTextStorageEditedAttributes)
978            range:oldRange changeInLength:fullRange.length-oldRange.length];
981 - (NSRange)charRangeForRow:(int)row column:(int*)pcol cells:(int*)pcells
983     int col = *pcol;
984     int cells = *pcells;
986     // If no wide chars are used and if every char has length 1 (no composing
987     // characters, no > 16 bit characters), then we can compute the range.
988     if (characterEqualsColumn)
989         return NSMakeRange(row*(actualColumns+1) + col, cells);
991     NSString *string = [attribString string];
992     unsigned stringLen = [string length];
993     NSRange r, range = { NSNotFound, 0 };
994     unsigned idx;
995     int i;
997     if (row < 0 || row >= actualRows || col < 0 || col >= actualColumns
998             || col+cells > actualColumns) {
999 #if MM_TS_PARANOIA_LOG
1000         ASLogErr(@"row=%d col=%d cells=%d is out of range (length=%d)",
1001                  row, col, cells, stringLen);
1002 #endif
1003         return range;
1004     }
1006 #if MM_USE_ROW_CACHE
1007     // Locate the beginning of the row
1008     MMRowCacheEntry *cache = rowCache;
1009     idx = 0;
1010     for (i = 0; i < row; ++i, ++cache)
1011         idx += cache->length;
1013     int rowEnd = idx + cache->length;
1014 #else
1015     // Locate the beginning of the row by scanning for EOL characters.
1016     r.location = 0;
1017     for (i = 0; i < row; ++i) {
1018         r.length = stringLen - r.location;
1019         r = [string rangeOfString:@"\n" options:NSLiteralSearch range:r];
1020         if (NSNotFound == r.location)
1021             return range;
1022         ++r.location;
1023     }
1024 #endif
1026     // Locate the column
1027 #if MM_USE_ROW_CACHE
1028     cache = &rowCache[row];
1030     i = cache->col;
1031     if (col == i) {
1032         // Cache hit
1033         idx += cache->colOffset;
1034     } else {
1035         range.location = idx;
1037 #if 0  // Backward search seems to be broken...
1038         // Cache miss
1039         if (col < i - col) {
1040             // Search forward from beginning of line.
1041             i = 0;
1042         } else if (actualColumns - col < col - i) {
1043             // Search backward from end of line.
1044             i = actualColumns - 1;
1045             idx += cache->length - 2;
1046         } else {
1047             // Search from cache spot (forward or backward).
1048             idx += cache->colOffset;
1049         }
1051         if (col > i) {
1052             // Forward search
1053             while (col > i) {
1054                 if (idx >= stringLen)
1055                     return NSMakeRange(NSNotFound, 0);
1056                 r = [string rangeOfComposedCharacterSequenceAtIndex:idx];
1058                 // Wide chars take up two display cells.
1059                 if ([attribString attribute:MMWideCharacterAttributeName
1060                                     atIndex:idx
1061                              effectiveRange:nil])
1062                     ++i;
1064                 idx += r.length;
1065                 ++i;
1066             }
1067         } else if (col < i) {
1068             // Backward search
1069             while (col < i) {
1070                 if (idx-1 >= stringLen)
1071                     return NSMakeRange(NSNotFound, 0);
1072                 r = [string rangeOfComposedCharacterSequenceAtIndex:idx-1];
1073                 idx -= r.length;
1074                 --i;
1076                 // Wide chars take up two display cells.
1077                 if ([attribString attribute:MMWideCharacterAttributeName
1078                                     atIndex:idx
1079                              effectiveRange:nil])
1080                     --i;
1081             }
1082         }
1084         *pcol = i;
1085         cache->col = i;
1086         cache->colOffset = idx - range.location;
1087 #else
1088         // Cache miss
1089         if (col < i) {
1090             // Search forward from beginning of line.
1091             i = 0;
1092         } else {
1093             // Search forward from cache spot.
1094             idx += cache->colOffset;
1095         }
1097         // Forward search
1098         while (col > i) {
1099             if (idx >= stringLen)
1100                 return NSMakeRange(NSNotFound, 0);
1101             r = [string rangeOfComposedCharacterSequenceAtIndex:idx];
1103             // Wide chars take up two display cells.
1104             if ([attribString attribute:MMWideCharacterAttributeName
1105                                 atIndex:idx
1106                          effectiveRange:nil])
1107                 ++i;
1109             idx += r.length;
1110             ++i;
1111         }
1113         *pcol = i;
1114         cache->col = i;
1115         cache->colOffset = idx - range.location;
1116 #endif
1117     }
1118 #else
1119     idx = r.location;
1120     for (i = 0; i < col; ++i) {
1121         if (idx >= stringLen)
1122             return NSMakeRange(NSNotFound, 0);
1123         r = [string rangeOfComposedCharacterSequenceAtIndex:idx];
1125         // Wide chars take up two display cells.
1126         if ([attribString attribute:MMWideCharacterAttributeName
1127                             atIndex:idx
1128                      effectiveRange:nil])
1129             ++i;
1131         idx += r.length;
1132     }
1133 #endif
1135     // Count the number of characters that cover the cells.
1136     range.location = idx;
1137     for (i = 0; i < cells; ++i) {
1138         if (idx >= stringLen)
1139             return NSMakeRange(NSNotFound, 0);
1140         r = [string rangeOfComposedCharacterSequenceAtIndex:idx];
1142         // Wide chars take up two display cells.
1143         if ([attribString attribute:MMWideCharacterAttributeName
1144                             atIndex:idx
1145                      effectiveRange:nil])
1146             ++i;
1148         idx += r.length;
1149         range.length += r.length;
1150     }
1152     *pcells = i;
1154 #if MM_TS_PARANOIA_LOG
1155 #if MM_USE_ROW_CACHE
1156     if (range.location >= rowEnd-1) {
1157         ASLogErr(@"INTERNAL ERROR: row=%d col=%d cells=%d --> range=%@",
1158                  row, col, cells, NSStringFromRange(range));
1159         range.location = rowEnd - 2;
1160         range.length = 1;
1161     } else if (NSMaxRange(range) >= rowEnd) {
1162         ASLogErr(@"INTERNAL ERROR: row=%d col=%d cells=%d --> range=%@",
1163                  row, col, cells, NSStringFromRange(range));
1164         range.length = rowEnd - range.location - 1;
1165     }
1166 #endif
1168     if (NSMaxRange(range) > stringLen) {
1169         ASLogErr(@"INTERNAL ERROR: row=%d col=%d cells=%d --> range=%@",
1170                  row, col, cells, NSStringFromRange(range));
1171         range.location = NSNotFound;
1172         range.length = 0;
1173     }
1174 #endif
1176     return range;
1179 - (void)fixInvalidCharactersInRange:(NSRange)range
1181     static NSCharacterSet *invalidCharacterSet = nil;
1182     NSRange invalidRange;
1183     unsigned end;
1185     if (!invalidCharacterSet)
1186         invalidCharacterSet = [[NSCharacterSet characterSetWithRange:
1187             NSMakeRange(0x2028, 2)] retain];
1189     // HACK! Replace characters that the text system can't handle (currently
1190     // LINE SEPARATOR U+2028 and PARAGRAPH SEPARATOR U+2029) with space.
1191     //
1192     // TODO: Treat these separately inside of Vim so we don't have to bother
1193     // here.
1194     while (range.length > 0) {
1195         invalidRange = [[attribString string]
1196             rangeOfCharacterFromSet:invalidCharacterSet
1197                             options:NSLiteralSearch
1198                               range:range];
1199         if (NSNotFound == invalidRange.location)
1200             break;
1202         [attribString replaceCharactersInRange:invalidRange withString:@" "];
1204         end = NSMaxRange(invalidRange);
1205         range.length -= end - range.location;
1206         range.location = end;
1207     }
1210 @end // MMTextStorage (Private)