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 [defaultForegroundColor release];
59 [attribString release];
65 //NSLog(@"%s : attribString=%@", _cmd, attribString);
66 return [attribString string];
69 - (NSDictionary *)attributesAtIndex:(unsigned)index
70 effectiveRange:(NSRangePointer)range
73 if (index>=[attribString length]) {
74 //NSLog(@"%sWARNING: index (%d) out of bounds", _cmd, index);
76 *range = NSMakeRange(NSNotFound, 0);
78 return [NSDictionary dictionary];
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.
98 [attribString setAttributes:attributes range:range];
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
105 NSFont *newFont = [attributes objectForKey:NSFontAttributeName];
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)
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];
130 [attribString setAttributes:attributes range:range];
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.
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);
165 #if !HEED_DRAW_TRANSP
166 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 NSDictionary *attributes = [NSDictionary dictionaryWithObjectsAndKeys:
193 font, NSFontAttributeName,
194 bg, NSBackgroundColorAttributeName,
195 fg, NSForegroundColorAttributeName,
196 sp, NSUnderlineColorAttributeName,
198 [attribString setAttributes:attributes range:range];
201 if ( !(flags & DRAW_TRANSP) ) {
202 [attribString addAttribute:NSBackgroundColorAttributeName value:bg
207 // TODO: cache bold font and apply in setAttributes:range:
208 if (flags & DRAW_BOLD) {
209 [attribString applyFontTraits:NSBoldFontMask range:range];
212 // TODO: cache italic font and apply in setAttributes:range:
213 if (flags & DRAW_ITALIC) {
214 [attribString applyFontTraits:NSItalicFontMask range:range];
217 if (flags & DRAW_UNDERL) {
218 NSNumber *value = [NSNumber numberWithInt:(NSUnderlineStyleSingle
219 | NSUnderlinePatternSolid)]; // | NSUnderlineByWordMask
220 [attribString addAttribute:NSUnderlineStyleAttributeName
221 value:value range:range];
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];
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.
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);
247 if (row < 0 || row+count > maxRows) {
248 //NSLog(@"[%s] WARNING : out of range, row=%d (%d) count=%d", _cmd, row,
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 };
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;
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
280 destRange.location += maxColumns+1;
285 * Insert 'count' empty lines at 'row' and delete 'count' lines from the bottom
286 * of the scroll region.
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);
295 if (row < 0 || row+count > maxRows) {
296 //NSLog(@"[%s] WARNING : out of range, row=%d (%d) count=%d", _cmd, row,
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 };
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;
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
328 destRange.location -= maxColumns+1;
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,
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,
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 };
354 for (r=row1; r<=row2; ++r) {
355 [attribString setAttributes:attribs range:range];
356 [self edited:NSTextStorageEditedAttributes range:range
358 range.location += maxColumns+1;
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;
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
386 if (defaultForegroundColor != fgColor) {
387 [defaultForegroundColor release];
388 defaultForegroundColor = fgColor ? [fgColor retain] : nil;
392 - (void)setFont:(NSFont*)newFont
394 if (newFont && font != newFont) {
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]];
419 NSLayoutManager *lm = [[self layoutManagers] objectAtIndex:0];
420 cellSize.height = lm ? [lm defaultLineHeightForFont:font]
421 : [font defaultLineHeightForFont];
430 - (NSColor *)defaultBackgroundColor
432 return defaultBackgroundColor;
435 - (NSColor *)defaultForegroundColor
437 return defaultForegroundColor;
442 return NSMakeSize(maxColumns*cellSize.width, maxRows*cellSize.height);
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;
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;
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];
502 // Return NO only if dimensions did not change.
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.)
523 // TODO: No need to search since line height is fixed, just calculate
525 int rowCount = maxRows;
527 for (rowsToRemove = 0; rowsToRemove < maxRows-3; ++rowsToRemove) {
528 float height = cellSize.height*rowCount;
530 if (height <= size.height) {
531 fitSize.height = height;
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;
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;
555 if (rows) *rows = fitRows;
556 if (columns) *columns = fitCols;
561 @end // MMTextStorage
566 @implementation MMTextStorage (Private)
571 // Do nothing if the dimensions are already right.
572 if (actualRows == maxRows && actualColumns == maxColumns)
575 NSRange oldRange = NSMakeRange(0, actualRows*(actualColumns+1));
577 actualRows = maxRows;
578 actualColumns = maxColumns;
581 if (defaultBackgroundColor) {
582 dict = [NSDictionary dictionaryWithObjectsAndKeys:
583 font, NSFontAttributeName,
584 defaultBackgroundColor, NSBackgroundColorAttributeName, nil];
586 dict = [NSDictionary dictionaryWithObjectsAndKeys:
587 font, NSFontAttributeName, nil];
590 NSMutableString *rowString = [NSMutableString string];
591 for (i = 0; i < maxColumns; ++i) {
592 [rowString appendString:@" "];
594 [rowString appendString:@"\n"];
596 [emptyRowString release];
597 emptyRowString = [[NSAttributedString alloc] initWithString:rowString
600 [attribString release];
601 attribString = [[NSMutableAttributedString alloc] init];
602 for (i=0; i<maxRows; ++i) {
603 [attribString appendAttributedString:emptyRowString];
606 NSRange fullRange = NSMakeRange(0, [attribString length]);
607 [self edited:(NSTextStorageEditedCharacters|NSTextStorageEditedAttributes)
608 range:oldRange changeInLength:fullRange.length-oldRange.length];
611 @end // MMTextStorage (Private)