1 /* vi:set ts=8 sts=4 sw=4 ft=objc:
3 * VIM - Vi IMproved by Bram Moolenaar
4 * MacVim GUI port by Bjorn Winckler
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.
11 #import "MMTextStorage.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)
33 @implementation MMTextStorage
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 cellSize.height = [font pointSize];
43 cellSize.width = [font defaultLineHeightForFont];
51 //NSLog(@"%@ %s", [self className], _cmd);
53 [emptyRowString release];
54 [boldItalicFont release];
58 [defaultBackgroundColor release];
59 [defaultForegroundColor release];
60 [attribString release];
66 //NSLog(@"%s : attribString=%@", _cmd, attribString);
67 return [attribString string];
70 - (NSDictionary *)attributesAtIndex:(unsigned)index
71 effectiveRange:(NSRangePointer)range
74 if (index>=[attribString length]) {
75 //NSLog(@"%sWARNING: index (%d) out of bounds", _cmd, index);
77 *range = NSMakeRange(NSNotFound, 0);
79 return [NSDictionary dictionary];
82 return [attribString attributesAtIndex:index effectiveRange:range];
85 - (void)replaceCharactersInRange:(NSRange)range
86 withString:(NSString *)string
88 //NSLog(@"replaceCharactersInRange:(%d,%d) withString:%@", range.location,
89 // range.length, string);
90 NSLog(@"WARNING: calling %s on MMTextStorage is unsupported", _cmd);
91 //[attribString replaceCharactersInRange:range withString:string];
94 - (void)setAttributes:(NSDictionary *)attributes range:(NSRange)range
96 // NOTE! This method must be implemented since the text system calls it
97 // constantly to 'fix attributes', apply font substitution, etc.
99 [attribString setAttributes:attributes range:range];
101 // HACK! If the font attribute is being modified, then ensure that the new
102 // font has a fixed advancement which is either the same as the current
103 // font or twice that, depending on whether it is a 'wide' character that
104 // is being fixed or not. This code really only works if 'range' has
106 NSFont *newFont = [attributes objectForKey:NSFontAttributeName];
108 float adv = cellSize.width;
109 if ([attribString length] > range.location+1) {
110 // If the first char is followed by zero-width space, then it is a
111 // 'wide' character, so double the advancement.
112 NSString *string = [attribString string];
113 if ([string characterAtIndex:range.location+1] == 0x200b)
117 // Create a new font which has the 'fixed advance attribute' set.
118 NSDictionary *dict = [NSDictionary dictionaryWithObjectsAndKeys:
119 [NSNumber numberWithFloat:adv], NSFontFixedAdvanceAttribute, nil];
120 NSFontDescriptor *desc = [newFont fontDescriptor];
121 desc = [desc fontDescriptorByAddingAttributes:dict];
122 newFont = [NSFont fontWithDescriptor:desc size:[newFont pointSize]];
124 // Now modify the 'attributes' dictionary to hold the new font.
125 NSMutableDictionary *newAttr = [NSMutableDictionary
126 dictionaryWithDictionary:attributes];
127 [newAttr setObject:newFont forKey:NSFontAttributeName];
129 [attribString setAttributes:newAttr range:range];
131 [attribString setAttributes:attributes range:range];
146 - (void)getMaxRows:(int*)rows columns:(int*)cols
148 if (rows) *rows = maxRows;
149 if (cols) *cols = maxColumns;
152 - (void)setMaxRows:(int)rows columns:(int)cols
154 // NOTE: Just remember the new values, the actual resizing is done lazily.
159 - (void)replaceString:(NSString *)string atRow:(int)row column:(int)col
160 withFlags:(int)flags foregroundColor:(NSColor *)fg
161 backgroundColor:(NSColor *)bg specialColor:(NSColor *)sp
163 //NSLog(@"replaceString:atRow:%d column:%d withFlags:%d", row, col, flags);
166 // TODO: support DRAW_TRANSP
167 if (flags & DRAW_TRANSP)
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]);
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.
183 if (!(fg && bg && sp)) {
184 NSLog(@"[%s] WARNING: background, foreground or special color not "
189 NSRange range = NSMakeRange(col+row*(maxColumns+1), [string length]);
190 [attribString replaceCharactersInRange:range withString:string];
192 NSFont *theFont = font;
193 if (flags & DRAW_BOLD)
194 theFont = flags & DRAW_ITALIC ? boldItalicFont : boldFont;
195 else if (flags & DRAW_ITALIC)
196 theFont = italicFont;
198 NSDictionary *attributes = [NSDictionary dictionaryWithObjectsAndKeys:
199 theFont, NSFontAttributeName,
200 bg, NSBackgroundColorAttributeName,
201 fg, NSForegroundColorAttributeName,
202 sp, NSUnderlineColorAttributeName,
204 [attribString setAttributes:attributes range:range];
206 if (flags & DRAW_UNDERL) {
207 NSNumber *value = [NSNumber numberWithInt:(NSUnderlineStyleSingle
208 | NSUnderlinePatternSolid)]; // | NSUnderlineByWordMask
209 [attribString addAttribute:NSUnderlineStyleAttributeName
210 value:value range:range];
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];
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.
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);
236 if (row < 0 || row+count > maxRows) {
237 //NSLog(@"[%s] WARNING : out of range, row=%d (%d) count=%d", _cmd, row,
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 };
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;
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
269 destRange.location += maxColumns+1;
274 * Insert 'count' empty lines at 'row' and delete 'count' lines from the bottom
275 * of the scroll region.
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);
284 if (row < 0 || row+count > maxRows) {
285 //NSLog(@"[%s] WARNING : out of range, row=%d (%d) count=%d", _cmd, row,
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 };
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;
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
317 destRange.location -= maxColumns+1;
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,
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,
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 };
343 for (r=row1; r<=row2; ++r) {
344 [attribString setAttributes:attribs range:range];
345 [self edited:NSTextStorageEditedAttributes range:range
347 range.location += maxColumns+1;
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 if (defaultBackgroundColor != bgColor) {
368 [defaultBackgroundColor release];
369 defaultBackgroundColor = bgColor ? [bgColor retain] : nil;
372 // NOTE: The default foreground color isn't actually used for anything, but
373 // other class instances might want to be able to access it so it is stored
375 if (defaultForegroundColor != fgColor) {
376 [defaultForegroundColor release];
377 defaultForegroundColor = fgColor ? [fgColor retain] : nil;
381 - (void)setFont:(NSFont*)newFont
383 if (newFont && font != newFont) {
386 // NOTE! When setting a new font we make sure that the advancement of
387 // each glyph is fixed.
389 float em = [newFont widthOfString:@"m"];
390 float cellWidthMultiplier = [[NSUserDefaults standardUserDefaults]
391 floatForKey:MMCellWidthMultiplierKey];
393 // NOTE! Even though NSFontFixedAdvanceAttribute is a float, it will
394 // only render at integer sizes. Hence, we restrict the cell width to
395 // an integer here, otherwise the window width and the actual text
396 // width will not match.
397 cellSize.width = ceilf(em * cellWidthMultiplier);
399 float pointSize = [newFont pointSize];
400 NSDictionary *dict = [NSDictionary
401 dictionaryWithObject:[NSNumber numberWithFloat:cellSize.width]
402 forKey:NSFontFixedAdvanceAttribute];
404 NSFontDescriptor *desc = [newFont fontDescriptor];
405 desc = [desc fontDescriptorByAddingAttributes:dict];
406 font = [NSFont fontWithDescriptor:desc size:pointSize];
409 NSLayoutManager *lm = [[self layoutManagers] objectAtIndex:0];
410 cellSize.height = lm ? [lm defaultLineHeightForFont:font]
411 : [font defaultLineHeightForFont];
413 // NOTE: The font manager does not care about the 'font fixed advance'
414 // attribute, so after converting the font we have to add this
416 boldFont = [[NSFontManager sharedFontManager]
417 convertFont:font toHaveTrait:NSBoldFontMask];
418 desc = [boldFont fontDescriptor];
419 desc = [desc fontDescriptorByAddingAttributes:dict];
420 boldFont = [NSFont fontWithDescriptor:desc size:pointSize];
423 italicFont = [[NSFontManager sharedFontManager]
424 convertFont:font toHaveTrait:NSItalicFontMask];
425 desc = [italicFont fontDescriptor];
426 desc = [desc fontDescriptorByAddingAttributes:dict];
427 italicFont = [NSFont fontWithDescriptor:desc size:pointSize];
430 boldItalicFont = [[NSFontManager sharedFontManager]
431 convertFont:italicFont toHaveTrait:NSBoldFontMask];
432 desc = [boldItalicFont fontDescriptor];
433 desc = [desc fontDescriptorByAddingAttributes:dict];
434 boldItalicFont = [NSFont fontWithDescriptor:desc size:pointSize];
435 [boldItalicFont retain];
444 - (NSColor *)defaultBackgroundColor
446 return defaultBackgroundColor;
449 - (NSColor *)defaultForegroundColor
451 return defaultForegroundColor;
456 return NSMakeSize(maxColumns*cellSize.width, maxRows*cellSize.height);
464 - (NSRect)rectForRowsInRange:(NSRange)range
466 NSRect rect = { 0, 0, 0, 0 };
467 unsigned start = range.location > maxRows ? maxRows : range.location;
468 unsigned length = range.length;
470 if (start+length > maxRows)
471 length = maxRows - start;
473 rect.origin.y = cellSize.height * start;
474 rect.size.height = cellSize.height * length;
479 - (NSRect)rectForColumnsInRange:(NSRange)range
481 NSRect rect = { 0, 0, 0, 0 };
482 unsigned start = range.location > maxColumns ? maxColumns : range.location;
483 unsigned length = range.length;
485 if (start+length > maxColumns)
486 length = maxColumns - start;
488 rect.origin.x = cellSize.width * start;
489 rect.size.width = cellSize.width * length;
494 - (unsigned)characterIndexForRow:(int)row column:(int)col
496 // Ensure the offset returned is valid.
497 // This code also works if maxRows and/or maxColumns is 0.
498 if (row >= maxRows) row = maxRows-1;
499 if (row < 0) row = 0;
500 if (col >= maxColumns) col = maxColumns-1;
501 if (col < 0) col = 0;
503 return (unsigned)(col + row*(maxColumns+1));
506 - (BOOL)resizeToFitSize:(NSSize)size
508 int rows = maxRows, cols = maxColumns;
510 [self fitToSize:size rows:&rows columns:&cols];
511 if (rows != maxRows || cols != maxColumns) {
512 [self setMaxRows:rows columns:cols];
516 // Return NO only if dimensions did not change.
520 - (NSSize)fitToSize:(NSSize)size
522 return [self fitToSize:size rows:NULL columns:NULL];
525 - (NSSize)fitToSize:(NSSize)size rows:(int *)rows columns:(int *)columns
527 NSSize curSize = [self size];
528 NSSize fitSize = curSize;
529 int fitRows = maxRows;
530 int fitCols = maxColumns;
532 if (size.height < curSize.height) {
533 // Remove lines until the height of the text storage fits inside
534 // 'size'. However, always make sure there are at least 3 lines in the
535 // text storage. (Why 3? It seem Vim never allows less than 3 lines.)
537 // TODO: No need to search since line height is fixed, just calculate
539 int rowCount = maxRows;
541 for (rowsToRemove = 0; rowsToRemove < maxRows-3; ++rowsToRemove) {
542 float height = cellSize.height*rowCount;
544 if (height <= size.height) {
545 fitSize.height = height;
552 fitRows -= rowsToRemove;
553 } else if (size.height > curSize.height) {
554 float fh = cellSize.height;
555 if (fh < 1.0f) fh = 1.0f;
557 fitRows = floor(size.height/fh);
558 fitSize.height = fh*fitRows;
561 if (size.width != curSize.width) {
562 float fw = cellSize.width;
563 if (fw < 1.0f) fw = 1.0f;
565 fitCols = floor(size.width/fw);
566 fitSize.width = fw*fitCols;
569 if (rows) *rows = fitRows;
570 if (columns) *columns = fitCols;
575 @end // MMTextStorage
580 @implementation MMTextStorage (Private)
585 // Do nothing if the dimensions are already right.
586 if (actualRows == maxRows && actualColumns == maxColumns)
589 NSRange oldRange = NSMakeRange(0, actualRows*(actualColumns+1));
591 actualRows = maxRows;
592 actualColumns = maxColumns;
595 if (defaultBackgroundColor) {
596 dict = [NSDictionary dictionaryWithObjectsAndKeys:
597 font, NSFontAttributeName,
598 defaultBackgroundColor, NSBackgroundColorAttributeName, nil];
600 dict = [NSDictionary dictionaryWithObjectsAndKeys:
601 font, NSFontAttributeName, nil];
604 NSMutableString *rowString = [NSMutableString string];
605 for (i = 0; i < maxColumns; ++i) {
606 [rowString appendString:@" "];
608 [rowString appendString:@"\n"];
610 [emptyRowString release];
611 emptyRowString = [[NSAttributedString alloc] initWithString:rowString
614 [attribString release];
615 attribString = [[NSMutableAttributedString alloc] init];
616 for (i=0; i<maxRows; ++i) {
617 [attribString appendAttributedString:emptyRowString];
620 NSRange fullRange = NSMakeRange(0, [attribString length]);
621 [self edited:(NSTextStorageEditedCharacters|NSTextStorageEditedAttributes)
622 range:oldRange changeInLength:fullRange.length-oldRange.length];
625 @end // MMTextStorage (Private)