Update 'gfw' on Cmd--/Cmd-+
[MacVim.git] / src / MacVim / MMTextView.m
bloba2aa406e0dd168a8b8089a1a5af690dba9954ba3
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  * MMTextView
12  *
13  * Dispatches keyboard and mouse input to the backend.  Handles drag-n-drop of
14  * files onto window.
15  *
16  * Support for input managers is somewhat hacked together.  Marked text is
17  * drawn "pseudo-inline"; it will simply draw on top of existing text and it
18  * does not respect Vim-window boundaries.
19  */
21 #import "MMAppController.h"
22 #import "MMTextStorage.h"
23 #import "MMTextView.h"
24 #import "MMTextViewHelper.h"
25 #import "MMTypesetter.h"
26 #import "MMVimController.h"
27 #import "MMWindowController.h"
28 #import "Miscellaneous.h"
32 // This is taken from gui.h
33 #define DRAW_CURSOR 0x20
37 @interface MMTextView (Private)
38 - (MMWindowController *)windowController;
39 - (MMVimController *)vimController;
40 - (void)setShouldDrawInsertionPoint:(BOOL)on;
41 - (void)drawInsertionPointAtRow:(int)row column:(int)col shape:(int)shape
42                        fraction:(int)percent;
43 - (void)drawInvertedRectAtRow:(int)row column:(int)col numRows:(int)nrows
44                    numColumns:(int)ncols invert:(int)invert;
45 @end
49 @implementation MMTextView
51 - (id)initWithFrame:(NSRect)frame
53     // Set up a Cocoa text system.  Note that the textStorage is released in
54     // -[MMVimView dealloc].
55     MMTextStorage *textStorage = [[MMTextStorage alloc] init];
56     NSLayoutManager *lm = [[NSLayoutManager alloc] init];
57     NSTextContainer *tc = [[NSTextContainer alloc] initWithContainerSize:
58                     NSMakeSize(1.0e7,1.0e7)];
60     NSString *typesetterString = [[NSUserDefaults standardUserDefaults]
61             stringForKey:MMTypesetterKey];
62     if ([typesetterString isEqual:@"MMTypesetter"]) {
63         NSTypesetter *typesetter = [[MMTypesetter alloc] init];
64         [lm setTypesetter:typesetter];
65         [typesetter release];
66 #if MM_USE_ROW_CACHE
67     } else if ([typesetterString isEqual:@"MMTypesetter2"]) {
68         NSTypesetter *typesetter = [[MMTypesetter2 alloc] init];
69         [lm setTypesetter:typesetter];
70         [typesetter release];
71 #endif // MM_USE_ROW_CACHE
72     } else {
73         // Only MMTypesetter supports different cell width multipliers.
74         [[NSUserDefaults standardUserDefaults]
75                 setFloat:1.0 forKey:MMCellWidthMultiplierKey];
76     }
78     // The characters in the text storage are in display order, so disable
79     // bidirectional text processing (this call is 10.4 only).
80     [[lm typesetter] setBidiProcessingEnabled:NO];
82     [tc setWidthTracksTextView:NO];
83     [tc setHeightTracksTextView:NO];
84     [tc setLineFragmentPadding:0];
86     [textStorage addLayoutManager:lm];
87     [lm addTextContainer:tc];
89     // The text storage retains the layout manager which in turn retains
90     // the text container.
91     [tc autorelease];
92     [lm autorelease];
94     // NOTE: This will make the text storage the principal owner of the text
95     // system.  Releasing the text storage will in turn release the layout
96     // manager, the text container, and finally the text view (self).  This
97     // complicates deallocation somewhat, see -[MMVimView dealloc].
98     if (![super initWithFrame:frame textContainer:tc]) {
99         [textStorage release];
100         return nil;
101     }
103     helper = [[MMTextViewHelper alloc] init];
104     [helper setTextView:self];
106     // NOTE: If the default changes to 'NO' then the intialization of
107     // p_antialias in option.c must change as well.
108     antialias = YES;
110     return self;
113 - (void)dealloc
115     LOG_DEALLOC
117     if (invertRects) {
118         free(invertRects);
119         invertRects = NULL;
120         numInvertRects = 0;
121     }
123     [helper setTextView:nil];
124     [helper dealloc];  helper = nil;
126     [super dealloc];
129 - (BOOL)shouldDrawInsertionPoint
131     // NOTE: The insertion point is drawn manually in drawRect:.  It would be
132     // nice to be able to use the insertion point related methods of
133     // NSTextView, but it seems impossible to get them to work properly (search
134     // the cocoabuilder archives).
135     return NO;
138 - (void)setPreEditRow:(int)row column:(int)col
140     [helper setPreEditRow:row column:col];
143 #define MM_DEBUG_DRAWING 0
145 - (void)performBatchDrawWithData:(NSData *)data
147     MMTextStorage *textStorage = (MMTextStorage *)[self textStorage];
148     if (!textStorage)
149         return;
151     const void *bytes = [data bytes];
152     const void *end = bytes + [data length];
153     int cursorRow = -1, cursorCol = 0;
155 #if MM_DEBUG_DRAWING
156     NSLog(@"====> BEGIN %s", _cmd);
157 #endif
158     [textStorage beginEditing];
160     // TODO: Sanity check input
162     while (bytes < end) {
163         int type = *((int*)bytes);  bytes += sizeof(int);
165         if (ClearAllDrawType == type) {
166 #if MM_DEBUG_DRAWING
167             NSLog(@"   Clear all");
168 #endif
169             [textStorage clearAll];
170         } else if (ClearBlockDrawType == type) {
171             unsigned color = *((unsigned*)bytes);  bytes += sizeof(unsigned);
172             int row1 = *((int*)bytes);  bytes += sizeof(int);
173             int col1 = *((int*)bytes);  bytes += sizeof(int);
174             int row2 = *((int*)bytes);  bytes += sizeof(int);
175             int col2 = *((int*)bytes);  bytes += sizeof(int);
177 #if MM_DEBUG_DRAWING
178             NSLog(@"   Clear block (%d,%d) -> (%d,%d)", row1, col1,
179                     row2,col2);
180 #endif
181             [textStorage clearBlockFromRow:row1 column:col1
182                     toRow:row2 column:col2
183                     color:[NSColor colorWithArgbInt:color]];
184         } else if (DeleteLinesDrawType == type) {
185             unsigned color = *((unsigned*)bytes);  bytes += sizeof(unsigned);
186             int row = *((int*)bytes);  bytes += sizeof(int);
187             int count = *((int*)bytes);  bytes += sizeof(int);
188             int bot = *((int*)bytes);  bytes += sizeof(int);
189             int left = *((int*)bytes);  bytes += sizeof(int);
190             int right = *((int*)bytes);  bytes += sizeof(int);
192 #if MM_DEBUG_DRAWING
193             NSLog(@"   Delete %d line(s) from %d", count, row);
194 #endif
195             [textStorage deleteLinesFromRow:row lineCount:count
196                     scrollBottom:bot left:left right:right
197                            color:[NSColor colorWithArgbInt:color]];
198         } else if (DrawStringDrawType == type) {
199             int bg = *((int*)bytes);  bytes += sizeof(int);
200             int fg = *((int*)bytes);  bytes += sizeof(int);
201             int sp = *((int*)bytes);  bytes += sizeof(int);
202             int row = *((int*)bytes);  bytes += sizeof(int);
203             int col = *((int*)bytes);  bytes += sizeof(int);
204             int cells = *((int*)bytes);  bytes += sizeof(int);
205             int flags = *((int*)bytes);  bytes += sizeof(int);
206             int len = *((int*)bytes);  bytes += sizeof(int);
207             NSString *string = [[NSString alloc]
208                     initWithBytes:(void*)bytes length:len
209                          encoding:NSUTF8StringEncoding];
210             bytes += len;
212 #if MM_DEBUG_DRAWING
213             NSLog(@"   Draw string at (%d,%d) length=%d flags=%d fg=0x%x "
214                     "bg=0x%x sp=0x%x (%@)", row, col, len, flags, fg, bg, sp,
215                     len > 0 ? [string substringToIndex:1] : @"");
216 #endif
217             // NOTE: If this is a call to draw the (block) cursor, then cancel
218             // any previous request to draw the insertion point, or it might
219             // get drawn as well.
220             if (flags & DRAW_CURSOR)
221                 [self setShouldDrawInsertionPoint:NO];
223             [textStorage drawString:string
224                               atRow:row column:col cells:cells
225                           withFlags:flags
226                     foregroundColor:[NSColor colorWithRgbInt:fg]
227                     backgroundColor:[NSColor colorWithArgbInt:bg]
228                        specialColor:[NSColor colorWithRgbInt:sp]];
230             [string release];
231         } else if (InsertLinesDrawType == type) {
232             unsigned color = *((unsigned*)bytes);  bytes += sizeof(unsigned);
233             int row = *((int*)bytes);  bytes += sizeof(int);
234             int count = *((int*)bytes);  bytes += sizeof(int);
235             int bot = *((int*)bytes);  bytes += sizeof(int);
236             int left = *((int*)bytes);  bytes += sizeof(int);
237             int right = *((int*)bytes);  bytes += sizeof(int);
239 #if MM_DEBUG_DRAWING
240             NSLog(@"   Insert %d line(s) at row %d", count, row);
241 #endif
242             [textStorage insertLinesAtRow:row lineCount:count
243                              scrollBottom:bot left:left right:right
244                                     color:[NSColor colorWithArgbInt:color]];
245         } else if (DrawCursorDrawType == type) {
246             unsigned color = *((unsigned*)bytes);  bytes += sizeof(unsigned);
247             int row = *((int*)bytes);  bytes += sizeof(int);
248             int col = *((int*)bytes);  bytes += sizeof(int);
249             int shape = *((int*)bytes);  bytes += sizeof(int);
250             int percent = *((int*)bytes);  bytes += sizeof(int);
252 #if MM_DEBUG_DRAWING
253             NSLog(@"   Draw cursor at (%d,%d)", row, col);
254 #endif
255             [helper setInsertionPointColor:[NSColor colorWithRgbInt:color]];
256             [self drawInsertionPointAtRow:row column:col shape:shape
257                                  fraction:percent];
258         } else if (DrawInvertedRectDrawType == type) {
259             int row = *((int*)bytes);  bytes += sizeof(int);
260             int col = *((int*)bytes);  bytes += sizeof(int);
261             int nr = *((int*)bytes);  bytes += sizeof(int);
262             int nc = *((int*)bytes);  bytes += sizeof(int);
263             int invert = *((int*)bytes);  bytes += sizeof(int);
265 #if MM_DEBUG_DRAWING
266             NSLog(@"   Draw inverted rect: row=%d col=%d nrows=%d ncols=%d",
267                     row, col, nr, nc);
268 #endif
269             [self drawInvertedRectAtRow:row column:col numRows:nr numColumns:nc
270                                  invert:invert];
271         } else if (SetCursorPosDrawType == type) {
272             cursorRow = *((int*)bytes);  bytes += sizeof(int);
273             cursorCol = *((int*)bytes);  bytes += sizeof(int);
274         } else {
275             NSLog(@"WARNING: Unknown draw type (type=%d)", type);
276         }
277     }
279     [textStorage endEditing];
281     if (cursorRow >= 0) {
282         unsigned off = [textStorage characterIndexForRow:cursorRow
283                                                   column:cursorCol];
284         unsigned maxoff = [[textStorage string] length];
285         if (off > maxoff) off = maxoff;
287         [self setSelectedRange:NSMakeRange(off, 0)];
288     }
290     // NOTE: During resizing, Cocoa only sends draw messages before Vim's rows
291     // and columns are changed (due to ipc delays). Force a redraw here.
292     if ([self inLiveResize])
293         [self display];
295 #if MM_DEBUG_DRAWING
296     NSLog(@"<==== END   %s", _cmd);
297 #endif
300 - (void)setMouseShape:(int)shape
302     [helper setMouseShape:shape];
305 - (void)setAntialias:(BOOL)state
307     antialias = state;
310 - (void)setImControl:(BOOL)enable
312     [helper setImControl:enable];
315 - (NSFont *)font
317     return [(MMTextStorage*)[self textStorage] font];
320 - (void)setFont:(NSFont *)newFont
322     [(MMTextStorage*)[self textStorage] setFont:newFont];
325 - (NSFont *)fontWide
327     return [(MMTextStorage*)[self textStorage] fontWide];
330 - (void)setWideFont:(NSFont *)newFont
332     [(MMTextStorage*)[self textStorage] setWideFont:newFont];
335 - (NSSize)cellSize
337     return [(MMTextStorage*)[self textStorage] cellSize];
340 - (void)setLinespace:(float)newLinespace
342     return [(MMTextStorage*)[self textStorage] setLinespace:newLinespace];
345 - (int)maxRows
347     MMTextStorage *ts = (MMTextStorage *)[self textStorage];
348     return [ts maxRows];
351 - (int)maxColumns
353     MMTextStorage *ts = (MMTextStorage *)[self textStorage];
354     return [ts maxColumns];
357 - (void)getMaxRows:(int*)rows columns:(int*)cols
359     return [(MMTextStorage*)[self textStorage] getMaxRows:rows columns:cols];
362 - (void)setMaxRows:(int)rows columns:(int)cols
364     return [(MMTextStorage*)[self textStorage] setMaxRows:rows columns:cols];
367 - (NSRect)rectForRowsInRange:(NSRange)range
369     return [(MMTextStorage*)[self textStorage] rectForRowsInRange:range];
372 - (NSRect)rectForColumnsInRange:(NSRange)range
374     return [(MMTextStorage*)[self textStorage] rectForColumnsInRange:range];
377 - (void)setDefaultColorsBackground:(NSColor *)bgColor
378                         foreground:(NSColor *)fgColor
380     [self setBackgroundColor:bgColor];
381     return [(MMTextStorage*)[self textStorage]
382             setDefaultColorsBackground:bgColor foreground:fgColor];
385 - (NSColor *)defaultBackgroundColor
387     return [(MMTextStorage*)[self textStorage] defaultBackgroundColor];
390 - (NSColor *)defaultForegroundColor
392     return [(MMTextStorage*)[self textStorage] defaultForegroundColor];
395 - (NSSize)constrainRows:(int *)rows columns:(int *)cols toSize:(NSSize)size
397     NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
398     int right = [ud integerForKey:MMTextInsetRightKey];
399     int bot = [ud integerForKey:MMTextInsetBottomKey];
401     size.width -= [self textContainerOrigin].x + right;
402     size.height -= [self textContainerOrigin].y + bot;
404     NSSize newSize = [(MMTextStorage*)[self textStorage] fitToSize:size
405                                                               rows:rows
406                                                            columns:cols];
408     newSize.width += [self textContainerOrigin].x + right;
409     newSize.height += [self textContainerOrigin].y + bot;
411     return newSize;
414 - (NSSize)desiredSize
416     NSSize size = [(MMTextStorage*)[self textStorage] size];
418     NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
419     int right = [ud integerForKey:MMTextInsetRightKey];
420     int bot = [ud integerForKey:MMTextInsetBottomKey];
422     size.width += [self textContainerOrigin].x + right;
423     size.height += [self textContainerOrigin].y + bot;
425     return size;
428 - (NSSize)minSize
430     NSSize cellSize = [(MMTextStorage*)[self textStorage] cellSize];
431     NSSize size = { MMMinColumns*cellSize.width, MMMinRows*cellSize.height };
433     NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
434     int right = [ud integerForKey:MMTextInsetRightKey];
435     int bot = [ud integerForKey:MMTextInsetBottomKey];
437     size.width += [self textContainerOrigin].x + right;
438     size.height += [self textContainerOrigin].y + bot;
440     return size;
443 - (BOOL)convertPoint:(NSPoint)point toRow:(int *)row column:(int *)column
445     MMTextStorage *ts = (MMTextStorage*)[self textStorage];
446     NSSize cellSize = [ts cellSize];
447     if (!(cellSize.width > 0 && cellSize.height > 0))
448         return NO;
449     NSPoint origin = [self textContainerOrigin];
451     if (row) *row = floor((point.y-origin.y-1) / cellSize.height);
452     if (column) *column = floor((point.x-origin.x-1) / cellSize.width);
454     //NSLog(@"convertPoint:%@ toRow:%d column:%d", NSStringFromPoint(point),
455     //        *row, *column);
457     return YES;
460 - (NSPoint)pointForRow:(int)row column:(int)col
462     // Return the upper-left coordinate for (row,column).
463     // NOTE: The coordinate system is flipped!
464     NSPoint pt = [self textContainerOrigin];
465     MMTextStorage *ts = (MMTextStorage*)[self textStorage];
466     NSSize cellSize = [ts cellSize];
468     pt.x += col * cellSize.width;
469     pt.y += row * cellSize.height;
471     return pt;
474 - (NSRect)rectForRow:(int)row column:(int)col numRows:(int)nr
475           numColumns:(int)nc
477     // Return the rect for the block which covers the specified rows and
478     // columns.  The upper-left corner is the origin of this rect.
479     // NOTE: The coordinate system is flipped!
480     NSRect rect;
481     MMTextStorage *ts = (MMTextStorage*)[self textStorage];
482     NSSize cellSize = [ts cellSize];
484     rect.origin = [self textContainerOrigin];
485     rect.origin.x += col * cellSize.width;
486     rect.origin.y += row * cellSize.height;
487     rect.size.width = cellSize.width * nc;
488     rect.size.height = cellSize.height * nr;
490     return rect;
493 - (BOOL)isOpaque
495     return NO;
498 - (void)drawRect:(NSRect)rect
500     NSGraphicsContext *context = [NSGraphicsContext currentContext];
501     [context setShouldAntialias:antialias];
503     [super drawRect:rect];
505     if (invertRects) {
506         CGContextRef cgctx = (CGContextRef)[context graphicsPort];
507         CGContextSaveGState(cgctx);
508         CGContextSetBlendMode(cgctx, kCGBlendModeDifference);
509         CGContextSetRGBFillColor(cgctx, 1.0, 1.0, 1.0, 1.0);
511         int i;
512         CGRect *rect = (CGRect*)invertRects;
513         for (i = 0; i < numInvertRects; ++i)
514             CGContextFillRect(cgctx, rect[i]);
516         CGContextRestoreGState(cgctx);
518         free(invertRects);
519         invertRects = NULL;
520         numInvertRects = 0;
521     }
523     if ([self hasMarkedText]) {
524         shouldDrawInsertionPoint = YES;
525         MMTextStorage *ts = (MMTextStorage*)[self textStorage];
526         NSSize inset = [self textContainerInset];
528         // HACK! Get the baseline of the zeroth glyph and use that as the
529         // baseline for the marked text.  (Is there a better way to figure out
530         // what baseline NSTextView uses?)
531         NSLayoutManager *lm = [self layoutManager];
532         NSTypesetter *tsr = [lm typesetter];
533         float baseline = [tsr baselineOffsetInLayoutManager:lm glyphIndex:0];
535         // Also adjust for 'linespace' option (TODO: Why not .5*linespace?)
536         baseline -= floor([ts linespace]);
538         inset.height -= baseline;
540         int len = [[helper markedText] length];
541         // The following implementation should be re-written with
542         // more efficient way...
544         // Calculate how many wide-font characters can be inserted at
545         // a first line, and draw those characters.
546         int cols = ([ts actualColumns] - insertionPointColumn);
547         NSFont *theFont = [[self markedTextAttributes]
548                 valueForKey:NSFontAttributeName];
549         if (theFont == [ts fontWide])
550             cols = cols / 2;
551         int done = 0;
552         int lend = cols > len ? len : cols;
553         NSAttributedString *aString = [[helper markedText]
554                 attributedSubstringFromRange:NSMakeRange(done, lend)];
555         [aString drawAtPoint:NSMakePoint(
556                 [helper preEditColumn]*[ts cellSize].width + inset.width,
557                 [helper preEditRow]*[ts cellSize].height + inset.height)];
559         done = lend;
560         // Check whether there're charecters that aren't drawn at
561         // the first line. If everything is already done, the follow
562         // check fails.
563         if (done != len) {
564             int r;
565             // Calculate How many rows are needed to draw all the left
566             // characters.
567             int rows = (len - done) / ([ts actualColumns] / 2) + 1;
568             for (r = 1; r <= rows; r++) {
569                 lend = len - done > [ts actualColumns] / 2
570                         ? [ts actualColumns] / 2 : len - done;
571                 aString = [[helper markedText] attributedSubstringFromRange:
572                         NSMakeRange(done, lend)];
573                 [aString drawAtPoint:NSMakePoint(
574                         inset.width,
575                         ([helper preEditRow] + r)*[ts cellSize].height
576                             + inset.height)];
577                 done += lend;
578             }
579         }
580     }
582     if (shouldDrawInsertionPoint) {
583         MMTextStorage *ts = (MMTextStorage*)[self textStorage];
585         NSRect ipRect = [ts boundingRectForCharacterAtRow:[helper preEditRow]
586                                                    column:[helper preEditColumn]];
587         ipRect.origin.x += [self textContainerOrigin].x;
588         ipRect.origin.y += [self textContainerOrigin].y;
590         // Draw insertion point inside marked text.
591         if ([self hasMarkedText]) {
592             NSFont *theFont = [[self markedTextAttributes]
593                     valueForKey:NSFontAttributeName];
594             if (theFont == [ts font])
595                 ipRect.origin.x += [ts cellSize].width *
596                                    ([helper imRange].location +
597                                    [helper imRange].length);
598             else
599                 ipRect.origin.x += [ts cellSize].width * 2 *
600                                    ([helper imRange].location +
601                                    [helper imRange].length);
602         }
604         if (MMInsertionPointHorizontal == insertionPointShape) {
605             int frac = ([ts cellSize].height * insertionPointFraction + 99)/100;
606             ipRect.origin.y += ipRect.size.height - frac;
607             ipRect.size.height = frac;
608         } else if (MMInsertionPointVertical == insertionPointShape) {
609             int frac = ([ts cellSize].width * insertionPointFraction + 99)/100;
610             ipRect.size.width = frac;
611         } else if (MMInsertionPointVerticalRight == insertionPointShape) {
612             int frac = ([ts cellSize].width * insertionPointFraction + 99)/100;
613             ipRect.origin.x += ipRect.size.width - frac;
614             ipRect.size.width = frac;
615         }
617         [[helper insertionPointColor] set];
618         if (MMInsertionPointHollow == insertionPointShape) {
619             NSFrameRect(ipRect);
620         } else {
621             NSRectFill(ipRect);
622         }
624         // NOTE: We only draw the cursor once and rely on Vim to say when it
625         // should be drawn again.
626         shouldDrawInsertionPoint = NO;
628         //NSLog(@"%s draw insertion point %@ shape=%d color=%@", _cmd,
629         //        NSStringFromRect(ipRect), insertionPointShape,
630         //        [helper insertionPointColor]);
631     }
633 #if 0
634     // this code invalidates the shadow, so we don't 
635     // get shifting ghost text on scroll and resize
636     // but makes speed unusable
637     MMTextStorage *ts = (MMTextStorage*)[self textStorage];
638     if ([ts defaultBackgroundAlpha] < 1.0f) {
639         if (floor(NSAppKitVersionNumber) <= NSAppKitVersionNumber10_1)
640         {
641             [[self window] setHasShadow:NO];
642             [[self window] setHasShadow:YES];
643         }
644         else
645             [[self window] invalidateShadow];
647     }
648 #endif
651 - (void)keyDown:(NSEvent *)event
653     [helper keyDown:event];
656 - (void)insertText:(id)string
658     [helper insertText:string];
661 - (void)doCommandBySelector:(SEL)selector
663     [helper doCommandBySelector:selector];
666 - (BOOL)performKeyEquivalent:(NSEvent *)event
668     return [helper performKeyEquivalent:event];
671 - (BOOL)hasMarkedText
673     return [helper hasMarkedText];
676 - (NSRange)markedRange
678     return [helper markedRange];
681 - (NSDictionary *)markedTextAttributes
683     return [helper markedTextAttributes];
686 - (void)setMarkedTextAttributes:(NSDictionary *)attr
688     [helper setMarkedTextAttributes:attr];
691 - (void)setMarkedText:(id)text selectedRange:(NSRange)range
693     [helper setMarkedText:text selectedRange:range];
696 - (void)unmarkText
698     [helper unmarkText];
701 - (NSRect)firstRectForCharacterRange:(NSRange)range
703     return [helper firstRectForCharacterRange:range];
706 - (void)scrollWheel:(NSEvent *)event
708     [helper scrollWheel:event];
711 - (void)mouseDown:(NSEvent *)event
713     [helper mouseDown:event];
716 - (void)rightMouseDown:(NSEvent *)event
718     [helper mouseDown:event];
721 - (void)otherMouseDown:(NSEvent *)event
723     [helper mouseDown:event];
726 - (void)mouseUp:(NSEvent *)event
728     [helper mouseUp:event];
731 - (void)rightMouseUp:(NSEvent *)event
733     [helper mouseUp:event];
736 - (void)otherMouseUp:(NSEvent *)event
738     [helper mouseUp:event];
741 - (void)mouseDragged:(NSEvent *)event
743     [helper mouseDragged:event];
746 - (void)rightMouseDragged:(NSEvent *)event
748     [helper mouseDragged:event];
751 - (void)otherMouseDragged:(NSEvent *)event
753     [helper mouseDragged:event];
756 - (void)mouseMoved:(NSEvent *)event
758     [helper mouseMoved:event];
761 - (void)mouseEntered:(NSEvent *)event
763     [helper mouseEntered:event];
766 - (void)mouseExited:(NSEvent *)event
768     [helper mouseExited:event];
771 - (void)setFrame:(NSRect)frame
773     [super setFrame:frame];
774     [helper setFrame:frame];
777 - (void)viewDidMoveToWindow
779     [helper viewDidMoveToWindow];
782 - (void)viewWillMoveToWindow:(NSWindow *)newWindow
784     [helper viewWillMoveToWindow:newWindow];
787 - (NSMenu*)menuForEvent:(NSEvent *)event
789     // HACK! Return nil to disable NSTextView's popup menus (Vim provides its
790     // own).  Called when user Ctrl-clicks in the view (this is already handled
791     // in rightMouseDown:).
792     return nil;
795 - (NSArray *)acceptableDragTypes
797     return [NSArray arrayWithObjects:NSFilenamesPboardType,
798            NSStringPboardType, nil];
801 - (BOOL)performDragOperation:(id <NSDraggingInfo>)sender
803     return [helper performDragOperation:sender];
806 - (NSDragOperation)draggingEntered:(id <NSDraggingInfo>)sender
808     return [helper draggingEntered:sender];
811 - (NSDragOperation)draggingUpdated:(id <NSDraggingInfo>)sender
813     return [helper draggingUpdated:sender];
816 - (void)changeFont:(id)sender
818     [helper changeFont:sender];
821 - (void)resetCursorRects
823     // No need to set up cursor rects since Vim handles cursor changes.
826 - (void)updateFontPanel
828     // The font panel is updated whenever the font is set.
833 // NOTE: The menu items cut/copy/paste/undo/redo/select all/... must be bound
834 // to the same actions as in IB otherwise they will not work with dialogs.  All
835 // we do here is forward these actions to the Vim process.
837 - (IBAction)cut:(id)sender
839     [[self windowController] vimMenuItemAction:sender];
842 - (IBAction)copy:(id)sender
844     [[self windowController] vimMenuItemAction:sender];
847 - (IBAction)paste:(id)sender
849     [[self windowController] vimMenuItemAction:sender];
852 - (IBAction)undo:(id)sender
854     [[self windowController] vimMenuItemAction:sender];
857 - (IBAction)redo:(id)sender
859     [[self windowController] vimMenuItemAction:sender];
862 - (IBAction)selectAll:(id)sender
864     [[self windowController] vimMenuItemAction:sender];
867 - (BOOL)validateMenuItem:(NSMenuItem *)item
869     if ([item action] == @selector(cut:)
870             || [item action] == @selector(copy:)
871             || [item action] == @selector(paste:)
872             || [item action] == @selector(undo:)
873             || [item action] == @selector(redo:)
874             || [item action] == @selector(selectAll:))
875         return [item tag];
877     return YES;
880 @end // MMTextView
885 @implementation MMTextView (Private)
887 - (MMWindowController *)windowController
889     id windowController = [[self window] windowController];
890     if ([windowController isKindOfClass:[MMWindowController class]])
891         return (MMWindowController*)windowController;
892     return nil;
895 - (MMVimController *)vimController
897     return [[self windowController] vimController];
900 - (void)setShouldDrawInsertionPoint:(BOOL)on
902     shouldDrawInsertionPoint = on;
905 - (void)drawInsertionPointAtRow:(int)row column:(int)col shape:(int)shape
906                        fraction:(int)percent
908     // This only stores where to draw the insertion point, the actual drawing
909     // is done in drawRect:.
910     shouldDrawInsertionPoint = YES;
911     insertionPointRow = row;
912     insertionPointColumn = col;
913     insertionPointShape = shape;
914     insertionPointFraction = percent;
917 - (void)drawInvertedRectAtRow:(int)row column:(int)col numRows:(int)nrows
918                    numColumns:(int)ncols invert:(int)invert
920     if (invert) {
921         // The result should be inverted.
922         int n = numInvertRects++;
923         invertRects = reallocf(invertRects,
924                                numInvertRects*sizeof(NSRect));
925         if (NULL != invertRects) {
926             invertRects[n] = [self rectForRow:row column:col numRows:nrows
927                                    numColumns:ncols];
928             [self setNeedsDisplayInRect:invertRects[n]];
929         } else {
930             n = numInvertRects = 0;
931         }
932     } else {
933         // The result should look normal; all we need to do is to mark
934         // the rect for redrawing and Cocoa will redraw the text.
935         NSRect rect = [self rectForRow:row column:col numRows:nrows
936                             numColumns:ncols];
937         [self setNeedsDisplayInRect:rect];
938     }
941 @end // MMTextView (Private)