Menu items bound to :macaction are properly recognized (fixes bug where all
[MacVim/jjgod.git] / MMTextStorage.m
blob719b18b2b36b90a2b3036fb309fa37d215a06619
1 /* vi:set ts=8 sts=4 sw=4 ft=objc:
2  *
3  * VIM - Vi IMproved            by Bram Moolenaar
4  *                              MacVim GUI port by Bjorn Winckler
5  *
6  * Do ":help uganda"  in Vim to read copying and usage conditions.
7  * Do ":help credits" in Vim to see a list of people who contributed.
8  * See README.txt for an overview of the Vim source code.
9  */
11 #import "MMTextStorage.h"
12 #import "MacVim.h"
17 // TODO: support DRAW_TRANSP flag
18 #define DRAW_TRANSP               0x01    /* draw with transparant bg */
19 #define DRAW_BOLD                 0x02    /* draw bold text */
20 #define DRAW_UNDERL               0x04    /* draw underline text */
21 #define DRAW_UNDERC               0x08    /* draw undercurl text */
22 #define DRAW_ITALIC               0x10    /* draw italic text */
27 @interface MMTextStorage (Private)
28 - (void)lazyResize:(BOOL)force;
29 @end
33 @implementation MMTextStorage
35 - (id)init
37     if ((self = [super init])) {
38         attribString = [[NSMutableAttributedString alloc] initWithString:@""];
39         // NOTE!  It does not matter which font is set here, Vim will set its
40         // own font on startup anyway.  Just set some bogus values.
41         font = [[NSFont userFixedPitchFontOfSize:0] retain];
42         boldFont = [font retain];
43         italicFont = [font retain];
44         boldItalicFont = [font retain];
45         cellSize.height = [font pointSize];
46         cellSize.width = [font defaultLineHeightForFont];
47     }
49     return self;
52 - (void)dealloc
54     //NSLog(@"%@ %s", [self className], _cmd);
56     [emptyRowString release];
57     [boldItalicFont release];
58     [italicFont release];
59     [boldFont release];
60     [font release];
61     [defaultBackgroundColor release];
62     [defaultForegroundColor release];
63     [attribString release];
64     [super dealloc];
67 - (NSString *)string
69     //NSLog(@"%s : attribString=%@", _cmd, attribString);
70     return [attribString string];
73 - (NSDictionary *)attributesAtIndex:(unsigned)index
74                      effectiveRange:(NSRangePointer)range
76     //NSLog(@"%s", _cmd);
77     if (index>=[attribString length]) {
78         //NSLog(@"%sWARNING: index (%d) out of bounds", _cmd, index);
79         if (range) {
80             *range = NSMakeRange(NSNotFound, 0);
81         }
82         return [NSDictionary dictionary];
83     }
85     return [attribString attributesAtIndex:index effectiveRange:range];
88 - (void)replaceCharactersInRange:(NSRange)range
89                       withString:(NSString *)string
91     //NSLog(@"replaceCharactersInRange:(%d,%d) withString:%@", range.location,
92     //        range.length, string);
93     NSLog(@"WARNING: calling %s on MMTextStorage is unsupported", _cmd);
94     //[attribString replaceCharactersInRange:range withString:string];
97 - (void)setAttributes:(NSDictionary *)attributes range:(NSRange)range
99     // NOTE!  This method must be implemented since the text system calls it
100     // constantly to 'fix attributes', apply font substitution, etc.
101 #if 0
102     [attribString setAttributes:attributes range:range];
103 #else
104     // HACK! If the font attribute is being modified, then ensure that the new
105     // font has a fixed advancement which is either the same as the current
106     // font or twice that, depending on whether it is a 'wide' character that
107     // is being fixed or not.  This code really only works if 'range' has
108     // length 1 or 2.
109     NSFont *newFont = [attributes objectForKey:NSFontAttributeName];
110     if (newFont) {
111         float adv = cellSize.width;
112         if ([attribString length] > range.location+1) {
113             // If the first char is followed by zero-width space, then it is a
114             // 'wide' character, so double the advancement.
115             NSString *string = [attribString string];
116             if ([string characterAtIndex:range.location+1] == 0x200b)
117                 adv += adv;
118         }
120         // Create a new font which has the 'fixed advance attribute' set.
121         NSDictionary *dict = [NSDictionary dictionaryWithObjectsAndKeys:
122             [NSNumber numberWithFloat:adv], NSFontFixedAdvanceAttribute, nil];
123         NSFontDescriptor *desc = [newFont fontDescriptor];
124         desc = [desc fontDescriptorByAddingAttributes:dict];
125         newFont = [NSFont fontWithDescriptor:desc size:[newFont pointSize]];
127         // Now modify the 'attributes' dictionary to hold the new font.
128         NSMutableDictionary *newAttr = [NSMutableDictionary
129             dictionaryWithDictionary:attributes];
130         [newAttr setObject:newFont forKey:NSFontAttributeName];
132         [attribString setAttributes:newAttr range:range];
133     } else {
134         [attribString setAttributes:attributes range:range];
135     }
136 #endif
139 - (int)maxRows
141     return maxRows;
144 - (int)maxColumns
146     return maxColumns;
149 - (int)actualRows
151     return actualRows;
154 - (int)actualColumns
156     return actualColumns;
159 - (float)linespace
161     return linespace;
164 - (void)setLinespace:(float)newLinespace
166     NSLayoutManager *lm = [[self layoutManagers] objectAtIndex:0];
168     linespace = newLinespace;
170     // NOTE: The linespace is added to the cell height in order for a multiline
171     // selection not to have white (background color) gaps between lines.  Also
172     // this simplifies the code a lot because there is no need to check the
173     // linespace when calculating the size of the text view etc.  When the
174     // linespace is non-zero the baseline will be adjusted as well; check
175     // MMTypesetter.
176     cellSize.height = linespace + (lm ? [lm defaultLineHeightForFont:font]
177                                       : [font defaultLineHeightForFont]);
180 - (void)getMaxRows:(int*)rows columns:(int*)cols
182     if (rows) *rows = maxRows;
183     if (cols) *cols = maxColumns;
186 - (void)setMaxRows:(int)rows columns:(int)cols
188     // NOTE: Just remember the new values, the actual resizing is done lazily.
189     maxRows = rows;
190     maxColumns = cols;
193 - (void)replaceString:(NSString *)string atRow:(int)row column:(int)col
194             withFlags:(int)flags foregroundColor:(NSColor *)fg
195       backgroundColor:(NSColor *)bg specialColor:(NSColor *)sp
197     //NSLog(@"replaceString:atRow:%d column:%d withFlags:%d "
198     //          "foreground:%@ background:%@ special:%@",
199     //          row, col, flags, fg, bg, sp);
200     [self lazyResize:NO];
202     // TODO: support DRAW_TRANSP
203     if (flags & DRAW_TRANSP)
204         return;
206     if (row < 0 || row >= maxRows || col < 0 || col >= maxColumns
207             || col+[string length] > maxColumns) {
208         //NSLog(@"[%s] WARNING : out of range, row=%d (%d) col=%d (%d) "
209         //        "length=%d (%d)", _cmd, row, maxRows, col, maxColumns,
210         //        [string length], [attribString length]);
211         return;
212     }
214     // NOTE: If 'string' was initialized with bad data it might be nil; this
215     // may be due to 'enc' being set to an unsupported value, so don't print an
216     // error message or stdout will most likely get flooded.
217     if (!string) return;
219     if (!(fg && bg && sp)) {
220         NSLog(@"[%s] WARNING: background, foreground or special color not "
221                 "specified", _cmd);
222         return;
223     }
225     NSRange range = NSMakeRange(col+row*(maxColumns+1), [string length]);
226     [attribString replaceCharactersInRange:range withString:string];
228     NSFont *theFont = font;
229     if (flags & DRAW_BOLD)
230         theFont = flags & DRAW_ITALIC ? boldItalicFont : boldFont;
231     else if (flags & DRAW_ITALIC)
232         theFont = italicFont;
234     NSDictionary *attributes = [NSDictionary dictionaryWithObjectsAndKeys:
235             theFont, NSFontAttributeName,
236             bg, NSBackgroundColorAttributeName,
237             fg, NSForegroundColorAttributeName,
238             sp, NSUnderlineColorAttributeName,
239             nil];
240     [attribString setAttributes:attributes range:range];
242     if (flags & DRAW_UNDERL) {
243         NSNumber *value = [NSNumber numberWithInt:(NSUnderlineStyleSingle
244                 | NSUnderlinePatternSolid)]; // | NSUnderlineByWordMask
245         [attribString addAttribute:NSUnderlineStyleAttributeName
246                 value:value range:range];
247     }
249     // TODO: figure out how do draw proper undercurls
250     if (flags & DRAW_UNDERC) {
251         NSNumber *value = [NSNumber numberWithInt:(NSUnderlineStyleThick
252                 | NSUnderlinePatternDot)]; // | NSUnderlineByWordMask
253         [attribString addAttribute:NSUnderlineStyleAttributeName
254                 value:value range:range];
255     }
257     [self edited:(NSTextStorageEditedCharacters|NSTextStorageEditedAttributes)
258            range:range changeInLength:0];
262  * Delete 'count' lines from 'row' and insert 'count' empty lines at the bottom
263  * of the scroll region.
264  */
265 - (void)deleteLinesFromRow:(int)row lineCount:(int)count
266               scrollBottom:(int)bottom left:(int)left right:(int)right
267                      color:(NSColor *)color
269     //NSLog(@"deleteLinesFromRow:%d lineCount:%d color:%@", row, count, color);
270     [self lazyResize:NO];
272     if (row < 0 || row+count > maxRows) {
273         //NSLog(@"[%s] WARNING : out of range, row=%d (%d) count=%d", _cmd, row,
274         //        maxRows, count);
275         return;
276     }
278     int total = 1 + bottom - row;
279     int move = total - count;
280     int width = right - left + 1;
281     NSRange destRange = { row*(maxColumns+1) + left, width };
282     NSRange srcRange = { (row+count)*(maxColumns+1) + left, width };
283     int i;
285     for (i = 0; i < move; ++i) {
286         NSAttributedString *srcString = [attribString
287                 attributedSubstringFromRange:srcRange];
288         [attribString replaceCharactersInRange:destRange
289                           withAttributedString:srcString];
290         [self edited:(NSTextStorageEditedCharacters
291                 | NSTextStorageEditedAttributes)
292                 range:destRange changeInLength:0];
293         destRange.location += maxColumns+1;
294         srcRange.location += maxColumns+1;
295     }
297     NSRange emptyRange = {0,width};
298     NSAttributedString *emptyString =
299             [emptyRowString attributedSubstringFromRange: emptyRange];
300     NSDictionary *attribs = [NSDictionary dictionaryWithObjectsAndKeys:
301             font, NSFontAttributeName,
302             color, NSBackgroundColorAttributeName, nil];
304     for (i = 0; i < count; ++i) {
305         [attribString replaceCharactersInRange:destRange
306                           withAttributedString:emptyString];
307         [attribString setAttributes:attribs range:destRange];
308         [self edited:(NSTextStorageEditedAttributes
309                 | NSTextStorageEditedCharacters) range:destRange
310                                         changeInLength:0];
311         destRange.location += maxColumns+1;
312     }
316  * Insert 'count' empty lines at 'row' and delete 'count' lines from the bottom
317  * of the scroll region.
318  */
319 - (void)insertLinesAtRow:(int)row lineCount:(int)count
320             scrollBottom:(int)bottom left:(int)left right:(int)right
321                    color:(NSColor *)color
323     //NSLog(@"insertLinesAtRow:%d lineCount:%d color:%@", row, count, color);
324     [self lazyResize:NO];
326     if (row < 0 || row+count > maxRows) {
327         //NSLog(@"[%s] WARNING : out of range, row=%d (%d) count=%d", _cmd, row,
328         //        maxRows, count);
329         return;
330     }
332     int total = 1 + bottom - row;
333     int move = total - count;
334     int width = right - left + 1;
335     NSRange destRange = { bottom*(maxColumns+1) + left, width };
336     NSRange srcRange = { (row+move-1)*(maxColumns+1) + left, width };
337     int i;
339     for (i = 0; i < move; ++i) {
340         NSAttributedString *srcString = [attribString
341                 attributedSubstringFromRange:srcRange];
342         [attribString replaceCharactersInRange:destRange
343                           withAttributedString:srcString];
344         [self edited:(NSTextStorageEditedCharacters
345                 | NSTextStorageEditedAttributes)
346                 range:destRange changeInLength:0];
347         destRange.location -= maxColumns+1;
348         srcRange.location -= maxColumns+1;
349     }
350     
351     NSRange emptyRange = {0,width};
352     NSAttributedString *emptyString =
353             [emptyRowString attributedSubstringFromRange:emptyRange];
354     NSDictionary *attribs = [NSDictionary dictionaryWithObjectsAndKeys:
355             font, NSFontAttributeName,
356             color, NSBackgroundColorAttributeName, nil];
357     
358     for (i = 0; i < count; ++i) {
359         [attribString replaceCharactersInRange:destRange
360                           withAttributedString:emptyString];
361         [attribString setAttributes:attribs range:destRange];
362         [self edited:(NSTextStorageEditedAttributes
363                 | NSTextStorageEditedCharacters) range:destRange
364                                         changeInLength:0];
365         destRange.location -= maxColumns+1;
366     }
369 - (void)clearBlockFromRow:(int)row1 column:(int)col1 toRow:(int)row2
370                    column:(int)col2 color:(NSColor *)color
372     //NSLog(@"clearBlockFromRow:%d column:%d toRow:%d column:%d color:%@",
373     //        row1, col1, row2, col2, color);
374     [self lazyResize:NO];
376     if (row1 < 0 || row2 >= maxRows || col1 < 0 || col2 > maxColumns) {
377         //NSLog(@"[%s] WARNING : out of range, row1=%d row2=%d (%d) col1=%d "
378         //        "col2=%d (%d)", _cmd, row1, row2, maxRows, col1, col2,
379         //        maxColumns);
380         return;
381     }
383     NSDictionary *attribs = [NSDictionary dictionaryWithObjectsAndKeys:
384             font, NSFontAttributeName,
385             color, NSBackgroundColorAttributeName, nil];
387     NSRange range = { row1*(maxColumns+1) + col1, col2-col1+1 };
388     
389     NSRange emptyRange = {0,col2-col1+1};
390     NSAttributedString *emptyString =
391             [emptyRowString attributedSubstringFromRange:emptyRange];
392     int r;
393     for (r=row1; r<=row2; ++r) {
394         [attribString replaceCharactersInRange:range
395                           withAttributedString:emptyString];
396         [attribString setAttributes:attribs range:range];
397         [self edited:(NSTextStorageEditedAttributes
398                 | NSTextStorageEditedCharacters) range:range
399                                         changeInLength:0];
400         range.location += maxColumns+1;
401     }
404 - (void)clearAll
406     //NSLog(@"%s%@", _cmd, color);
407     [self lazyResize:YES];
410 - (void)setDefaultColorsBackground:(NSColor *)bgColor
411                         foreground:(NSColor *)fgColor
413     //NSLog(@"setDefaultColorsBackground:%@ foreground:%@", bgColor, fgColor);
415     if (defaultBackgroundColor != bgColor) {
416         [defaultBackgroundColor release];
417         defaultBackgroundColor = bgColor ? [bgColor retain] : nil;
418     }
420     // NOTE: The default foreground color isn't actually used for anything, but
421     // other class instances might want to be able to access it so it is stored
422     // here.
423     if (defaultForegroundColor != fgColor) {
424         [defaultForegroundColor release];
425         defaultForegroundColor = fgColor ? [fgColor retain] : nil;
426     }
429 - (void)setFont:(NSFont*)newFont
431     if (newFont && font != newFont) {
432         [font release];
434         // NOTE! When setting a new font we make sure that the advancement of
435         // each glyph is fixed. 
437         float em = [newFont widthOfString:@"m"];
438         float cellWidthMultiplier = [[NSUserDefaults standardUserDefaults]
439                 floatForKey:MMCellWidthMultiplierKey];
441         // NOTE! Even though NSFontFixedAdvanceAttribute is a float, it will
442         // only render at integer sizes.  Hence, we restrict the cell width to
443         // an integer here, otherwise the window width and the actual text
444         // width will not match.
445         cellSize.width = ceilf(em * cellWidthMultiplier);
447         float pointSize = [newFont pointSize];
448         NSDictionary *dict = [NSDictionary
449             dictionaryWithObject:[NSNumber numberWithFloat:cellSize.width]
450                           forKey:NSFontFixedAdvanceAttribute];
452         NSFontDescriptor *desc = [newFont fontDescriptor];
453         desc = [desc fontDescriptorByAddingAttributes:dict];
454         font = [NSFont fontWithDescriptor:desc size:pointSize];
455         [font retain];
457         NSLayoutManager *lm = [[self layoutManagers] objectAtIndex:0];
458         cellSize.height = linespace + (lm ? [lm defaultLineHeightForFont:font]
459                                           : [font defaultLineHeightForFont]);
461         // NOTE: The font manager does not care about the 'font fixed advance'
462         // attribute, so after converting the font we have to add this
463         // attribute again.
464         boldFont = [[NSFontManager sharedFontManager]
465             convertFont:font toHaveTrait:NSBoldFontMask];
466         desc = [boldFont fontDescriptor];
467         desc = [desc fontDescriptorByAddingAttributes:dict];
468         boldFont = [NSFont fontWithDescriptor:desc size:pointSize];
469         [boldFont retain];
471         italicFont = [[NSFontManager sharedFontManager]
472             convertFont:font toHaveTrait:NSItalicFontMask];
473         desc = [italicFont fontDescriptor];
474         desc = [desc fontDescriptorByAddingAttributes:dict];
475         italicFont = [NSFont fontWithDescriptor:desc size:pointSize];
476         [italicFont retain];
478         boldItalicFont = [[NSFontManager sharedFontManager]
479             convertFont:italicFont toHaveTrait:NSBoldFontMask];
480         desc = [boldItalicFont fontDescriptor];
481         desc = [desc fontDescriptorByAddingAttributes:dict];
482         boldItalicFont = [NSFont fontWithDescriptor:desc size:pointSize];
483         [boldItalicFont retain];
484     }
487 - (NSFont*)font
489     return font;
492 - (NSColor *)defaultBackgroundColor
494     return defaultBackgroundColor;
497 - (NSColor *)defaultForegroundColor
499     return defaultForegroundColor;
502 - (NSSize)size
504     return NSMakeSize(maxColumns*cellSize.width, maxRows*cellSize.height);
507 - (NSSize)cellSize
509     return cellSize;
512 - (NSRect)rectForRowsInRange:(NSRange)range
514     NSRect rect = { 0, 0, 0, 0 };
515     unsigned start = range.location > maxRows ? maxRows : range.location;
516     unsigned length = range.length;
518     if (start+length > maxRows)
519         length = maxRows - start;
521     rect.origin.y = cellSize.height * start;
522     rect.size.height = cellSize.height * length;
524     return rect;
527 - (NSRect)rectForColumnsInRange:(NSRange)range
529     NSRect rect = { 0, 0, 0, 0 };
530     unsigned start = range.location > maxColumns ? maxColumns : range.location;
531     unsigned length = range.length;
533     if (start+length > maxColumns)
534         length = maxColumns - start;
536     rect.origin.x = cellSize.width * start;
537     rect.size.width = cellSize.width * length;
539     return rect;
542 - (unsigned)characterIndexForRow:(int)row column:(int)col
544     // Ensure the offset returned is valid.
545     // This code also works if maxRows and/or maxColumns is 0.
546     if (row >= maxRows) row = maxRows-1;
547     if (row < 0) row = 0;
548     if (col >= maxColumns) col = maxColumns-1;
549     if (col < 0) col = 0;
551     return (unsigned)(col + row*(maxColumns+1));
554 - (BOOL)resizeToFitSize:(NSSize)size
556     int rows = maxRows, cols = maxColumns;
558     [self fitToSize:size rows:&rows columns:&cols];
559     if (rows != maxRows || cols != maxColumns) {
560         [self setMaxRows:rows columns:cols];
561         return YES;
562     }
564     // Return NO only if dimensions did not change.
565     return NO;
568 - (NSSize)fitToSize:(NSSize)size
570     return [self fitToSize:size rows:NULL columns:NULL];
573 - (NSSize)fitToSize:(NSSize)size rows:(int *)rows columns:(int *)columns
575     NSSize curSize = [self size];
576     NSSize fitSize = curSize;
577     int fitRows = maxRows;
578     int fitCols = maxColumns;
580     if (size.height < curSize.height) {
581         // Remove lines until the height of the text storage fits inside
582         // 'size'.  However, always make sure there are at least 3 lines in the
583         // text storage.  (Why 3? It seem Vim never allows less than 3 lines.)
584         //
585         // TODO: No need to search since line height is fixed, just calculate
586         // the new height.
587         int rowCount = maxRows;
588         int rowsToRemove;
589         for (rowsToRemove = 0; rowsToRemove < maxRows-3; ++rowsToRemove) {
590             float height = cellSize.height*rowCount;
592             if (height <= size.height) {
593                 fitSize.height = height;
594                 break;
595             }
597             --rowCount;
598         }
600         fitRows -= rowsToRemove;
601     } else if (size.height > curSize.height) {
602         float fh = cellSize.height;
603         if (fh < 1.0f) fh = 1.0f;
605         fitRows = floor(size.height/fh);
606         fitSize.height = fh*fitRows;
607     }
609     if (size.width != curSize.width) {
610         float fw = cellSize.width;
611         if (fw < 1.0f) fw = 1.0f;
613         fitCols = floor(size.width/fw);
614         fitSize.width = fw*fitCols;
615     }
617     if (rows) *rows = fitRows;
618     if (columns) *columns = fitCols;
620     return fitSize;
623 @end // MMTextStorage
628 @implementation MMTextStorage (Private)
629 - (void)lazyResize:(BOOL)force
631     int i;
633     // Do nothing if the dimensions are already right.
634     if (!force && actualRows == maxRows && actualColumns == maxColumns)
635         return;
637     NSRange oldRange = NSMakeRange(0, actualRows*(actualColumns+1));
639     actualRows = maxRows;
640     actualColumns = maxColumns;
642     NSDictionary *dict;
643     if (defaultBackgroundColor) {
644         dict = [NSDictionary dictionaryWithObjectsAndKeys:
645                 font, NSFontAttributeName,
646                 defaultBackgroundColor, NSBackgroundColorAttributeName, nil];
647     } else {
648         dict = [NSDictionary dictionaryWithObjectsAndKeys:
649                 font, NSFontAttributeName, nil];
650     }
651             
652     NSMutableString *rowString = [NSMutableString string];
653     for (i = 0; i < maxColumns; ++i) {
654         [rowString appendString:@" "];
655     }
656     [rowString appendString:@"\n"];
658     [emptyRowString release];
659     emptyRowString = [[NSAttributedString alloc] initWithString:rowString
660                                                      attributes:dict];
662     [attribString release];
663     attribString = [[NSMutableAttributedString alloc] init];
664     for (i=0; i<maxRows; ++i) {
665         [attribString appendAttributedString:emptyRowString];
666     }
668     NSRange fullRange = NSMakeRange(0, [attribString length]);
669     [self edited:(NSTextStorageEditedCharacters|NSTextStorageEditedAttributes)
670            range:oldRange changeInLength:fullRange.length-oldRange.length];
673 @end // MMTextStorage (Private)