Got rid of multibyte chars in comment
[MacVim/jjgod.git] / MMTextStorage.m
blob89de6b8f0f22c42b2e527a55681d1d6b158249de
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;
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", row, col, flags);
198     [self lazyResize];
200     // TODO: support DRAW_TRANSP
201     if (flags & DRAW_TRANSP)
202         return;
204     if (row < 0 || row >= maxRows || col < 0 || col >= maxColumns
205             || col+[string length] > maxColumns) {
206         //NSLog(@"[%s] WARNING : out of range, row=%d (%d) col=%d (%d) "
207         //        "length=%d (%d)", _cmd, row, maxRows, col, maxColumns,
208         //        [string length], [attribString length]);
209         return;
210     }
212     // NOTE: If 'string' was initialized with bad data it might be nil; this
213     // may be due to 'enc' being set to an unsupported value, so don't print an
214     // error message or stdout will most likely get flooded.
215     if (!string) return;
217     if (!(fg && bg && sp)) {
218         NSLog(@"[%s] WARNING: background, foreground or special color not "
219                 "specified", _cmd);
220         return;
221     }
223     NSRange range = NSMakeRange(col+row*(maxColumns+1), [string length]);
224     [attribString replaceCharactersInRange:range withString:string];
226     NSFont *theFont = font;
227     if (flags & DRAW_BOLD)
228         theFont = flags & DRAW_ITALIC ? boldItalicFont : boldFont;
229     else if (flags & DRAW_ITALIC)
230         theFont = italicFont;
232     NSDictionary *attributes = [NSDictionary dictionaryWithObjectsAndKeys:
233             theFont, NSFontAttributeName,
234             bg, NSBackgroundColorAttributeName,
235             fg, NSForegroundColorAttributeName,
236             sp, NSUnderlineColorAttributeName,
237             nil];
238     [attribString setAttributes:attributes range:range];
240     if (flags & DRAW_UNDERL) {
241         NSNumber *value = [NSNumber numberWithInt:(NSUnderlineStyleSingle
242                 | NSUnderlinePatternSolid)]; // | NSUnderlineByWordMask
243         [attribString addAttribute:NSUnderlineStyleAttributeName
244                 value:value range:range];
245     }
247     // TODO: figure out how do draw proper undercurls
248     if (flags & DRAW_UNDERC) {
249         NSNumber *value = [NSNumber numberWithInt:(NSUnderlineStyleThick
250                 | NSUnderlinePatternDot)]; // | NSUnderlineByWordMask
251         [attribString addAttribute:NSUnderlineStyleAttributeName
252                 value:value range:range];
253     }
255     [self edited:(NSTextStorageEditedCharacters|NSTextStorageEditedAttributes)
256            range:range changeInLength:0];
260  * Delete 'count' lines from 'row' and insert 'count' empty lines at the bottom
261  * of the scroll region.
262  */
263 - (void)deleteLinesFromRow:(int)row lineCount:(int)count
264               scrollBottom:(int)bottom left:(int)left right:(int)right
265                      color:(NSColor *)color
267     //NSLog(@"deleteLinesFromRow:%d lineCount:%d", row, count);
268     [self lazyResize];
270     if (row < 0 || row+count > maxRows) {
271         //NSLog(@"[%s] WARNING : out of range, row=%d (%d) count=%d", _cmd, row,
272         //        maxRows, count);
273         return;
274     }
276     int total = 1 + bottom - row;
277     int move = total - count;
278     int width = right - left + 1;
279     NSRange destRange = { row*(maxColumns+1) + left, width };
280     NSRange srcRange = { (row+count)*(maxColumns+1) + left, width };
281     int i;
283     for (i = 0; i < move; ++i) {
284         NSAttributedString *srcString = [attribString
285                 attributedSubstringFromRange:srcRange];
286         [attribString replaceCharactersInRange:destRange
287                           withAttributedString:srcString];
288         [self edited:(NSTextStorageEditedCharacters
289                 | NSTextStorageEditedAttributes)
290                 range:destRange changeInLength:0];
291         destRange.location += maxColumns+1;
292         srcRange.location += maxColumns+1;
293     }
295     for (i = 0; i < count; ++i) {
296         NSDictionary *attribs = [NSDictionary dictionaryWithObjectsAndKeys:
297                 font, NSFontAttributeName,
298                 color, NSForegroundColorAttributeName,
299                 color, NSBackgroundColorAttributeName, nil];
300         [attribString setAttributes:attribs range:destRange];
301         [self edited:NSTextStorageEditedAttributes range:destRange
302                 changeInLength:0];
303         destRange.location += maxColumns+1;
304     }
308  * Insert 'count' empty lines at 'row' and delete 'count' lines from the bottom
309  * of the scroll region.
310  */
311 - (void)insertLinesAtRow:(int)row lineCount:(int)count
312             scrollBottom:(int)bottom left:(int)left right:(int)right
313                    color:(NSColor *)color
315     //NSLog(@"insertLinesAtRow:%d lineCount:%d", row, count);
316     [self lazyResize];
318     if (row < 0 || row+count > maxRows) {
319         //NSLog(@"[%s] WARNING : out of range, row=%d (%d) count=%d", _cmd, row,
320         //        maxRows, count);
321         return;
322     }
324     int total = 1 + bottom - row;
325     int move = total - count;
326     int width = right - left + 1;
327     NSRange destRange = { bottom*(maxColumns+1) + left, width };
328     NSRange srcRange = { (row+move-1)*(maxColumns+1) + left, width };
329     int i;
331     for (i = 0; i < move; ++i) {
332         NSAttributedString *srcString = [attribString
333                 attributedSubstringFromRange:srcRange];
334         [attribString replaceCharactersInRange:destRange
335                           withAttributedString:srcString];
336         [self edited:(NSTextStorageEditedCharacters
337                 | NSTextStorageEditedAttributes)
338                 range:destRange changeInLength:0];
339         destRange.location -= maxColumns+1;
340         srcRange.location -= maxColumns+1;
341     }
343     for (i = 0; i < count; ++i) {
344         NSDictionary *attribs = [NSDictionary dictionaryWithObjectsAndKeys:
345                 font, NSFontAttributeName,
346                 color, NSForegroundColorAttributeName,
347                 color, NSBackgroundColorAttributeName, nil];
348         [attribString setAttributes:attribs range:destRange];
349         [self edited:NSTextStorageEditedAttributes range:destRange
350                 changeInLength:0];
351         destRange.location -= maxColumns+1;
352     }
355 - (void)clearBlockFromRow:(int)row1 column:(int)col1 toRow:(int)row2
356                    column:(int)col2 color:(NSColor *)color
358     //NSLog(@"clearBlockFromRow:%d column:%d toRow:%d column:%d", row1, col1,
359     //        row2, col2);
360     [self lazyResize];
362     if (row1 < 0 || row2 >= maxRows || col1 < 0 || col2 > maxColumns) {
363         //NSLog(@"[%s] WARNING : out of range, row1=%d row2=%d (%d) col1=%d "
364         //        "col2=%d (%d)", _cmd, row1, row2, maxRows, col1, col2,
365         //        maxColumns);
366         return;
367     }
369     NSDictionary *attribs = [NSDictionary dictionaryWithObjectsAndKeys:
370             font, NSFontAttributeName,
371             color, NSForegroundColorAttributeName,
372             color, NSBackgroundColorAttributeName, nil];
374     NSRange range = { row1*(maxColumns+1) + col1, col2-col1+1 };
376     int r;
377     for (r=row1; r<=row2; ++r) {
378         [attribString setAttributes:attribs range:range];
379         [self edited:NSTextStorageEditedAttributes range:range
380                 changeInLength:0];
381         range.location += maxColumns+1;
382     }
385 - (void)clearAllWithColor:(NSColor *)color
387     //NSLog(@"%s%@", _cmd, color);
389     NSRange range = { 0, [attribString length] };
390     NSDictionary *attribs = [NSDictionary dictionaryWithObjectsAndKeys:
391             font, NSFontAttributeName,
392             color, NSForegroundColorAttributeName,
393             color, NSBackgroundColorAttributeName, nil];
394     [attribString setAttributes:attribs range:range];
395     [self edited:NSTextStorageEditedAttributes range:range changeInLength:0];
398 - (void)setDefaultColorsBackground:(NSColor *)bgColor
399                         foreground:(NSColor *)fgColor
401     if (defaultBackgroundColor != bgColor) {
402         [defaultBackgroundColor release];
403         defaultBackgroundColor = bgColor ? [bgColor retain] : nil;
404     }
406     // NOTE: The default foreground color isn't actually used for anything, but
407     // other class instances might want to be able to access it so it is stored
408     // here.
409     if (defaultForegroundColor != fgColor) {
410         [defaultForegroundColor release];
411         defaultForegroundColor = fgColor ? [fgColor retain] : nil;
412     }
415 - (void)setFont:(NSFont*)newFont
417     if (newFont && font != newFont) {
418         [font release];
420         // NOTE! When setting a new font we make sure that the advancement of
421         // each glyph is fixed. 
423         float em = [newFont widthOfString:@"m"];
424         float cellWidthMultiplier = [[NSUserDefaults standardUserDefaults]
425                 floatForKey:MMCellWidthMultiplierKey];
427         // NOTE! Even though NSFontFixedAdvanceAttribute is a float, it will
428         // only render at integer sizes.  Hence, we restrict the cell width to
429         // an integer here, otherwise the window width and the actual text
430         // width will not match.
431         cellSize.width = ceilf(em * cellWidthMultiplier);
433         float pointSize = [newFont pointSize];
434         NSDictionary *dict = [NSDictionary
435             dictionaryWithObject:[NSNumber numberWithFloat:cellSize.width]
436                           forKey:NSFontFixedAdvanceAttribute];
438         NSFontDescriptor *desc = [newFont fontDescriptor];
439         desc = [desc fontDescriptorByAddingAttributes:dict];
440         font = [NSFont fontWithDescriptor:desc size:pointSize];
441         [font retain];
443         NSLayoutManager *lm = [[self layoutManagers] objectAtIndex:0];
444         cellSize.height = linespace + (lm ? [lm defaultLineHeightForFont:font]
445                                           : [font defaultLineHeightForFont]);
447         // NOTE: The font manager does not care about the 'font fixed advance'
448         // attribute, so after converting the font we have to add this
449         // attribute again.
450         boldFont = [[NSFontManager sharedFontManager]
451             convertFont:font toHaveTrait:NSBoldFontMask];
452         desc = [boldFont fontDescriptor];
453         desc = [desc fontDescriptorByAddingAttributes:dict];
454         boldFont = [NSFont fontWithDescriptor:desc size:pointSize];
455         [boldFont retain];
457         italicFont = [[NSFontManager sharedFontManager]
458             convertFont:font toHaveTrait:NSItalicFontMask];
459         desc = [italicFont fontDescriptor];
460         desc = [desc fontDescriptorByAddingAttributes:dict];
461         italicFont = [NSFont fontWithDescriptor:desc size:pointSize];
462         [italicFont retain];
464         boldItalicFont = [[NSFontManager sharedFontManager]
465             convertFont:italicFont toHaveTrait:NSBoldFontMask];
466         desc = [boldItalicFont fontDescriptor];
467         desc = [desc fontDescriptorByAddingAttributes:dict];
468         boldItalicFont = [NSFont fontWithDescriptor:desc size:pointSize];
469         [boldItalicFont retain];
470     }
473 - (NSFont*)font
475     return font;
478 - (NSColor *)defaultBackgroundColor
480     return defaultBackgroundColor;
483 - (NSColor *)defaultForegroundColor
485     return defaultForegroundColor;
488 - (NSSize)size
490     return NSMakeSize(maxColumns*cellSize.width, maxRows*cellSize.height);
493 - (NSSize)cellSize
495     return cellSize;
498 - (NSRect)rectForRowsInRange:(NSRange)range
500     NSRect rect = { 0, 0, 0, 0 };
501     unsigned start = range.location > maxRows ? maxRows : range.location;
502     unsigned length = range.length;
504     if (start+length > maxRows)
505         length = maxRows - start;
507     rect.origin.y = cellSize.height * start;
508     rect.size.height = cellSize.height * length;
510     return rect;
513 - (NSRect)rectForColumnsInRange:(NSRange)range
515     NSRect rect = { 0, 0, 0, 0 };
516     unsigned start = range.location > maxColumns ? maxColumns : range.location;
517     unsigned length = range.length;
519     if (start+length > maxColumns)
520         length = maxColumns - start;
522     rect.origin.x = cellSize.width * start;
523     rect.size.width = cellSize.width * length;
525     return rect;
528 - (unsigned)characterIndexForRow:(int)row column:(int)col
530     // Ensure the offset returned is valid.
531     // This code also works if maxRows and/or maxColumns is 0.
532     if (row >= maxRows) row = maxRows-1;
533     if (row < 0) row = 0;
534     if (col >= maxColumns) col = maxColumns-1;
535     if (col < 0) col = 0;
537     return (unsigned)(col + row*(maxColumns+1));
540 - (BOOL)resizeToFitSize:(NSSize)size
542     int rows = maxRows, cols = maxColumns;
544     [self fitToSize:size rows:&rows columns:&cols];
545     if (rows != maxRows || cols != maxColumns) {
546         [self setMaxRows:rows columns:cols];
547         return YES;
548     }
550     // Return NO only if dimensions did not change.
551     return NO;
554 - (NSSize)fitToSize:(NSSize)size
556     return [self fitToSize:size rows:NULL columns:NULL];
559 - (NSSize)fitToSize:(NSSize)size rows:(int *)rows columns:(int *)columns
561     NSSize curSize = [self size];
562     NSSize fitSize = curSize;
563     int fitRows = maxRows;
564     int fitCols = maxColumns;
566     if (size.height < curSize.height) {
567         // Remove lines until the height of the text storage fits inside
568         // 'size'.  However, always make sure there are at least 3 lines in the
569         // text storage.  (Why 3? It seem Vim never allows less than 3 lines.)
570         //
571         // TODO: No need to search since line height is fixed, just calculate
572         // the new height.
573         int rowCount = maxRows;
574         int rowsToRemove;
575         for (rowsToRemove = 0; rowsToRemove < maxRows-3; ++rowsToRemove) {
576             float height = cellSize.height*rowCount;
578             if (height <= size.height) {
579                 fitSize.height = height;
580                 break;
581             }
583             --rowCount;
584         }
586         fitRows -= rowsToRemove;
587     } else if (size.height > curSize.height) {
588         float fh = cellSize.height;
589         if (fh < 1.0f) fh = 1.0f;
591         fitRows = floor(size.height/fh);
592         fitSize.height = fh*fitRows;
593     }
595     if (size.width != curSize.width) {
596         float fw = cellSize.width;
597         if (fw < 1.0f) fw = 1.0f;
599         fitCols = floor(size.width/fw);
600         fitSize.width = fw*fitCols;
601     }
603     if (rows) *rows = fitRows;
604     if (columns) *columns = fitCols;
606     return fitSize;
609 @end // MMTextStorage
614 @implementation MMTextStorage (Private)
615 - (void)lazyResize
617     int i;
619     // Do nothing if the dimensions are already right.
620     if (actualRows == maxRows && actualColumns == maxColumns)
621         return;
623     NSRange oldRange = NSMakeRange(0, actualRows*(actualColumns+1));
625     actualRows = maxRows;
626     actualColumns = maxColumns;
628     NSDictionary *dict;
629     if (defaultBackgroundColor) {
630         dict = [NSDictionary dictionaryWithObjectsAndKeys:
631                 font, NSFontAttributeName,
632                 defaultBackgroundColor, NSBackgroundColorAttributeName, nil];
633     } else {
634         dict = [NSDictionary dictionaryWithObjectsAndKeys:
635                 font, NSFontAttributeName, nil];
636     }
637             
638     NSMutableString *rowString = [NSMutableString string];
639     for (i = 0; i < maxColumns; ++i) {
640         [rowString appendString:@" "];
641     }
642     [rowString appendString:@"\n"];
644     [emptyRowString release];
645     emptyRowString = [[NSAttributedString alloc] initWithString:rowString
646                                                      attributes:dict];
648     [attribString release];
649     attribString = [[NSMutableAttributedString alloc] init];
650     for (i=0; i<maxRows; ++i) {
651         [attribString appendAttributedString:emptyRowString];
652     }
654     NSRange fullRange = NSMakeRange(0, [attribString length]);
655     [self edited:(NSTextStorageEditedCharacters|NSTextStorageEditedAttributes)
656            range:oldRange changeInLength:fullRange.length-oldRange.length];
659 @end // MMTextStorage (Private)