Added mouse shape support
[MacVim/jjgod.git] / MMTextStorage.m
blobadeae57ad3051f5a289673b3e722798020505a4b
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 specialColor:(NSColor *)sp
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     // NOTE: If 'string' was initialized with bad data it might be nil; this
173     // may be due to 'enc' being set to an unsupported value, so don't print an
174     // error message or stdout will most likely get flooded.
175     if (!string) return;
177     if (!(fg && bg && sp)) {
178         NSLog(@"[%s] WARNING: background, foreground or special color not "
179                 "specified", _cmd);
180         return;
181     }
183     NSRange range = NSMakeRange(col+row*(maxColumns+1), [string length]);
184     [attribString replaceCharactersInRange:range withString:string];
186     NSDictionary *attributes = [NSDictionary dictionaryWithObjectsAndKeys:
187             font, NSFontAttributeName,
188 #if !HEED_DRAW_TRANSP
189             bg, NSBackgroundColorAttributeName,
190 #endif
191             fg, NSForegroundColorAttributeName,
192             sp, NSUnderlineColorAttributeName,
193             nil];
194     [attribString setAttributes:attributes range:range];
196 #if HEED_DRAW_TRANSP
197     if ( !(flags & DRAW_TRANSP) ) {
198         [attribString addAttribute:NSBackgroundColorAttributeName value:bg
199                 range:range];
200     }
201 #endif
203     // TODO: cache bold font and apply in setAttributes:range:
204     if (flags & DRAW_BOLD) {
205         [attribString applyFontTraits:NSBoldFontMask range:range];
206     }
208     // TODO: cache italic font and apply in setAttributes:range:
209     if (flags & DRAW_ITALIC) {
210         [attribString applyFontTraits:NSItalicFontMask range:range];
211     }
213     if (flags & DRAW_UNDERL) {
214         NSNumber *value = [NSNumber numberWithInt:(NSUnderlineStyleSingle
215                 | NSUnderlinePatternSolid)]; // | NSUnderlineByWordMask
216         [attribString addAttribute:NSUnderlineStyleAttributeName
217                 value:value range:range];
218     }
220     // TODO: figure out how do draw proper undercurls
221     if (flags & DRAW_UNDERC) {
222         NSNumber *value = [NSNumber numberWithInt:(NSUnderlineStyleThick
223                 | NSUnderlinePatternDot)]; // | NSUnderlineByWordMask
224         [attribString addAttribute:NSUnderlineStyleAttributeName
225                 value:value range:range];
226     }
228     [self edited:(NSTextStorageEditedCharacters|NSTextStorageEditedAttributes)
229            range:range changeInLength:0];
233  * Delete 'count' lines from 'row' and insert 'count' empty lines at the bottom
234  * of the scroll region.
235  */
236 - (void)deleteLinesFromRow:(int)row lineCount:(int)count
237               scrollBottom:(int)bottom left:(int)left right:(int)right
238                      color:(NSColor *)color
240     //NSLog(@"deleteLinesFromRow:%d lineCount:%d", row, count);
241     [self lazyResize];
243     if (row < 0 || row+count > maxRows) {
244         //NSLog(@"[%s] WARNING : out of range, row=%d (%d) count=%d", _cmd, row,
245         //        maxRows, count);
246         return;
247     }
249     int total = 1 + bottom - row;
250     int move = total - count;
251     int width = right - left + 1;
252     NSRange destRange = { row*(maxColumns+1) + left, width };
253     NSRange srcRange = { (row+count)*(maxColumns+1) + left, width };
254     int i;
256     for (i = 0; i < move; ++i) {
257         NSAttributedString *srcString = [attribString
258                 attributedSubstringFromRange:srcRange];
259         [attribString replaceCharactersInRange:destRange
260                           withAttributedString:srcString];
261         [self edited:(NSTextStorageEditedCharacters
262                 | NSTextStorageEditedAttributes)
263                 range:destRange changeInLength:0];
264         destRange.location += maxColumns+1;
265         srcRange.location += maxColumns+1;
266     }
268     for (i = 0; i < count; ++i) {
269         NSDictionary *attribs = [NSDictionary dictionaryWithObjectsAndKeys:
270                 font, NSFontAttributeName,
271                 color, NSForegroundColorAttributeName,
272                 color, NSBackgroundColorAttributeName, nil];
273         [attribString setAttributes:attribs range:destRange];
274         [self edited:NSTextStorageEditedAttributes range:destRange
275                 changeInLength:0];
276         destRange.location += maxColumns+1;
277     }
281  * Insert 'count' empty lines at 'row' and delete 'count' lines from the bottom
282  * of the scroll region.
283  */
284 - (void)insertLinesAtRow:(int)row lineCount:(int)count
285             scrollBottom:(int)bottom left:(int)left right:(int)right
286                    color:(NSColor *)color
288     //NSLog(@"insertLinesAtRow:%d lineCount:%d", row, count);
289     [self lazyResize];
291     if (row < 0 || row+count > maxRows) {
292         //NSLog(@"[%s] WARNING : out of range, row=%d (%d) count=%d", _cmd, row,
293         //        maxRows, count);
294         return;
295     }
297     int total = 1 + bottom - row;
298     int move = total - count;
299     int width = right - left + 1;
300     NSRange destRange = { bottom*(maxColumns+1) + left, width };
301     NSRange srcRange = { (row+move-1)*(maxColumns+1) + left, width };
302     int i;
304     for (i = 0; i < move; ++i) {
305         NSAttributedString *srcString = [attribString
306                 attributedSubstringFromRange:srcRange];
307         [attribString replaceCharactersInRange:destRange
308                           withAttributedString:srcString];
309         [self edited:(NSTextStorageEditedCharacters
310                 | NSTextStorageEditedAttributes)
311                 range:destRange changeInLength:0];
312         destRange.location -= maxColumns+1;
313         srcRange.location -= maxColumns+1;
314     }
316     for (i = 0; i < count; ++i) {
317         NSDictionary *attribs = [NSDictionary dictionaryWithObjectsAndKeys:
318                 font, NSFontAttributeName,
319                 color, NSForegroundColorAttributeName,
320                 color, NSBackgroundColorAttributeName, nil];
321         [attribString setAttributes:attribs range:destRange];
322         [self edited:NSTextStorageEditedAttributes range:destRange
323                 changeInLength:0];
324         destRange.location -= maxColumns+1;
325     }
328 - (void)clearBlockFromRow:(int)row1 column:(int)col1 toRow:(int)row2
329                    column:(int)col2 color:(NSColor *)color
331     //NSLog(@"clearBlockFromRow:%d column:%d toRow:%d column:%d", row1, col1,
332     //        row2, col2);
333     [self lazyResize];
335     if (row1 < 0 || row2 >= maxRows || col1 < 0 || col2 > maxColumns) {
336         //NSLog(@"[%s] WARNING : out of range, row1=%d row2=%d (%d) col1=%d "
337         //        "col2=%d (%d)", _cmd, row1, row2, maxRows, col1, col2,
338         //        maxColumns);
339         return;
340     }
342     NSDictionary *attribs = [NSDictionary dictionaryWithObjectsAndKeys:
343             font, NSFontAttributeName,
344             color, NSForegroundColorAttributeName,
345             color, NSBackgroundColorAttributeName, nil];
347     NSRange range = { row1*(maxColumns+1) + col1, col2-col1+1 };
349     int r;
350     for (r=row1; r<=row2; ++r) {
351         [attribString setAttributes:attribs range:range];
352         [self edited:NSTextStorageEditedAttributes range:range
353                 changeInLength:0];
354         range.location += maxColumns+1;
355     }
358 - (void)clearAllWithColor:(NSColor *)color
360     //NSLog(@"%s%@", _cmd, color);
362     NSRange range = { 0, [attribString length] };
363     NSDictionary *attribs = [NSDictionary dictionaryWithObjectsAndKeys:
364             font, NSFontAttributeName,
365             color, NSForegroundColorAttributeName,
366             color, NSBackgroundColorAttributeName, nil];
367     [attribString setAttributes:attribs range:range];
368     [self edited:NSTextStorageEditedAttributes range:range changeInLength:0];
371 - (void)setDefaultColorsBackground:(NSColor *)bgColor
372                         foreground:(NSColor *)fgColor
374     // NOTE: Foreground color is ignored.
375     [defaultBackgroundColor release];
376     defaultBackgroundColor = bgColor ? [bgColor retain] : nil;
379 - (void)setFont:(NSFont*)newFont
381     if (newFont && font != newFont) {
382         [font release];
384         // NOTE! When setting a new font we make sure that the advancement of
385         // each glyph is fixed. 
387         float em = [newFont widthOfString:@"m"];
388         float cellWidthMultiplier = [[NSUserDefaults standardUserDefaults]
389                 floatForKey:MMCellWidthMultiplierKey];
391         // NOTE! Even though NSFontFixedAdvanceAttribute is a float, it will
392         // only render at integer sizes.  Hence, we restrict the cell width to
393         // an integer here, otherwise the window width and the actual text
394         // width will not match.
395         cellSize.width = ceilf(em * cellWidthMultiplier);
397         NSDictionary *dict = [NSDictionary
398             dictionaryWithObject:[NSNumber numberWithFloat:cellSize.width]
399                           forKey:NSFontFixedAdvanceAttribute];
400         NSFontDescriptor *desc = [newFont fontDescriptor];
401         desc = [desc fontDescriptorByAddingAttributes:dict];
403         font = [NSFont fontWithDescriptor:desc size:[newFont pointSize]];
404         [font retain];
406         NSLayoutManager *lm = [[self layoutManagers] objectAtIndex:0];
407         cellSize.height = lm ? [lm defaultLineHeightForFont:font]
408                              : [font defaultLineHeightForFont];
409     }
412 - (NSFont*)font
414     return font;
417 - (NSSize)size
419     return NSMakeSize(maxColumns*cellSize.width, maxRows*cellSize.height);
422 - (NSSize)cellSize
424     return cellSize;
427 - (NSRect)rectForRowsInRange:(NSRange)range
429     NSRect rect = { 0, 0, 0, 0 };
430     unsigned start = range.location > maxRows ? maxRows : range.location;
431     unsigned length = range.length;
433     if (start+length > maxRows)
434         length = maxRows - start;
436     rect.origin.y = cellSize.height * start;
437     rect.size.height = cellSize.height * length;
439     return rect;
442 - (NSRect)rectForColumnsInRange:(NSRange)range
444     NSRect rect = { 0, 0, 0, 0 };
445     unsigned start = range.location > maxColumns ? maxColumns : range.location;
446     unsigned length = range.length;
448     if (start+length > maxColumns)
449         length = maxColumns - start;
451     rect.origin.x = cellSize.width * start;
452     rect.size.width = cellSize.width * length;
454     return rect;
457 - (unsigned)offsetFromRow:(int)row column:(int)col
459     // Ensure the offset returned is valid.
460     // This code also works if maxRows and/or maxColumns is 0.
461     if (row >= maxRows) row = maxRows-1;
462     if (row < 0) row = 0;
463     if (col >= maxColumns) col = maxColumns-1;
464     if (col < 0) col = 0;
466     return (unsigned)(col + row*(maxColumns+1));
469 - (BOOL)resizeToFitSize:(NSSize)size
471     int rows = maxRows, cols = maxColumns;
473     [self fitToSize:size rows:&rows columns:&cols];
474     if (rows != maxRows || cols != maxColumns) {
475         [self setMaxRows:rows columns:cols];
476         return YES;
477     }
479     // Return NO only if dimensions did not change.
480     return NO;
483 - (NSSize)fitToSize:(NSSize)size
485     return [self fitToSize:size rows:NULL columns:NULL];
488 - (NSSize)fitToSize:(NSSize)size rows:(int *)rows columns:(int *)columns
490     NSSize curSize = [self size];
491     NSSize fitSize = curSize;
492     int fitRows = maxRows;
493     int fitCols = maxColumns;
495     if (size.height < curSize.height) {
496         // Remove lines until the height of the text storage fits inside
497         // 'size'.  However, always make sure there are at least 3 lines in the
498         // text storage.  (Why 3? It seem Vim never allows less than 3 lines.)
499         //
500         // TODO: No need to search since line height is fixed, just calculate
501         // the new height.
502         int rowCount = maxRows;
503         int rowsToRemove;
504         for (rowsToRemove = 0; rowsToRemove < maxRows-3; ++rowsToRemove) {
505             float height = cellSize.height*rowCount;
507             if (height <= size.height) {
508                 fitSize.height = height;
509                 break;
510             }
512             --rowCount;
513         }
515         fitRows -= rowsToRemove;
516     } else if (size.height > curSize.height) {
517         float fh = cellSize.height;
518         if (fh < 1.0f) fh = 1.0f;
520         fitRows = floor(size.height/fh);
521         fitSize.height = fh*fitRows;
522     }
524     if (size.width != curSize.width) {
525         float fw = cellSize.width;
526         if (fw < 1.0f) fw = 1.0f;
528         fitCols = floor(size.width/fw);
529         fitSize.width = fw*fitCols;
530     }
532     if (rows) *rows = fitRows;
533     if (columns) *columns = fitCols;
535     return fitSize;
538 @end // MMTextStorage
543 @implementation MMTextStorage (Private)
544 - (void)lazyResize
546     int i;
548     // Do nothing if the dimensions are already right.
549     if (actualRows == maxRows && actualColumns == maxColumns)
550         return;
552     NSRange oldRange = NSMakeRange(0, actualRows*(actualColumns+1));
554     actualRows = maxRows;
555     actualColumns = maxColumns;
557     NSDictionary *dict;
558     if (defaultBackgroundColor) {
559         dict = [NSDictionary dictionaryWithObjectsAndKeys:
560                 font, NSFontAttributeName,
561                 defaultBackgroundColor, NSBackgroundColorAttributeName, nil];
562     } else {
563         dict = [NSDictionary dictionaryWithObjectsAndKeys:
564                 font, NSFontAttributeName, nil];
565     }
566             
567     NSMutableString *rowString = [NSMutableString string];
568     for (i = 0; i < maxColumns; ++i) {
569         [rowString appendString:@" "];
570     }
571     [rowString appendString:@"\n"];
573     [emptyRowString release];
574     emptyRowString = [[NSAttributedString alloc] initWithString:rowString
575                                                      attributes:dict];
577     [attribString release];
578     attribString = [[NSMutableAttributedString alloc] init];
579     for (i=0; i<maxRows; ++i) {
580         [attribString appendAttributedString:emptyRowString];
581     }
583     NSRange fullRange = NSMakeRange(0, [attribString length]);
584     [self edited:(NSTextStorageEditedCharacters|NSTextStorageEditedAttributes)
585            range:oldRange changeInLength:fullRange.length-oldRange.length];
588 @end // MMTextStorage (Private)