Use 'behave mswin' instead of setting 'keymodel' and 'selectmode'.
[MacVim/jjgod.git] / MMTextStorage.m
blob7574f13a6ab99776046fe33fbdeabb86cab0549b
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. TODO: Figure out why this flag is set.
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 !HEED_DRAW_TRANSP
165     if (flags & DRAW_TRANSP)
166         return;
167 #endif
169     if (row < 0 || row >= maxRows || col < 0 || col >= maxColumns
170             || col+[string length] > maxColumns) {
171         //NSLog(@"[%s] WARNING : out of range, row=%d (%d) col=%d (%d) "
172         //        "length=%d (%d)", _cmd, row, maxRows, col, maxColumns,
173         //        [string length], [attribString length]);
174         return;
175     }
177     // NOTE: If 'string' was initialized with bad data it might be nil; this
178     // may be due to 'enc' being set to an unsupported value, so don't print an
179     // error message or stdout will most likely get flooded.
180     if (!string) return;
182     if (!(fg && bg && sp)) {
183         NSLog(@"[%s] WARNING: background, foreground or special color not "
184                 "specified", _cmd);
185         return;
186     }
188     NSRange range = NSMakeRange(col+row*(maxColumns+1), [string length]);
189     [attribString replaceCharactersInRange:range withString:string];
191     NSDictionary *attributes = [NSDictionary dictionaryWithObjectsAndKeys:
192             font, NSFontAttributeName,
193             bg, NSBackgroundColorAttributeName,
194             fg, NSForegroundColorAttributeName,
195             sp, NSUnderlineColorAttributeName,
196             nil];
197     [attribString setAttributes:attributes range:range];
199 #if HEED_DRAW_TRANSP
200     if ( !(flags & DRAW_TRANSP) ) {
201         [attribString addAttribute:NSBackgroundColorAttributeName value:bg
202                 range:range];
203     }
204 #endif
206     // TODO: cache bold font and apply in setAttributes:range:
207     if (flags & DRAW_BOLD) {
208         [attribString applyFontTraits:NSBoldFontMask range:range];
209     }
211     // TODO: cache italic font and apply in setAttributes:range:
212     if (flags & DRAW_ITALIC) {
213         [attribString applyFontTraits:NSItalicFontMask range:range];
214     }
216     if (flags & DRAW_UNDERL) {
217         NSNumber *value = [NSNumber numberWithInt:(NSUnderlineStyleSingle
218                 | NSUnderlinePatternSolid)]; // | NSUnderlineByWordMask
219         [attribString addAttribute:NSUnderlineStyleAttributeName
220                 value:value range:range];
221     }
223     // TODO: figure out how do draw proper undercurls
224     if (flags & DRAW_UNDERC) {
225         NSNumber *value = [NSNumber numberWithInt:(NSUnderlineStyleThick
226                 | NSUnderlinePatternDot)]; // | NSUnderlineByWordMask
227         [attribString addAttribute:NSUnderlineStyleAttributeName
228                 value:value range:range];
229     }
231     [self edited:(NSTextStorageEditedCharacters|NSTextStorageEditedAttributes)
232            range:range changeInLength:0];
236  * Delete 'count' lines from 'row' and insert 'count' empty lines at the bottom
237  * of the scroll region.
238  */
239 - (void)deleteLinesFromRow:(int)row lineCount:(int)count
240               scrollBottom:(int)bottom left:(int)left right:(int)right
241                      color:(NSColor *)color
243     //NSLog(@"deleteLinesFromRow:%d lineCount:%d", row, count);
244     [self lazyResize];
246     if (row < 0 || row+count > maxRows) {
247         //NSLog(@"[%s] WARNING : out of range, row=%d (%d) count=%d", _cmd, row,
248         //        maxRows, count);
249         return;
250     }
252     int total = 1 + bottom - row;
253     int move = total - count;
254     int width = right - left + 1;
255     NSRange destRange = { row*(maxColumns+1) + left, width };
256     NSRange srcRange = { (row+count)*(maxColumns+1) + left, width };
257     int i;
259     for (i = 0; i < move; ++i) {
260         NSAttributedString *srcString = [attribString
261                 attributedSubstringFromRange:srcRange];
262         [attribString replaceCharactersInRange:destRange
263                           withAttributedString:srcString];
264         [self edited:(NSTextStorageEditedCharacters
265                 | NSTextStorageEditedAttributes)
266                 range:destRange changeInLength:0];
267         destRange.location += maxColumns+1;
268         srcRange.location += maxColumns+1;
269     }
271     for (i = 0; i < count; ++i) {
272         NSDictionary *attribs = [NSDictionary dictionaryWithObjectsAndKeys:
273                 font, NSFontAttributeName,
274                 color, NSForegroundColorAttributeName,
275                 color, NSBackgroundColorAttributeName, nil];
276         [attribString setAttributes:attribs range:destRange];
277         [self edited:NSTextStorageEditedAttributes range:destRange
278                 changeInLength:0];
279         destRange.location += maxColumns+1;
280     }
284  * Insert 'count' empty lines at 'row' and delete 'count' lines from the bottom
285  * of the scroll region.
286  */
287 - (void)insertLinesAtRow:(int)row lineCount:(int)count
288             scrollBottom:(int)bottom left:(int)left right:(int)right
289                    color:(NSColor *)color
291     //NSLog(@"insertLinesAtRow:%d lineCount:%d", row, count);
292     [self lazyResize];
294     if (row < 0 || row+count > maxRows) {
295         //NSLog(@"[%s] WARNING : out of range, row=%d (%d) count=%d", _cmd, row,
296         //        maxRows, count);
297         return;
298     }
300     int total = 1 + bottom - row;
301     int move = total - count;
302     int width = right - left + 1;
303     NSRange destRange = { bottom*(maxColumns+1) + left, width };
304     NSRange srcRange = { (row+move-1)*(maxColumns+1) + left, width };
305     int i;
307     for (i = 0; i < move; ++i) {
308         NSAttributedString *srcString = [attribString
309                 attributedSubstringFromRange:srcRange];
310         [attribString replaceCharactersInRange:destRange
311                           withAttributedString:srcString];
312         [self edited:(NSTextStorageEditedCharacters
313                 | NSTextStorageEditedAttributes)
314                 range:destRange changeInLength:0];
315         destRange.location -= maxColumns+1;
316         srcRange.location -= maxColumns+1;
317     }
319     for (i = 0; i < count; ++i) {
320         NSDictionary *attribs = [NSDictionary dictionaryWithObjectsAndKeys:
321                 font, NSFontAttributeName,
322                 color, NSForegroundColorAttributeName,
323                 color, NSBackgroundColorAttributeName, nil];
324         [attribString setAttributes:attribs range:destRange];
325         [self edited:NSTextStorageEditedAttributes range:destRange
326                 changeInLength:0];
327         destRange.location -= maxColumns+1;
328     }
331 - (void)clearBlockFromRow:(int)row1 column:(int)col1 toRow:(int)row2
332                    column:(int)col2 color:(NSColor *)color
334     //NSLog(@"clearBlockFromRow:%d column:%d toRow:%d column:%d", row1, col1,
335     //        row2, col2);
336     [self lazyResize];
338     if (row1 < 0 || row2 >= maxRows || col1 < 0 || col2 > maxColumns) {
339         //NSLog(@"[%s] WARNING : out of range, row1=%d row2=%d (%d) col1=%d "
340         //        "col2=%d (%d)", _cmd, row1, row2, maxRows, col1, col2,
341         //        maxColumns);
342         return;
343     }
345     NSDictionary *attribs = [NSDictionary dictionaryWithObjectsAndKeys:
346             font, NSFontAttributeName,
347             color, NSForegroundColorAttributeName,
348             color, NSBackgroundColorAttributeName, nil];
350     NSRange range = { row1*(maxColumns+1) + col1, col2-col1+1 };
352     int r;
353     for (r=row1; r<=row2; ++r) {
354         [attribString setAttributes:attribs range:range];
355         [self edited:NSTextStorageEditedAttributes range:range
356                 changeInLength:0];
357         range.location += maxColumns+1;
358     }
361 - (void)clearAllWithColor:(NSColor *)color
363     //NSLog(@"%s%@", _cmd, color);
365     NSRange range = { 0, [attribString length] };
366     NSDictionary *attribs = [NSDictionary dictionaryWithObjectsAndKeys:
367             font, NSFontAttributeName,
368             color, NSForegroundColorAttributeName,
369             color, NSBackgroundColorAttributeName, nil];
370     [attribString setAttributes:attribs range:range];
371     [self edited:NSTextStorageEditedAttributes range:range changeInLength:0];
374 - (void)setDefaultColorsBackground:(NSColor *)bgColor
375                         foreground:(NSColor *)fgColor
377     // NOTE: Foreground color is ignored.
378     [defaultBackgroundColor release];
379     defaultBackgroundColor = bgColor ? [bgColor retain] : nil;
382 - (void)setFont:(NSFont*)newFont
384     if (newFont && font != newFont) {
385         [font release];
387         // NOTE! When setting a new font we make sure that the advancement of
388         // each glyph is fixed. 
390         float em = [newFont widthOfString:@"m"];
391         float cellWidthMultiplier = [[NSUserDefaults standardUserDefaults]
392                 floatForKey:MMCellWidthMultiplierKey];
394         // NOTE! Even though NSFontFixedAdvanceAttribute is a float, it will
395         // only render at integer sizes.  Hence, we restrict the cell width to
396         // an integer here, otherwise the window width and the actual text
397         // width will not match.
398         cellSize.width = ceilf(em * cellWidthMultiplier);
400         NSDictionary *dict = [NSDictionary
401             dictionaryWithObject:[NSNumber numberWithFloat:cellSize.width]
402                           forKey:NSFontFixedAdvanceAttribute];
403         NSFontDescriptor *desc = [newFont fontDescriptor];
404         desc = [desc fontDescriptorByAddingAttributes:dict];
406         font = [NSFont fontWithDescriptor:desc size:[newFont pointSize]];
407         [font retain];
409         NSLayoutManager *lm = [[self layoutManagers] objectAtIndex:0];
410         cellSize.height = lm ? [lm defaultLineHeightForFont:font]
411                              : [font defaultLineHeightForFont];
412     }
415 - (NSFont*)font
417     return font;
420 - (NSSize)size
422     return NSMakeSize(maxColumns*cellSize.width, maxRows*cellSize.height);
425 - (NSSize)cellSize
427     return cellSize;
430 - (NSRect)rectForRowsInRange:(NSRange)range
432     NSRect rect = { 0, 0, 0, 0 };
433     unsigned start = range.location > maxRows ? maxRows : range.location;
434     unsigned length = range.length;
436     if (start+length > maxRows)
437         length = maxRows - start;
439     rect.origin.y = cellSize.height * start;
440     rect.size.height = cellSize.height * length;
442     return rect;
445 - (NSRect)rectForColumnsInRange:(NSRange)range
447     NSRect rect = { 0, 0, 0, 0 };
448     unsigned start = range.location > maxColumns ? maxColumns : range.location;
449     unsigned length = range.length;
451     if (start+length > maxColumns)
452         length = maxColumns - start;
454     rect.origin.x = cellSize.width * start;
455     rect.size.width = cellSize.width * length;
457     return rect;
460 - (unsigned)characterIndexForRow:(int)row column:(int)col
462     // Ensure the offset returned is valid.
463     // This code also works if maxRows and/or maxColumns is 0.
464     if (row >= maxRows) row = maxRows-1;
465     if (row < 0) row = 0;
466     if (col >= maxColumns) col = maxColumns-1;
467     if (col < 0) col = 0;
469     return (unsigned)(col + row*(maxColumns+1));
472 - (BOOL)resizeToFitSize:(NSSize)size
474     int rows = maxRows, cols = maxColumns;
476     [self fitToSize:size rows:&rows columns:&cols];
477     if (rows != maxRows || cols != maxColumns) {
478         [self setMaxRows:rows columns:cols];
479         return YES;
480     }
482     // Return NO only if dimensions did not change.
483     return NO;
486 - (NSSize)fitToSize:(NSSize)size
488     return [self fitToSize:size rows:NULL columns:NULL];
491 - (NSSize)fitToSize:(NSSize)size rows:(int *)rows columns:(int *)columns
493     NSSize curSize = [self size];
494     NSSize fitSize = curSize;
495     int fitRows = maxRows;
496     int fitCols = maxColumns;
498     if (size.height < curSize.height) {
499         // Remove lines until the height of the text storage fits inside
500         // 'size'.  However, always make sure there are at least 3 lines in the
501         // text storage.  (Why 3? It seem Vim never allows less than 3 lines.)
502         //
503         // TODO: No need to search since line height is fixed, just calculate
504         // the new height.
505         int rowCount = maxRows;
506         int rowsToRemove;
507         for (rowsToRemove = 0; rowsToRemove < maxRows-3; ++rowsToRemove) {
508             float height = cellSize.height*rowCount;
510             if (height <= size.height) {
511                 fitSize.height = height;
512                 break;
513             }
515             --rowCount;
516         }
518         fitRows -= rowsToRemove;
519     } else if (size.height > curSize.height) {
520         float fh = cellSize.height;
521         if (fh < 1.0f) fh = 1.0f;
523         fitRows = floor(size.height/fh);
524         fitSize.height = fh*fitRows;
525     }
527     if (size.width != curSize.width) {
528         float fw = cellSize.width;
529         if (fw < 1.0f) fw = 1.0f;
531         fitCols = floor(size.width/fw);
532         fitSize.width = fw*fitCols;
533     }
535     if (rows) *rows = fitRows;
536     if (columns) *columns = fitCols;
538     return fitSize;
541 @end // MMTextStorage
546 @implementation MMTextStorage (Private)
547 - (void)lazyResize
549     int i;
551     // Do nothing if the dimensions are already right.
552     if (actualRows == maxRows && actualColumns == maxColumns)
553         return;
555     NSRange oldRange = NSMakeRange(0, actualRows*(actualColumns+1));
557     actualRows = maxRows;
558     actualColumns = maxColumns;
560     NSDictionary *dict;
561     if (defaultBackgroundColor) {
562         dict = [NSDictionary dictionaryWithObjectsAndKeys:
563                 font, NSFontAttributeName,
564                 defaultBackgroundColor, NSBackgroundColorAttributeName, nil];
565     } else {
566         dict = [NSDictionary dictionaryWithObjectsAndKeys:
567                 font, NSFontAttributeName, nil];
568     }
569             
570     NSMutableString *rowString = [NSMutableString string];
571     for (i = 0; i < maxColumns; ++i) {
572         [rowString appendString:@" "];
573     }
574     [rowString appendString:@"\n"];
576     [emptyRowString release];
577     emptyRowString = [[NSAttributedString alloc] initWithString:rowString
578                                                      attributes:dict];
580     [attribString release];
581     attribString = [[NSMutableAttributedString alloc] init];
582     for (i=0; i<maxRows; ++i) {
583         [attribString appendAttributedString:emptyRowString];
584     }
586     NSRange fullRange = NSMakeRange(0, [attribString length]);
587     [self edited:(NSTextStorageEditedCharacters|NSTextStorageEditedAttributes)
588            range:oldRange changeInLength:fullRange.length-oldRange.length];
591 @end // MMTextStorage (Private)