- Marked text is displayed in a popup window
[MacVim/jjgod.git] / MMTextStorage.m
blob8d288342867fb565f6442fcd65139d75866ad648
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     [defaultForegroundColor release];
59     [attribString release];
60     [super dealloc];
63 - (NSString *)string
65     //NSLog(@"%s : attribString=%@", _cmd, attribString);
66     return [attribString string];
69 - (NSDictionary *)attributesAtIndex:(unsigned)index
70                      effectiveRange:(NSRangePointer)range
72     //NSLog(@"%s", _cmd);
73     if (index>=[attribString length]) {
74         //NSLog(@"%sWARNING: index (%d) out of bounds", _cmd, index);
75         if (range) {
76             *range = NSMakeRange(NSNotFound, 0);
77         }
78         return [NSDictionary dictionary];
79     }
81     return [attribString attributesAtIndex:index effectiveRange:range];
84 - (void)replaceCharactersInRange:(NSRange)range
85                       withString:(NSString *)string
87     //NSLog(@"replaceCharactersInRange:(%d,%d) withString:%@", range.location,
88     //        range.length, string);
89     NSLog(@"WARNING: calling %s on MMTextStorage is unsupported", _cmd);
90     //[attribString replaceCharactersInRange:range withString:string];
93 - (void)setAttributes:(NSDictionary *)attributes range:(NSRange)range
95     // NOTE!  This method must be implemented since the text system calls it
96     // constantly to 'fix attributes', apply font substitution, etc.
97 #if 0
98     [attribString setAttributes:attributes range:range];
99 #else
100     // HACK! If the font attribute is being modified, then ensure that the new
101     // font has a fixed advancement which is either the same as the current
102     // font or twice that, depending on whether it is a 'wide' character that
103     // is being fixed or not.  This code really only works if 'range' has
104     // length 1 or 2.
105     NSFont *newFont = [attributes objectForKey:NSFontAttributeName];
106     if (newFont) {
107         float adv = cellSize.width;
108         if ([attribString length] > range.location+1) {
109             // If the first char is followed by zero-width space, then it is a
110             // 'wide' character, so double the advancement.
111             NSString *string = [attribString string];
112             if ([string characterAtIndex:range.location+1] == 0x200b)
113                 adv += adv;
114         }
116         // Create a new font which has the 'fixed advance attribute' set.
117         NSDictionary *dict = [NSDictionary dictionaryWithObjectsAndKeys:
118             [NSNumber numberWithFloat:adv], NSFontFixedAdvanceAttribute, nil];
119         NSFontDescriptor *desc = [newFont fontDescriptor];
120         desc = [desc fontDescriptorByAddingAttributes:dict];
121         newFont = [NSFont fontWithDescriptor:desc size:[newFont pointSize]];
123         // Now modify the 'attributes' dictionary to hold the new font.
124         NSMutableDictionary *newAttr = [NSMutableDictionary
125             dictionaryWithDictionary:attributes];
126         [newAttr setObject:newFont forKey:NSFontAttributeName];
128         [attribString setAttributes:newAttr range:range];
129     } else {
130         [attribString setAttributes:attributes range:range];
131     }
132 #endif
135 - (int)maxRows
137     return maxRows;
140 - (int)maxColumns
142     return maxColumns;
145 - (void)getMaxRows:(int*)rows columns:(int*)cols
147     if (rows) *rows = maxRows;
148     if (cols) *cols = maxColumns;
151 - (void)setMaxRows:(int)rows columns:(int)cols
153     // NOTE: Just remember the new values, the actual resizing is done lazily.
154     maxRows = rows;
155     maxColumns = cols;
158 - (void)replaceString:(NSString *)string atRow:(int)row column:(int)col
159             withFlags:(int)flags foregroundColor:(NSColor *)fg
160       backgroundColor:(NSColor *)bg specialColor:(NSColor *)sp
162     //NSLog(@"replaceString:atRow:%d column:%d withFlags:%d", row, col, flags);
163     [self lazyResize];
165 #if !HEED_DRAW_TRANSP
166     if (flags & DRAW_TRANSP)
167         return;
168 #endif
170     if (row < 0 || row >= maxRows || col < 0 || col >= maxColumns
171             || col+[string length] > maxColumns) {
172         //NSLog(@"[%s] WARNING : out of range, row=%d (%d) col=%d (%d) "
173         //        "length=%d (%d)", _cmd, row, maxRows, col, maxColumns,
174         //        [string length], [attribString length]);
175         return;
176     }
178     // NOTE: If 'string' was initialized with bad data it might be nil; this
179     // may be due to 'enc' being set to an unsupported value, so don't print an
180     // error message or stdout will most likely get flooded.
181     if (!string) return;
183     if (!(fg && bg && sp)) {
184         NSLog(@"[%s] WARNING: background, foreground or special color not "
185                 "specified", _cmd);
186         return;
187     }
189     NSRange range = NSMakeRange(col+row*(maxColumns+1), [string length]);
190     [attribString replaceCharactersInRange:range withString:string];
192     NSDictionary *attributes = [NSDictionary dictionaryWithObjectsAndKeys:
193             font, NSFontAttributeName,
194             bg, NSBackgroundColorAttributeName,
195             fg, NSForegroundColorAttributeName,
196             sp, NSUnderlineColorAttributeName,
197             nil];
198     [attribString setAttributes:attributes range:range];
200 #if HEED_DRAW_TRANSP
201     if ( !(flags & DRAW_TRANSP) ) {
202         [attribString addAttribute:NSBackgroundColorAttributeName value:bg
203                 range:range];
204     }
205 #endif
207     // TODO: cache bold font and apply in setAttributes:range:
208     if (flags & DRAW_BOLD) {
209         [attribString applyFontTraits:NSBoldFontMask range:range];
210     }
212     // TODO: cache italic font and apply in setAttributes:range:
213     if (flags & DRAW_ITALIC) {
214         [attribString applyFontTraits:NSItalicFontMask range:range];
215     }
217     if (flags & DRAW_UNDERL) {
218         NSNumber *value = [NSNumber numberWithInt:(NSUnderlineStyleSingle
219                 | NSUnderlinePatternSolid)]; // | NSUnderlineByWordMask
220         [attribString addAttribute:NSUnderlineStyleAttributeName
221                 value:value range:range];
222     }
224     // TODO: figure out how do draw proper undercurls
225     if (flags & DRAW_UNDERC) {
226         NSNumber *value = [NSNumber numberWithInt:(NSUnderlineStyleThick
227                 | NSUnderlinePatternDot)]; // | NSUnderlineByWordMask
228         [attribString addAttribute:NSUnderlineStyleAttributeName
229                 value:value range:range];
230     }
232     [self edited:(NSTextStorageEditedCharacters|NSTextStorageEditedAttributes)
233            range:range changeInLength:0];
237  * Delete 'count' lines from 'row' and insert 'count' empty lines at the bottom
238  * of the scroll region.
239  */
240 - (void)deleteLinesFromRow:(int)row lineCount:(int)count
241               scrollBottom:(int)bottom left:(int)left right:(int)right
242                      color:(NSColor *)color
244     //NSLog(@"deleteLinesFromRow:%d lineCount:%d", row, count);
245     [self lazyResize];
247     if (row < 0 || row+count > maxRows) {
248         //NSLog(@"[%s] WARNING : out of range, row=%d (%d) count=%d", _cmd, row,
249         //        maxRows, count);
250         return;
251     }
253     int total = 1 + bottom - row;
254     int move = total - count;
255     int width = right - left + 1;
256     NSRange destRange = { row*(maxColumns+1) + left, width };
257     NSRange srcRange = { (row+count)*(maxColumns+1) + left, width };
258     int i;
260     for (i = 0; i < move; ++i) {
261         NSAttributedString *srcString = [attribString
262                 attributedSubstringFromRange:srcRange];
263         [attribString replaceCharactersInRange:destRange
264                           withAttributedString:srcString];
265         [self edited:(NSTextStorageEditedCharacters
266                 | NSTextStorageEditedAttributes)
267                 range:destRange changeInLength:0];
268         destRange.location += maxColumns+1;
269         srcRange.location += maxColumns+1;
270     }
272     for (i = 0; i < count; ++i) {
273         NSDictionary *attribs = [NSDictionary dictionaryWithObjectsAndKeys:
274                 font, NSFontAttributeName,
275                 color, NSForegroundColorAttributeName,
276                 color, NSBackgroundColorAttributeName, nil];
277         [attribString setAttributes:attribs range:destRange];
278         [self edited:NSTextStorageEditedAttributes range:destRange
279                 changeInLength:0];
280         destRange.location += maxColumns+1;
281     }
285  * Insert 'count' empty lines at 'row' and delete 'count' lines from the bottom
286  * of the scroll region.
287  */
288 - (void)insertLinesAtRow:(int)row lineCount:(int)count
289             scrollBottom:(int)bottom left:(int)left right:(int)right
290                    color:(NSColor *)color
292     //NSLog(@"insertLinesAtRow:%d lineCount:%d", row, count);
293     [self lazyResize];
295     if (row < 0 || row+count > maxRows) {
296         //NSLog(@"[%s] WARNING : out of range, row=%d (%d) count=%d", _cmd, row,
297         //        maxRows, count);
298         return;
299     }
301     int total = 1 + bottom - row;
302     int move = total - count;
303     int width = right - left + 1;
304     NSRange destRange = { bottom*(maxColumns+1) + left, width };
305     NSRange srcRange = { (row+move-1)*(maxColumns+1) + left, width };
306     int i;
308     for (i = 0; i < move; ++i) {
309         NSAttributedString *srcString = [attribString
310                 attributedSubstringFromRange:srcRange];
311         [attribString replaceCharactersInRange:destRange
312                           withAttributedString:srcString];
313         [self edited:(NSTextStorageEditedCharacters
314                 | NSTextStorageEditedAttributes)
315                 range:destRange changeInLength:0];
316         destRange.location -= maxColumns+1;
317         srcRange.location -= maxColumns+1;
318     }
320     for (i = 0; i < count; ++i) {
321         NSDictionary *attribs = [NSDictionary dictionaryWithObjectsAndKeys:
322                 font, NSFontAttributeName,
323                 color, NSForegroundColorAttributeName,
324                 color, NSBackgroundColorAttributeName, nil];
325         [attribString setAttributes:attribs range:destRange];
326         [self edited:NSTextStorageEditedAttributes range:destRange
327                 changeInLength:0];
328         destRange.location -= maxColumns+1;
329     }
332 - (void)clearBlockFromRow:(int)row1 column:(int)col1 toRow:(int)row2
333                    column:(int)col2 color:(NSColor *)color
335     //NSLog(@"clearBlockFromRow:%d column:%d toRow:%d column:%d", row1, col1,
336     //        row2, col2);
337     [self lazyResize];
339     if (row1 < 0 || row2 >= maxRows || col1 < 0 || col2 > maxColumns) {
340         //NSLog(@"[%s] WARNING : out of range, row1=%d row2=%d (%d) col1=%d "
341         //        "col2=%d (%d)", _cmd, row1, row2, maxRows, col1, col2,
342         //        maxColumns);
343         return;
344     }
346     NSDictionary *attribs = [NSDictionary dictionaryWithObjectsAndKeys:
347             font, NSFontAttributeName,
348             color, NSForegroundColorAttributeName,
349             color, NSBackgroundColorAttributeName, nil];
351     NSRange range = { row1*(maxColumns+1) + col1, col2-col1+1 };
353     int r;
354     for (r=row1; r<=row2; ++r) {
355         [attribString setAttributes:attribs range:range];
356         [self edited:NSTextStorageEditedAttributes range:range
357                 changeInLength:0];
358         range.location += maxColumns+1;
359     }
362 - (void)clearAllWithColor:(NSColor *)color
364     //NSLog(@"%s%@", _cmd, color);
366     NSRange range = { 0, [attribString length] };
367     NSDictionary *attribs = [NSDictionary dictionaryWithObjectsAndKeys:
368             font, NSFontAttributeName,
369             color, NSForegroundColorAttributeName,
370             color, NSBackgroundColorAttributeName, nil];
371     [attribString setAttributes:attribs range:range];
372     [self edited:NSTextStorageEditedAttributes range:range changeInLength:0];
375 - (void)setDefaultColorsBackground:(NSColor *)bgColor
376                         foreground:(NSColor *)fgColor
378     if (defaultBackgroundColor != bgColor) {
379         [defaultBackgroundColor release];
380         defaultBackgroundColor = bgColor ? [bgColor retain] : nil;
381     }
383     // NOTE: The default foreground color isn't actually used for anything, but
384     // other class instances might want to be able to access it so it is stored
385     // here.
386     if (defaultForegroundColor != fgColor) {
387         [defaultForegroundColor release];
388         defaultForegroundColor = fgColor ? [fgColor retain] : nil;
389     }
392 - (void)setFont:(NSFont*)newFont
394     if (newFont && font != newFont) {
395         [font release];
397         // NOTE! When setting a new font we make sure that the advancement of
398         // each glyph is fixed. 
400         float em = [newFont widthOfString:@"m"];
401         float cellWidthMultiplier = [[NSUserDefaults standardUserDefaults]
402                 floatForKey:MMCellWidthMultiplierKey];
404         // NOTE! Even though NSFontFixedAdvanceAttribute is a float, it will
405         // only render at integer sizes.  Hence, we restrict the cell width to
406         // an integer here, otherwise the window width and the actual text
407         // width will not match.
408         cellSize.width = ceilf(em * cellWidthMultiplier);
410         NSDictionary *dict = [NSDictionary
411             dictionaryWithObject:[NSNumber numberWithFloat:cellSize.width]
412                           forKey:NSFontFixedAdvanceAttribute];
413         NSFontDescriptor *desc = [newFont fontDescriptor];
414         desc = [desc fontDescriptorByAddingAttributes:dict];
416         font = [NSFont fontWithDescriptor:desc size:[newFont pointSize]];
417         [font retain];
419         NSLayoutManager *lm = [[self layoutManagers] objectAtIndex:0];
420         cellSize.height = lm ? [lm defaultLineHeightForFont:font]
421                              : [font defaultLineHeightForFont];
422     }
425 - (NSFont*)font
427     return font;
430 - (NSColor *)defaultBackgroundColor
432     return defaultBackgroundColor;
435 - (NSColor *)defaultForegroundColor
437     return defaultForegroundColor;
440 - (NSSize)size
442     return NSMakeSize(maxColumns*cellSize.width, maxRows*cellSize.height);
445 - (NSSize)cellSize
447     return cellSize;
450 - (NSRect)rectForRowsInRange:(NSRange)range
452     NSRect rect = { 0, 0, 0, 0 };
453     unsigned start = range.location > maxRows ? maxRows : range.location;
454     unsigned length = range.length;
456     if (start+length > maxRows)
457         length = maxRows - start;
459     rect.origin.y = cellSize.height * start;
460     rect.size.height = cellSize.height * length;
462     return rect;
465 - (NSRect)rectForColumnsInRange:(NSRange)range
467     NSRect rect = { 0, 0, 0, 0 };
468     unsigned start = range.location > maxColumns ? maxColumns : range.location;
469     unsigned length = range.length;
471     if (start+length > maxColumns)
472         length = maxColumns - start;
474     rect.origin.x = cellSize.width * start;
475     rect.size.width = cellSize.width * length;
477     return rect;
480 - (unsigned)characterIndexForRow:(int)row column:(int)col
482     // Ensure the offset returned is valid.
483     // This code also works if maxRows and/or maxColumns is 0.
484     if (row >= maxRows) row = maxRows-1;
485     if (row < 0) row = 0;
486     if (col >= maxColumns) col = maxColumns-1;
487     if (col < 0) col = 0;
489     return (unsigned)(col + row*(maxColumns+1));
492 - (BOOL)resizeToFitSize:(NSSize)size
494     int rows = maxRows, cols = maxColumns;
496     [self fitToSize:size rows:&rows columns:&cols];
497     if (rows != maxRows || cols != maxColumns) {
498         [self setMaxRows:rows columns:cols];
499         return YES;
500     }
502     // Return NO only if dimensions did not change.
503     return NO;
506 - (NSSize)fitToSize:(NSSize)size
508     return [self fitToSize:size rows:NULL columns:NULL];
511 - (NSSize)fitToSize:(NSSize)size rows:(int *)rows columns:(int *)columns
513     NSSize curSize = [self size];
514     NSSize fitSize = curSize;
515     int fitRows = maxRows;
516     int fitCols = maxColumns;
518     if (size.height < curSize.height) {
519         // Remove lines until the height of the text storage fits inside
520         // 'size'.  However, always make sure there are at least 3 lines in the
521         // text storage.  (Why 3? It seem Vim never allows less than 3 lines.)
522         //
523         // TODO: No need to search since line height is fixed, just calculate
524         // the new height.
525         int rowCount = maxRows;
526         int rowsToRemove;
527         for (rowsToRemove = 0; rowsToRemove < maxRows-3; ++rowsToRemove) {
528             float height = cellSize.height*rowCount;
530             if (height <= size.height) {
531                 fitSize.height = height;
532                 break;
533             }
535             --rowCount;
536         }
538         fitRows -= rowsToRemove;
539     } else if (size.height > curSize.height) {
540         float fh = cellSize.height;
541         if (fh < 1.0f) fh = 1.0f;
543         fitRows = floor(size.height/fh);
544         fitSize.height = fh*fitRows;
545     }
547     if (size.width != curSize.width) {
548         float fw = cellSize.width;
549         if (fw < 1.0f) fw = 1.0f;
551         fitCols = floor(size.width/fw);
552         fitSize.width = fw*fitCols;
553     }
555     if (rows) *rows = fitRows;
556     if (columns) *columns = fitCols;
558     return fitSize;
561 @end // MMTextStorage
566 @implementation MMTextStorage (Private)
567 - (void)lazyResize
569     int i;
571     // Do nothing if the dimensions are already right.
572     if (actualRows == maxRows && actualColumns == maxColumns)
573         return;
575     NSRange oldRange = NSMakeRange(0, actualRows*(actualColumns+1));
577     actualRows = maxRows;
578     actualColumns = maxColumns;
580     NSDictionary *dict;
581     if (defaultBackgroundColor) {
582         dict = [NSDictionary dictionaryWithObjectsAndKeys:
583                 font, NSFontAttributeName,
584                 defaultBackgroundColor, NSBackgroundColorAttributeName, nil];
585     } else {
586         dict = [NSDictionary dictionaryWithObjectsAndKeys:
587                 font, NSFontAttributeName, nil];
588     }
589             
590     NSMutableString *rowString = [NSMutableString string];
591     for (i = 0; i < maxColumns; ++i) {
592         [rowString appendString:@" "];
593     }
594     [rowString appendString:@"\n"];
596     [emptyRowString release];
597     emptyRowString = [[NSAttributedString alloc] initWithString:rowString
598                                                      attributes:dict];
600     [attribString release];
601     attribString = [[NSMutableAttributedString alloc] init];
602     for (i=0; i<maxRows; ++i) {
603         [attribString appendAttributedString:emptyRowString];
604     }
606     NSRange fullRange = NSMakeRange(0, [attribString length]);
607     [self edited:(NSTextStorageEditedCharacters|NSTextStorageEditedAttributes)
608            range:oldRange changeInLength:fullRange.length-oldRange.length];
611 @end // MMTextStorage (Private)