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"
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)
35 @implementation MMTextStorage
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];
53 //NSLog(@"%@ %s", [self className], _cmd);
55 [emptyRowString release];
57 [defaultBackgroundColor release];
58 [attribString release];
64 //NSLog(@"%s : attribString=%@", _cmd, attribString);
65 return [attribString string];
68 - (NSDictionary *)attributesAtIndex:(unsigned)index
69 effectiveRange:(NSRangePointer)range
72 if (index>=[attribString length]) {
73 //NSLog(@"%sWARNING: index (%d) out of bounds", _cmd, index);
75 *range = NSMakeRange(NSNotFound, 0);
77 return [NSDictionary dictionary];
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.
97 [attribString setAttributes:attributes range:range];
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
104 NSFont *newFont = [attributes objectForKey:NSFontAttributeName];
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)
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];
129 [attribString setAttributes:attributes range:range];
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.
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);
164 #if !HEED_DRAW_TRANSP
165 if (flags & DRAW_TRANSP)
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]);
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.
182 if (!(fg && bg && sp)) {
183 NSLog(@"[%s] WARNING: background, foreground or special color not "
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,
197 [attribString setAttributes:attributes range:range];
200 if ( !(flags & DRAW_TRANSP) ) {
201 [attribString addAttribute:NSBackgroundColorAttributeName value:bg
206 // TODO: cache bold font and apply in setAttributes:range:
207 if (flags & DRAW_BOLD) {
208 [attribString applyFontTraits:NSBoldFontMask range:range];
211 // TODO: cache italic font and apply in setAttributes:range:
212 if (flags & DRAW_ITALIC) {
213 [attribString applyFontTraits:NSItalicFontMask range:range];
216 if (flags & DRAW_UNDERL) {
217 NSNumber *value = [NSNumber numberWithInt:(NSUnderlineStyleSingle
218 | NSUnderlinePatternSolid)]; // | NSUnderlineByWordMask
219 [attribString addAttribute:NSUnderlineStyleAttributeName
220 value:value range:range];
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];
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.
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);
246 if (row < 0 || row+count > maxRows) {
247 //NSLog(@"[%s] WARNING : out of range, row=%d (%d) count=%d", _cmd, row,
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 };
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;
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
279 destRange.location += maxColumns+1;
284 * Insert 'count' empty lines at 'row' and delete 'count' lines from the bottom
285 * of the scroll region.
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);
294 if (row < 0 || row+count > maxRows) {
295 //NSLog(@"[%s] WARNING : out of range, row=%d (%d) count=%d", _cmd, row,
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 };
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;
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
327 destRange.location -= maxColumns+1;
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,
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,
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 };
353 for (r=row1; r<=row2; ++r) {
354 [attribString setAttributes:attribs range:range];
355 [self edited:NSTextStorageEditedAttributes range:range
357 range.location += maxColumns+1;
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) {
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]];
409 NSLayoutManager *lm = [[self layoutManagers] objectAtIndex:0];
410 cellSize.height = lm ? [lm defaultLineHeightForFont:font]
411 : [font defaultLineHeightForFont];
422 return NSMakeSize(maxColumns*cellSize.width, maxRows*cellSize.height);
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;
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;
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];
482 // Return NO only if dimensions did not change.
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.)
503 // TODO: No need to search since line height is fixed, just calculate
505 int rowCount = maxRows;
507 for (rowsToRemove = 0; rowsToRemove < maxRows-3; ++rowsToRemove) {
508 float height = cellSize.height*rowCount;
510 if (height <= size.height) {
511 fitSize.height = height;
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;
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;
535 if (rows) *rows = fitRows;
536 if (columns) *columns = fitCols;
541 @end // MMTextStorage
546 @implementation MMTextStorage (Private)
551 // Do nothing if the dimensions are already right.
552 if (actualRows == maxRows && actualColumns == maxColumns)
555 NSRange oldRange = NSMakeRange(0, actualRows*(actualColumns+1));
557 actualRows = maxRows;
558 actualColumns = maxColumns;
561 if (defaultBackgroundColor) {
562 dict = [NSDictionary dictionaryWithObjectsAndKeys:
563 font, NSFontAttributeName,
564 defaultBackgroundColor, NSBackgroundColorAttributeName, nil];
566 dict = [NSDictionary dictionaryWithObjectsAndKeys:
567 font, NSFontAttributeName, nil];
570 NSMutableString *rowString = [NSMutableString string];
571 for (i = 0; i < maxColumns; ++i) {
572 [rowString appendString:@" "];
574 [rowString appendString:@"\n"];
576 [emptyRowString release];
577 emptyRowString = [[NSAttributedString alloc] initWithString:rowString
580 [attribString release];
581 attribString = [[NSMutableAttributedString alloc] init];
582 for (i=0; i<maxRows; ++i) {
583 [attribString appendAttributedString:emptyRowString];
586 NSRange fullRange = NSMakeRange(0, [attribString length]);
587 [self edited:(NSTextStorageEditedCharacters|NSTextStorageEditedAttributes)
588 range:oldRange changeInLength:fullRange.length-oldRange.length];
591 @end // MMTextStorage (Private)