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"
12 #import "MMAppController.h"
14 // If 0 DRAW_TRANSP flag will be ignored. Setting it to 1 causes the cursor
15 // background to be drawn in white.
16 #define HEED_DRAW_TRANSP 0
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];
55 [defaultBackgroundColor release];
56 [attribString release];
62 //NSLog(@"%s : attribString=%@", _cmd, attribString);
63 return [attribString string];
66 - (NSDictionary *)attributesAtIndex:(unsigned)index
67 effectiveRange:(NSRangePointer)range
70 if (index>=[attribString length]) {
71 //NSLog(@"%sWARNING: index (%d) out of bounds", _cmd, index);
73 *range = NSMakeRange(NSNotFound, 0);
75 return [NSDictionary dictionary];
78 return [attribString attributesAtIndex:index effectiveRange:range];
81 - (void)replaceCharactersInRange:(NSRange)range
82 withString:(NSString *)string
84 //NSLog(@"replaceCharactersInRange:(%d,%d) withString:%@", range.location,
85 // range.length, string);
86 NSLog(@"WARNING: calling %s on MMTextStorage is unsupported", _cmd);
87 //[attribString replaceCharactersInRange:range withString:string];
90 - (void)setAttributes:(NSDictionary *)attributes range:(NSRange)range
92 // NOTE! This method must be implemented since the text system calls it
93 // constantly to 'fix attributes', apply font substitution, etc.
95 [attribString setAttributes:attributes range:range];
97 // HACK! If the font attribute is being modified, then ensure that the new
98 // font has a fixed advancement which is either the same as the current
99 // font or twice that, depending on whether it is a 'wide' character that
100 // is being fixed or not. This code really only works if 'range' has
102 NSFont *newFont = [attributes objectForKey:NSFontAttributeName];
104 float adv = cellSize.width;
105 if ([attribString length] > range.location+1) {
106 // If the first char is followed by zero-width space, then it is a
107 // 'wide' character, so double the advancement.
108 NSString *string = [attribString string];
109 if ([string characterAtIndex:range.location+1] == 0x200b)
113 // Create a new font which has the 'fixed advance attribute' set.
114 NSDictionary *dict = [NSDictionary dictionaryWithObjectsAndKeys:
115 [NSNumber numberWithFloat:adv], NSFontFixedAdvanceAttribute, nil];
116 NSFontDescriptor *desc = [newFont fontDescriptor];
117 desc = [desc fontDescriptorByAddingAttributes:dict];
118 newFont = [NSFont fontWithDescriptor:desc size:[newFont pointSize]];
120 // Now modify the 'attributes' dictionary to hold the new font.
121 NSMutableDictionary *newAttr = [NSMutableDictionary
122 dictionaryWithDictionary:attributes];
123 [newAttr setObject:newFont forKey:NSFontAttributeName];
125 [attribString setAttributes:newAttr range:range];
127 [attribString setAttributes:attributes range:range];
142 - (void)getMaxRows:(int*)rows columns:(int*)cols
144 if (rows) *rows = maxRows;
145 if (cols) *cols = maxColumns;
148 - (void)setMaxRows:(int)rows columns:(int)cols
150 // NOTE: Just remember the new values, the actual resizing is done lazily.
155 - (void)replaceString:(NSString*)string atRow:(int)row column:(int)col
156 withFlags:(int)flags foregroundColor:(NSColor*)fg
157 backgroundColor:(NSColor*)bg
159 //NSLog(@"replaceString:atRow:%d column:%d withFlags:%d", row, col, flags);
162 if (row < 0 || row >= maxRows || col < 0 || col >= maxColumns
163 || col+[string length] > maxColumns) {
164 //NSLog(@"[%s] WARNING : out of range, row=%d (%d) col=%d (%d) "
165 // "length=%d (%d)", _cmd, row, maxRows, col, maxColumns,
166 // [string length], [attribString length]);
171 NSLog(@"[%s] WARNING: background or foreground color not specified",
176 NSRange range = NSMakeRange(col+row*(maxColumns+1), [string length]);
177 [attribString replaceCharactersInRange:range withString:string];
179 NSDictionary *attributes = [NSDictionary dictionaryWithObjectsAndKeys:
180 font, NSFontAttributeName,
181 #if !HEED_DRAW_TRANSP
182 bg, NSBackgroundColorAttributeName,
184 fg, NSForegroundColorAttributeName, nil];
185 [attribString setAttributes:attributes range:range];
188 if ( !(flags & DRAW_TRANSP) ) {
189 [attribString addAttribute:NSBackgroundColorAttributeName value:bg
194 // TODO: cache bold font and apply in setAttributes:range:
195 if (flags & DRAW_BOLD) {
196 [attribString applyFontTraits:NSBoldFontMask range:range];
199 // TODO: cache italic font and apply in setAttributes:range:
200 if (flags & DRAW_ITALIC) {
201 [attribString applyFontTraits:NSItalicFontMask range:range];
204 if (flags & DRAW_UNDERL) {
205 NSNumber *value = [NSNumber numberWithInt:(NSUnderlineStyleSingle
206 | NSUnderlinePatternSolid)]; // | NSUnderlineByWordMask
207 [attribString addAttribute:NSUnderlineStyleAttributeName
208 value:value range:range];
211 // TODO: figure out how do draw proper undercurls
212 if (flags & DRAW_UNDERC) {
213 NSNumber *value = [NSNumber numberWithInt:(NSUnderlineStyleThick
214 | NSUnderlinePatternDot)]; // | NSUnderlineByWordMask
215 [attribString addAttribute:NSUnderlineStyleAttributeName
216 value:value range:range];
219 [self edited:(NSTextStorageEditedCharacters|NSTextStorageEditedAttributes)
220 range:range changeInLength:0];
224 * Delete 'count' lines from 'row' and insert 'count' empty lines at the bottom
225 * of the scroll region.
227 - (void)deleteLinesFromRow:(int)row lineCount:(int)count
228 scrollBottom:(int)bottom left:(int)left right:(int)right
229 color:(NSColor *)color
231 //NSLog(@"deleteLinesFromRow:%d lineCount:%d", row, count);
234 if (row < 0 || row+count > maxRows) {
235 //NSLog(@"[%s] WARNING : out of range, row=%d (%d) count=%d", _cmd, row,
240 int total = 1 + bottom - row;
241 int move = total - count;
242 int width = right - left + 1;
243 NSRange destRange = { row*(maxColumns+1) + left, width };
244 NSRange srcRange = { (row+count)*(maxColumns+1) + left, width };
247 for (i = 0; i < move; ++i) {
248 NSAttributedString *srcString = [attribString
249 attributedSubstringFromRange:srcRange];
250 [attribString replaceCharactersInRange:destRange
251 withAttributedString:srcString];
252 [self edited:(NSTextStorageEditedCharacters
253 | NSTextStorageEditedAttributes)
254 range:destRange changeInLength:0];
255 destRange.location += maxColumns+1;
256 srcRange.location += maxColumns+1;
259 for (i = 0; i < count; ++i) {
260 NSDictionary *attribs = [NSDictionary dictionaryWithObjectsAndKeys:
261 font, NSFontAttributeName,
262 color, NSForegroundColorAttributeName,
263 color, NSBackgroundColorAttributeName, nil];
264 [attribString setAttributes:attribs range:destRange];
265 [self edited:NSTextStorageEditedAttributes range:destRange
267 destRange.location += maxColumns+1;
272 * Insert 'count' empty lines at 'row' and delete 'count' lines from the bottom
273 * of the scroll region.
275 - (void)insertLinesAtRow:(int)row lineCount:(int)count
276 scrollBottom:(int)bottom left:(int)left right:(int)right
277 color:(NSColor *)color
279 //NSLog(@"insertLinesAtRow:%d lineCount:%d", row, count);
282 if (row < 0 || row+count > maxRows) {
283 //NSLog(@"[%s] WARNING : out of range, row=%d (%d) count=%d", _cmd, row,
288 int total = 1 + bottom - row;
289 int move = total - count;
290 int width = right - left + 1;
291 NSRange destRange = { bottom*(maxColumns+1) + left, width };
292 NSRange srcRange = { (row+move-1)*(maxColumns+1) + left, width };
295 for (i = 0; i < move; ++i) {
296 NSAttributedString *srcString = [attribString
297 attributedSubstringFromRange:srcRange];
298 [attribString replaceCharactersInRange:destRange
299 withAttributedString:srcString];
300 [self edited:(NSTextStorageEditedCharacters
301 | NSTextStorageEditedAttributes)
302 range:destRange changeInLength:0];
303 destRange.location -= maxColumns+1;
304 srcRange.location -= maxColumns+1;
307 for (i = 0; i < count; ++i) {
308 NSDictionary *attribs = [NSDictionary dictionaryWithObjectsAndKeys:
309 font, NSFontAttributeName,
310 color, NSForegroundColorAttributeName,
311 color, NSBackgroundColorAttributeName, nil];
312 [attribString setAttributes:attribs range:destRange];
313 [self edited:NSTextStorageEditedAttributes range:destRange
315 destRange.location -= maxColumns+1;
319 - (void)clearBlockFromRow:(int)row1 column:(int)col1 toRow:(int)row2
320 column:(int)col2 color:(NSColor *)color
322 //NSLog(@"clearBlockFromRow:%d column:%d toRow:%d column:%d", row1, col1,
326 if (row1 < 0 || row2 >= maxRows || col1 < 0 || col2 > maxColumns) {
327 //NSLog(@"[%s] WARNING : out of range, row1=%d row2=%d (%d) col1=%d "
328 // "col2=%d (%d)", _cmd, row1, row2, maxRows, col1, col2,
333 NSDictionary *attribs = [NSDictionary dictionaryWithObjectsAndKeys:
334 font, NSFontAttributeName,
335 color, NSForegroundColorAttributeName,
336 color, NSBackgroundColorAttributeName, nil];
338 NSRange range = { row1*(maxColumns+1) + col1, col2-col1+1 };
341 for (r=row1; r<=row2; ++r) {
342 [attribString setAttributes:attribs range:range];
343 [self edited:NSTextStorageEditedAttributes range:range
345 range.location += maxColumns+1;
349 - (void)clearAllWithColor:(NSColor *)color
351 //NSLog(@"%s%@", _cmd, color);
353 NSRange range = { 0, [attribString length] };
354 NSDictionary *attribs = [NSDictionary dictionaryWithObjectsAndKeys:
355 font, NSFontAttributeName,
356 color, NSForegroundColorAttributeName,
357 color, NSBackgroundColorAttributeName, nil];
358 [attribString setAttributes:attribs range:range];
359 [self edited:NSTextStorageEditedAttributes range:range changeInLength:0];
362 - (void)setDefaultColorsBackground:(NSColor *)bgColor
363 foreground:(NSColor *)fgColor
365 // NOTE: Foreground color is ignored.
366 [defaultBackgroundColor release];
367 defaultBackgroundColor = bgColor ? [bgColor retain] : nil;
370 - (void)setFont:(NSFont*)newFont
372 if (newFont && font != newFont) {
375 // NOTE! When setting a new font we make sure that the advancement of
376 // each glyph is fixed.
378 float em = [newFont widthOfString:@"m"];
379 float cellWidthMultiplier = [[NSUserDefaults standardUserDefaults]
380 floatForKey:MMCellWidthMultiplierKey];
382 cellSize.width = em * cellWidthMultiplier;
384 NSDictionary *dict = [NSDictionary
385 dictionaryWithObject:[NSNumber numberWithFloat:cellSize.width]
386 forKey:NSFontFixedAdvanceAttribute];
387 NSFontDescriptor *desc = [newFont fontDescriptor];
388 desc = [desc fontDescriptorByAddingAttributes:dict];
390 font = [NSFont fontWithDescriptor:desc size:[newFont pointSize]];
393 NSLayoutManager *lm = [[self layoutManagers] objectAtIndex:0];
394 cellSize.height = lm ? [lm defaultLineHeightForFont:font]
395 : [font defaultLineHeightForFont];
406 return NSMakeSize(maxColumns*cellSize.width, maxRows*cellSize.height);
414 - (NSRect)rectForRowsInRange:(NSRange)range
416 NSRect rect = { 0, 0, 0, 0 };
417 unsigned start = range.location > maxRows ? maxRows : range.location;
418 unsigned length = range.length;
420 if (start+length > maxRows)
421 length = maxRows - start;
423 rect.origin.y = cellSize.height * start;
424 rect.size.height = cellSize.height * length;
429 - (NSRect)rectForColumnsInRange:(NSRange)range
431 NSRect rect = { 0, 0, 0, 0 };
432 unsigned start = range.location > maxColumns ? maxColumns : range.location;
433 unsigned length = range.length;
435 if (start+length > maxColumns)
436 length = maxColumns - start;
438 rect.origin.x = cellSize.width * start;
439 rect.size.width = cellSize.width * length;
444 - (unsigned)offsetFromRow:(int)row column:(int)col
446 // Ensure the offset returned is valid.
447 // This code also works if maxRows and/or maxColumns is 0.
448 if (row >= maxRows) row = maxRows-1;
449 if (row < 0) row = 0;
450 if (col >= maxColumns) col = maxColumns-1;
451 if (col < 0) col = 0;
453 return (unsigned)(col + row*(maxColumns+1));
456 - (BOOL)resizeToFitSize:(NSSize)size
458 int rows = maxRows, cols = maxColumns;
460 [self fitToSize:size rows:&rows columns:&cols];
461 if (rows != maxRows || cols != maxColumns) {
462 [self setMaxRows:rows columns:cols];
466 // Return NO only if dimensions did not change.
470 - (NSSize)fitToSize:(NSSize)size
472 return [self fitToSize:size rows:NULL columns:NULL];
475 - (NSSize)fitToSize:(NSSize)size rows:(int *)rows columns:(int *)columns
477 NSSize curSize = [self size];
478 NSSize fitSize = curSize;
479 int fitRows = maxRows;
480 int fitCols = maxColumns;
482 if (size.height < curSize.height) {
483 // Remove lines until the height of the text storage fits inside
484 // 'size'. However, always make sure there are at least 3 lines in the
485 // text storage. (Why 3? It seem Vim never allows less than 3 lines.)
487 // TODO: No need to search since line height is fixed, just calculate
489 int rowCount = maxRows;
491 for (rowsToRemove = 0; rowsToRemove < maxRows-3; ++rowsToRemove) {
492 float height = cellSize.height*rowCount;
494 if (height <= size.height) {
495 fitSize.height = height;
502 fitRows -= rowsToRemove;
503 } else if (size.height > curSize.height) {
504 float fh = cellSize.height;
505 if (fh < 1.0f) fh = 1.0f;
507 fitRows = floor(size.height/fh);
508 fitSize.height = fh*fitRows;
511 if (size.width != curSize.width) {
512 float fw = cellSize.width;
513 if (fw < 1.0f) fw = 1.0f;
515 fitCols = floor(size.width/fw);
516 fitSize.width = fw*fitCols;
519 if (rows) *rows = fitRows;
520 if (columns) *columns = fitCols;
525 @end // MMTextStorage
530 @implementation MMTextStorage (Private)
535 // Do nothing if the dimensions are already right.
536 if (actualRows == maxRows && actualColumns == maxColumns)
539 NSRange oldRange = NSMakeRange(0, actualRows*(actualColumns+1));
541 actualRows = maxRows;
542 actualColumns = maxColumns;
545 if (defaultBackgroundColor) {
546 dict = [NSDictionary dictionaryWithObjectsAndKeys:
547 font, NSFontAttributeName,
548 defaultBackgroundColor, NSBackgroundColorAttributeName, nil];
550 dict = [NSDictionary dictionaryWithObjectsAndKeys:
551 font, NSFontAttributeName, nil];
554 NSMutableString *rowString = [NSMutableString string];
555 for (i = 0; i < maxColumns; ++i) {
556 [rowString appendString:@" "];
558 [rowString appendString:@"\n"];
560 [emptyRowString release];
561 emptyRowString = [[NSAttributedString alloc] initWithString:rowString
564 [attribString release];
565 attribString = [[NSMutableAttributedString alloc] init];
566 for (i=0; i<maxRows; ++i) {
567 [attribString appendAttributedString:emptyRowString];
570 NSRange fullRange = NSMakeRange(0, [attribString length]);
571 [self edited:(NSTextStorageEditedCharacters|NSTextStorageEditedAttributes)
572 range:oldRange changeInLength:fullRange.length-oldRange.length];
575 @end // MMTextStorage (Private)