Updated MessageStrings[]
[MacVim/jjgod.git] / MMTextStorage.m
blobed29f4d87ab3207a5737f535c87f9469bd94ee53
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"
16 // If 0 DRAW_TRANSP flag will be ignored.  Setting it to 1 causes the cursor
17 // background to be drawn in white.
18 #define HEED_DRAW_TRANSP 0
20 #define DRAW_TRANSP               0x01    /* draw with transparant bg */
21 #define DRAW_BOLD                 0x02    /* draw bold text */
22 #define DRAW_UNDERL               0x04    /* draw underline text */
23 #define DRAW_UNDERC               0x08    /* draw undercurl text */
24 #define DRAW_ITALIC               0x10    /* draw italic text */
29 @interface MMTextStorage (Private)
30 - (void)lazyResize;
31 @end
35 @implementation MMTextStorage
37 - (id)init
39     if ((self = [super init])) {
40         attribString = [[NSMutableAttributedString alloc] initWithString:@""];
41         // NOTE!  It does not matter which font is set here, Vim will set its
42         // own font on startup anyway.  Just set some bogus values.
43         font = [[NSFont userFixedPitchFontOfSize:0] retain];
44         cellSize.height = [font pointSize];
45         cellSize.width = [font defaultLineHeightForFont];
46     }
48     return self;
51 - (void)dealloc
53     //NSLog(@"%@ %s", [self className], _cmd);
55     [emptyRowString release];
56     [font release];
57     [defaultBackgroundColor release];
58     [attribString release];
59     [super dealloc];
62 - (NSString *)string
64     //NSLog(@"%s : attribString=%@", _cmd, attribString);
65     return [attribString string];
68 - (NSDictionary *)attributesAtIndex:(unsigned)index
69                      effectiveRange:(NSRangePointer)range
71     //NSLog(@"%s", _cmd);
72     if (index>=[attribString length]) {
73         //NSLog(@"%sWARNING: index (%d) out of bounds", _cmd, index);
74         if (range) {
75             *range = NSMakeRange(NSNotFound, 0);
76         }
77         return [NSDictionary dictionary];
78     }
80     return [attribString attributesAtIndex:index effectiveRange:range];
83 - (void)replaceCharactersInRange:(NSRange)range
84                       withString:(NSString *)string
86     //NSLog(@"replaceCharactersInRange:(%d,%d) withString:%@", range.location,
87     //        range.length, string);
88     NSLog(@"WARNING: calling %s on MMTextStorage is unsupported", _cmd);
89     //[attribString replaceCharactersInRange:range withString:string];
92 - (void)setAttributes:(NSDictionary *)attributes range:(NSRange)range
94     // NOTE!  This method must be implemented since the text system calls it
95     // constantly to 'fix attributes', apply font substitution, etc.
96 #if 0
97     [attribString setAttributes:attributes range:range];
98 #else
99     // HACK! If the font attribute is being modified, then ensure that the new
100     // font has a fixed advancement which is either the same as the current
101     // font or twice that, depending on whether it is a 'wide' character that
102     // is being fixed or not.  This code really only works if 'range' has
103     // length 1 or 2.
104     NSFont *newFont = [attributes objectForKey:NSFontAttributeName];
105     if (newFont) {
106         float adv = cellSize.width;
107         if ([attribString length] > range.location+1) {
108             // If the first char is followed by zero-width space, then it is a
109             // 'wide' character, so double the advancement.
110             NSString *string = [attribString string];
111             if ([string characterAtIndex:range.location+1] == 0x200b)
112                 adv += adv;
113         }
115         // Create a new font which has the 'fixed advance attribute' set.
116         NSDictionary *dict = [NSDictionary dictionaryWithObjectsAndKeys:
117             [NSNumber numberWithFloat:adv], NSFontFixedAdvanceAttribute, nil];
118         NSFontDescriptor *desc = [newFont fontDescriptor];
119         desc = [desc fontDescriptorByAddingAttributes:dict];
120         newFont = [NSFont fontWithDescriptor:desc size:[newFont pointSize]];
122         // Now modify the 'attributes' dictionary to hold the new font.
123         NSMutableDictionary *newAttr = [NSMutableDictionary
124             dictionaryWithDictionary:attributes];
125         [newAttr setObject:newFont forKey:NSFontAttributeName];
127         [attribString setAttributes:newAttr range:range];
128     } else {
129         [attribString setAttributes:attributes range:range];
130     }
131 #endif
134 - (int)maxRows
136     return maxRows;
139 - (int)maxColumns
141     return maxColumns;
144 - (void)getMaxRows:(int*)rows columns:(int*)cols
146     if (rows) *rows = maxRows;
147     if (cols) *cols = maxColumns;
150 - (void)setMaxRows:(int)rows columns:(int)cols
152     // NOTE: Just remember the new values, the actual resizing is done lazily.
153     maxRows = rows;
154     maxColumns = cols;
157 - (void)replaceString:(NSString*)string atRow:(int)row column:(int)col
158         withFlags:(int)flags foregroundColor:(NSColor*)fg
159         backgroundColor:(NSColor*)bg
161     //NSLog(@"replaceString:atRow:%d column:%d withFlags:%d", row, col, flags);
162     [self lazyResize];
164     if (row < 0 || row >= maxRows || col < 0 || col >= maxColumns
165             || col+[string length] > maxColumns) {
166         //NSLog(@"[%s] WARNING : out of range, row=%d (%d) col=%d (%d) "
167         //        "length=%d (%d)", _cmd, row, maxRows, col, maxColumns,
168         //        [string length], [attribString length]);
169         return;
170     }
172     if (!(fg && bg)) {
173         NSLog(@"[%s] WARNING: background or foreground color not specified",
174                 _cmd);
175         return;
176     }
178     NSRange range = NSMakeRange(col+row*(maxColumns+1), [string length]);
179     [attribString replaceCharactersInRange:range withString:string];
181     NSDictionary *attributes = [NSDictionary dictionaryWithObjectsAndKeys:
182             font, NSFontAttributeName,
183 #if !HEED_DRAW_TRANSP
184             bg, NSBackgroundColorAttributeName,
185 #endif
186             fg, NSForegroundColorAttributeName, nil];
187     [attribString setAttributes:attributes range:range];
189 #if HEED_DRAW_TRANSP
190     if ( !(flags & DRAW_TRANSP) ) {
191         [attribString addAttribute:NSBackgroundColorAttributeName value:bg
192                 range:range];
193     }
194 #endif
196     // TODO: cache bold font and apply in setAttributes:range:
197     if (flags & DRAW_BOLD) {
198         [attribString applyFontTraits:NSBoldFontMask range:range];
199     }
201     // TODO: cache italic font and apply in setAttributes:range:
202     if (flags & DRAW_ITALIC) {
203         [attribString applyFontTraits:NSItalicFontMask range:range];
204     }
206     if (flags & DRAW_UNDERL) {
207         NSNumber *value = [NSNumber numberWithInt:(NSUnderlineStyleSingle
208                 | NSUnderlinePatternSolid)]; // | NSUnderlineByWordMask
209         [attribString addAttribute:NSUnderlineStyleAttributeName
210                 value:value range:range];
211     }
213     // TODO: figure out how do draw proper undercurls
214     if (flags & DRAW_UNDERC) {
215         NSNumber *value = [NSNumber numberWithInt:(NSUnderlineStyleThick
216                 | NSUnderlinePatternDot)]; // | NSUnderlineByWordMask
217         [attribString addAttribute:NSUnderlineStyleAttributeName
218                 value:value range:range];
219     }
221     [self edited:(NSTextStorageEditedCharacters|NSTextStorageEditedAttributes)
222            range:range changeInLength:0];
226  * Delete 'count' lines from 'row' and insert 'count' empty lines at the bottom
227  * of the scroll region.
228  */
229 - (void)deleteLinesFromRow:(int)row lineCount:(int)count
230               scrollBottom:(int)bottom left:(int)left right:(int)right
231                      color:(NSColor *)color
233     //NSLog(@"deleteLinesFromRow:%d lineCount:%d", row, count);
234     [self lazyResize];
236     if (row < 0 || row+count > maxRows) {
237         //NSLog(@"[%s] WARNING : out of range, row=%d (%d) count=%d", _cmd, row,
238         //        maxRows, count);
239         return;
240     }
242     int total = 1 + bottom - row;
243     int move = total - count;
244     int width = right - left + 1;
245     NSRange destRange = { row*(maxColumns+1) + left, width };
246     NSRange srcRange = { (row+count)*(maxColumns+1) + left, width };
247     int i;
249     for (i = 0; i < move; ++i) {
250         NSAttributedString *srcString = [attribString
251                 attributedSubstringFromRange:srcRange];
252         [attribString replaceCharactersInRange:destRange
253                           withAttributedString:srcString];
254         [self edited:(NSTextStorageEditedCharacters
255                 | NSTextStorageEditedAttributes)
256                 range:destRange changeInLength:0];
257         destRange.location += maxColumns+1;
258         srcRange.location += maxColumns+1;
259     }
261     for (i = 0; i < count; ++i) {
262         NSDictionary *attribs = [NSDictionary dictionaryWithObjectsAndKeys:
263                 font, NSFontAttributeName,
264                 color, NSForegroundColorAttributeName,
265                 color, NSBackgroundColorAttributeName, nil];
266         [attribString setAttributes:attribs range:destRange];
267         [self edited:NSTextStorageEditedAttributes range:destRange
268                 changeInLength:0];
269         destRange.location += maxColumns+1;
270     }
274  * Insert 'count' empty lines at 'row' and delete 'count' lines from the bottom
275  * of the scroll region.
276  */
277 - (void)insertLinesAtRow:(int)row lineCount:(int)count
278             scrollBottom:(int)bottom left:(int)left right:(int)right
279                    color:(NSColor *)color
281     //NSLog(@"insertLinesAtRow:%d lineCount:%d", row, count);
282     [self lazyResize];
284     if (row < 0 || row+count > maxRows) {
285         //NSLog(@"[%s] WARNING : out of range, row=%d (%d) count=%d", _cmd, row,
286         //        maxRows, count);
287         return;
288     }
290     int total = 1 + bottom - row;
291     int move = total - count;
292     int width = right - left + 1;
293     NSRange destRange = { bottom*(maxColumns+1) + left, width };
294     NSRange srcRange = { (row+move-1)*(maxColumns+1) + left, width };
295     int i;
297     for (i = 0; i < move; ++i) {
298         NSAttributedString *srcString = [attribString
299                 attributedSubstringFromRange:srcRange];
300         [attribString replaceCharactersInRange:destRange
301                           withAttributedString:srcString];
302         [self edited:(NSTextStorageEditedCharacters
303                 | NSTextStorageEditedAttributes)
304                 range:destRange changeInLength:0];
305         destRange.location -= maxColumns+1;
306         srcRange.location -= maxColumns+1;
307     }
309     for (i = 0; i < count; ++i) {
310         NSDictionary *attribs = [NSDictionary dictionaryWithObjectsAndKeys:
311                 font, NSFontAttributeName,
312                 color, NSForegroundColorAttributeName,
313                 color, NSBackgroundColorAttributeName, nil];
314         [attribString setAttributes:attribs range:destRange];
315         [self edited:NSTextStorageEditedAttributes range:destRange
316                 changeInLength:0];
317         destRange.location -= maxColumns+1;
318     }
321 - (void)clearBlockFromRow:(int)row1 column:(int)col1 toRow:(int)row2
322                    column:(int)col2 color:(NSColor *)color
324     //NSLog(@"clearBlockFromRow:%d column:%d toRow:%d column:%d", row1, col1,
325     //        row2, col2);
326     [self lazyResize];
328     if (row1 < 0 || row2 >= maxRows || col1 < 0 || col2 > maxColumns) {
329         //NSLog(@"[%s] WARNING : out of range, row1=%d row2=%d (%d) col1=%d "
330         //        "col2=%d (%d)", _cmd, row1, row2, maxRows, col1, col2,
331         //        maxColumns);
332         return;
333     }
335     NSDictionary *attribs = [NSDictionary dictionaryWithObjectsAndKeys:
336             font, NSFontAttributeName,
337             color, NSForegroundColorAttributeName,
338             color, NSBackgroundColorAttributeName, nil];
340     NSRange range = { row1*(maxColumns+1) + col1, col2-col1+1 };
342     int r;
343     for (r=row1; r<=row2; ++r) {
344         [attribString setAttributes:attribs range:range];
345         [self edited:NSTextStorageEditedAttributes range:range
346                 changeInLength:0];
347         range.location += maxColumns+1;
348     }
351 - (void)clearAllWithColor:(NSColor *)color
353     //NSLog(@"%s%@", _cmd, color);
355     NSRange range = { 0, [attribString length] };
356     NSDictionary *attribs = [NSDictionary dictionaryWithObjectsAndKeys:
357             font, NSFontAttributeName,
358             color, NSForegroundColorAttributeName,
359             color, NSBackgroundColorAttributeName, nil];
360     [attribString setAttributes:attribs range:range];
361     [self edited:NSTextStorageEditedAttributes range:range changeInLength:0];
364 - (void)setDefaultColorsBackground:(NSColor *)bgColor
365                         foreground:(NSColor *)fgColor
367     // NOTE: Foreground color is ignored.
368     [defaultBackgroundColor release];
369     defaultBackgroundColor = bgColor ? [bgColor retain] : nil;
372 - (void)setFont:(NSFont*)newFont
374     if (newFont && font != newFont) {
375         [font release];
377         // NOTE! When setting a new font we make sure that the advancement of
378         // each glyph is fixed. 
380         float em = [newFont widthOfString:@"m"];
381         float cellWidthMultiplier = [[NSUserDefaults standardUserDefaults]
382                 floatForKey:MMCellWidthMultiplierKey];
384         // NOTE! Even though NSFontFixedAdvanceAttribute is a float, it will
385         // only render at integer sizes.  Hence, we restrict the cell width to
386         // an integer here, otherwise the window width and the actual text
387         // width will not match.
388         cellSize.width = ceilf(em * cellWidthMultiplier);
390         NSDictionary *dict = [NSDictionary
391             dictionaryWithObject:[NSNumber numberWithFloat:cellSize.width]
392                           forKey:NSFontFixedAdvanceAttribute];
393         NSFontDescriptor *desc = [newFont fontDescriptor];
394         desc = [desc fontDescriptorByAddingAttributes:dict];
396         font = [NSFont fontWithDescriptor:desc size:[newFont pointSize]];
397         [font retain];
399         NSLayoutManager *lm = [[self layoutManagers] objectAtIndex:0];
400         cellSize.height = lm ? [lm defaultLineHeightForFont:font]
401                              : [font defaultLineHeightForFont];
402     }
405 - (NSFont*)font
407     return font;
410 - (NSSize)size
412     return NSMakeSize(maxColumns*cellSize.width, maxRows*cellSize.height);
415 - (NSSize)cellSize
417     return cellSize;
420 - (NSRect)rectForRowsInRange:(NSRange)range
422     NSRect rect = { 0, 0, 0, 0 };
423     unsigned start = range.location > maxRows ? maxRows : range.location;
424     unsigned length = range.length;
426     if (start+length > maxRows)
427         length = maxRows - start;
429     rect.origin.y = cellSize.height * start;
430     rect.size.height = cellSize.height * length;
432     return rect;
435 - (NSRect)rectForColumnsInRange:(NSRange)range
437     NSRect rect = { 0, 0, 0, 0 };
438     unsigned start = range.location > maxColumns ? maxColumns : range.location;
439     unsigned length = range.length;
441     if (start+length > maxColumns)
442         length = maxColumns - start;
444     rect.origin.x = cellSize.width * start;
445     rect.size.width = cellSize.width * length;
447     return rect;
450 - (unsigned)offsetFromRow:(int)row column:(int)col
452     // Ensure the offset returned is valid.
453     // This code also works if maxRows and/or maxColumns is 0.
454     if (row >= maxRows) row = maxRows-1;
455     if (row < 0) row = 0;
456     if (col >= maxColumns) col = maxColumns-1;
457     if (col < 0) col = 0;
459     return (unsigned)(col + row*(maxColumns+1));
462 - (BOOL)resizeToFitSize:(NSSize)size
464     int rows = maxRows, cols = maxColumns;
466     [self fitToSize:size rows:&rows columns:&cols];
467     if (rows != maxRows || cols != maxColumns) {
468         [self setMaxRows:rows columns:cols];
469         return YES;
470     }
472     // Return NO only if dimensions did not change.
473     return NO;
476 - (NSSize)fitToSize:(NSSize)size
478     return [self fitToSize:size rows:NULL columns:NULL];
481 - (NSSize)fitToSize:(NSSize)size rows:(int *)rows columns:(int *)columns
483     NSSize curSize = [self size];
484     NSSize fitSize = curSize;
485     int fitRows = maxRows;
486     int fitCols = maxColumns;
488     if (size.height < curSize.height) {
489         // Remove lines until the height of the text storage fits inside
490         // 'size'.  However, always make sure there are at least 3 lines in the
491         // text storage.  (Why 3? It seem Vim never allows less than 3 lines.)
492         //
493         // TODO: No need to search since line height is fixed, just calculate
494         // the new height.
495         int rowCount = maxRows;
496         int rowsToRemove;
497         for (rowsToRemove = 0; rowsToRemove < maxRows-3; ++rowsToRemove) {
498             float height = cellSize.height*rowCount;
500             if (height <= size.height) {
501                 fitSize.height = height;
502                 break;
503             }
505             --rowCount;
506         }
508         fitRows -= rowsToRemove;
509     } else if (size.height > curSize.height) {
510         float fh = cellSize.height;
511         if (fh < 1.0f) fh = 1.0f;
513         fitRows = floor(size.height/fh);
514         fitSize.height = fh*fitRows;
515     }
517     if (size.width != curSize.width) {
518         float fw = cellSize.width;
519         if (fw < 1.0f) fw = 1.0f;
521         fitCols = floor(size.width/fw);
522         fitSize.width = fw*fitCols;
523     }
525     if (rows) *rows = fitRows;
526     if (columns) *columns = fitCols;
528     return fitSize;
531 @end // MMTextStorage
536 @implementation MMTextStorage (Private)
537 - (void)lazyResize
539     int i;
541     // Do nothing if the dimensions are already right.
542     if (actualRows == maxRows && actualColumns == maxColumns)
543         return;
545     NSRange oldRange = NSMakeRange(0, actualRows*(actualColumns+1));
547     actualRows = maxRows;
548     actualColumns = maxColumns;
550     NSDictionary *dict;
551     if (defaultBackgroundColor) {
552         dict = [NSDictionary dictionaryWithObjectsAndKeys:
553                 font, NSFontAttributeName,
554                 defaultBackgroundColor, NSBackgroundColorAttributeName, nil];
555     } else {
556         dict = [NSDictionary dictionaryWithObjectsAndKeys:
557                 font, NSFontAttributeName, nil];
558     }
559             
560     NSMutableString *rowString = [NSMutableString string];
561     for (i = 0; i < maxColumns; ++i) {
562         [rowString appendString:@" "];
563     }
564     [rowString appendString:@"\n"];
566     [emptyRowString release];
567     emptyRowString = [[NSAttributedString alloc] initWithString:rowString
568                                                      attributes:dict];
570     [attribString release];
571     attribString = [[NSMutableAttributedString alloc] init];
572     for (i=0; i<maxRows; ++i) {
573         [attribString appendAttributedString:emptyRowString];
574     }
576     NSRange fullRange = NSMakeRange(0, [attribString length]);
577     [self edited:(NSTextStorageEditedCharacters|NSTextStorageEditedAttributes)
578            range:oldRange changeInLength:fullRange.length-oldRange.length];
581 @end // MMTextStorage (Private)