Improved method to start Vim processes in a login shell
[MacVim.git] / src / MacVim / MMVimView.m
blobf43c6c4bc5cb041f63ee1263f978174e8544d6cc
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  * MMVimView
12  *
13  * A view class with a tabline, scrollbars, and a text view.  The tabline may
14  * appear at the top of the view in which case it fills up the view from left
15  * to right edge.  Any number of scrollbars may appear adjacent to all other
16  * edges of the view (there may be more than one scrollbar per edge and
17  * scrollbars may also be placed on the left edge of the view).  The rest of
18  * the view is filled by the text view.
19  */
21 #import "MMVimView.h"
23 #import <PSMTabBarControl.h>
24 #import "MacVim.h"
25 #import "MMTextView.h"
26 #import "MMVimController.h"
27 #import "MMAtsuiTextView.h"
31 // Scroller type; these must match SBAR_* in gui.h
32 enum {
33     MMScrollerTypeLeft = 0,
34     MMScrollerTypeRight,
35     MMScrollerTypeBottom
38 // TODO:  Move!
39 @interface NSTabView (MMExtras)
40 - (void)removeAllTabViewItems;
41 @end
44 // TODO:  Move!
45 @interface MMScroller : NSScroller {
46     long identifier;
47     int type;
48     NSRange range;
50 - (id)initWithIdentifier:(long)ident type:(int)type;
51 - (long)identifier;
52 - (int)type;
53 - (NSRange)range;
54 - (void)setRange:(NSRange)newRange;
55 @end
58 @interface MMVimView (Private)
59 - (BOOL)bottomScrollbarVisible;
60 - (BOOL)leftScrollbarVisible;
61 - (BOOL)rightScrollbarVisible;
62 - (void)placeScrollbars;
63 - (int)representedIndexOfTabViewItem:(NSTabViewItem *)tvi;
64 - (MMScroller *)scrollbarForIdentifier:(long)ident index:(unsigned *)idx;
65 - (NSSize)vimViewSizeForTextViewSize:(NSSize)textViewSize;
66 - (NSRect)textViewRectForVimViewSize:(NSSize)contentSize;
67 - (NSTabView *)tabView;
68 - (void)frameSizeMayHaveChanged;
69 @end
72 // This is an informal protocol implemented by MMWindowController (maybe it
73 // shold be a formal protocol, but ...).
74 @interface NSWindowController (MMVimViewDelegate)
75 - (void)liveResizeWillStart;
76 - (void)liveResizeDidEnd;
77 @end
81 @implementation MMVimView
83 - (MMVimView *)initWithFrame:(NSRect)frame
84                vimController:(MMVimController *)controller
86     if (![super initWithFrame:frame])
87         return nil;
88     
89     vimController = controller;
90     scrollbars = [[NSMutableArray alloc] init];
92     // Only the tabline is autoresized, all other subview placement is done in
93     // frameSizeMayHaveChanged.
94     [self setAutoresizesSubviews:YES];
96     if ([[NSUserDefaults standardUserDefaults] boolForKey:MMAtsuiRendererKey]) {
97         // Use ATSUI for text rendering.
98         //
99         // HACK! 'textView' has type MMTextView, but MMAtsuiTextView is not
100         // derived from MMTextView.
101         textView = [[MMAtsuiTextView alloc] initWithFrame:frame];
102     } else {
103         // Use Cocoa text system for text rendering.
104         textView = [[MMTextView alloc] initWithFrame:frame];
105     }
107     // Allow control of text view inset via MMTextInset* user defaults.
108     NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
109     int left = [ud integerForKey:MMTextInsetLeftKey];
110     int top = [ud integerForKey:MMTextInsetTopKey];
111     [textView setTextContainerInset:NSMakeSize(left, top)];
113     [textView setAutoresizingMask:NSViewNotSizable];
114     [self addSubview:textView];
115     
116     // Create the tab view (which is never visible, but the tab bar control
117     // needs it to function).
118     tabView = [[NSTabView alloc] initWithFrame:NSZeroRect];
120     // Create the tab bar control (which is responsible for actually
121     // drawing the tabline and tabs).
122     NSRect tabFrame = { { 0, frame.size.height - 22 },
123                         { frame.size.width, 22 } };
124     tabBarControl = [[PSMTabBarControl alloc] initWithFrame:tabFrame];
126     [tabView setDelegate:tabBarControl];
128     [tabBarControl setTabView:tabView];
129     [tabBarControl setDelegate:self];
130     [tabBarControl setHidden:YES];
132     [tabBarControl setCellMinWidth:[ud integerForKey:MMTabMinWidthKey]];
133     [tabBarControl setCellMaxWidth:[ud integerForKey:MMTabMaxWidthKey]];
134     [tabBarControl setCellOptimumWidth:
135                                      [ud integerForKey:MMTabOptimumWidthKey]];
137     [tabBarControl setShowAddTabButton:YES];
138     [[tabBarControl addTabButton] setTarget:self];
139     [[tabBarControl addTabButton] setAction:@selector(addNewTab:)];
140     [tabBarControl setAllowsDragBetweenWindows:NO];
142     [tabBarControl setAutoresizingMask:NSViewWidthSizable|NSViewMinYMargin];
143     
144     //[tabBarControl setPartnerView:textView];
145     
146     // tab bar resizing only works if awakeFromNib is called (that's where
147     // the NSViewFrameDidChangeNotification callback is installed). Sounds like
148     // a PSMTabBarControl bug, let's live with it for now.
149     [tabBarControl awakeFromNib];
151     [self addSubview:tabBarControl];
153     return self;
156 - (void)dealloc
158     [tabBarControl release];  tabBarControl = nil;
159     [tabView release];  tabView = nil;
160     [scrollbars release];  scrollbars = nil;
162     // HACK! The text storage is the principal owner of the text system, but we
163     // keep only a reference to the text view, so release the text storage
164     // first (unless we are using the ATSUI renderer).
165     if (![[NSUserDefaults standardUserDefaults]
166             boolForKey:MMAtsuiRendererKey])
167         [[textView textStorage] release];
169     [textView release];  textView = nil;
171     [super dealloc];
174 - (void)drawRect:(NSRect)rect
176     // On Leopard, we want to have a textured window background for nice
177     // looking tabs. However, the textured window background looks really
178     // weird behind the window resize throbber, so emulate the look of an
179     // NSScrollView in the bottom right corner.
180     if (![[self window] showsResizeIndicator]  // XXX: make this a flag
181             || !([[self window] styleMask] & NSTexturedBackgroundWindowMask))
182         return;
184     int sw = [NSScroller scrollerWidth];
186     // add .5 to the pixel locations to put the lines on a pixel boundary.
187     // the top and right edges of the rect will be outside of the bounds rect
188     // and clipped away.
189     NSRect sizerRect = NSMakeRect([self bounds].size.width - sw + .5, -.5,
190             sw, sw);
191     //NSBezierPath* path = [NSBezierPath bezierPath];
192     NSBezierPath* path = [NSBezierPath bezierPathWithRect:sizerRect];
194     // On Tiger, we have color #E8E8E8 behind the resize throbber
195     // (which is windowBackgroundColor on untextured windows or controlColor in
196     // general). Terminal.app on Leopard has #FFFFFF background and #D9D9D9 as
197     // stroke. The colors below are #FFFFFF and #D4D4D4, which is close enough
198     // for me.
199     [[NSColor controlBackgroundColor] set];
200     [path fill];
202     [[NSColor secondarySelectedControlColor] set];
203     [path stroke];
206 - (MMTextView *)textView
208     return textView;
211 - (NSMutableArray *)scrollbars
213     return scrollbars;
216 - (PSMTabBarControl *)tabBarControl
218     return tabBarControl;
221 - (void)cleanup
223     vimController = nil;
224     
225     // NOTE! There is a bug in PSMTabBarControl in that it retains the delegate
226     // so reset the delegate here, otherwise the delegate may never get
227     // released.
228     [tabView setDelegate:nil];
229     [tabBarControl setDelegate:nil];
230     [tabBarControl setTabView:nil];
231     [[self window] setDelegate:nil];
233     // NOTE! There is another bug in PSMTabBarControl where the control is not
234     // removed as an observer, so remove it here (failing to remove an observer
235     // may lead to very strange bugs).
236     [[NSNotificationCenter defaultCenter] removeObserver:tabBarControl];
238     [tabBarControl removeFromSuperviewWithoutNeedingDisplay];
239     [textView removeFromSuperviewWithoutNeedingDisplay];
241     unsigned i, count = [scrollbars count];
242     for (i = 0; i < count; ++i) {
243         MMScroller *sb = [scrollbars objectAtIndex:i];
244         [sb removeFromSuperviewWithoutNeedingDisplay];
245     }
247     [tabView removeAllTabViewItems];
250 - (NSSize)desiredSize
252     return [self vimViewSizeForTextViewSize:[textView desiredSize]];
255 - (NSSize)minSize
257     return [self vimViewSizeForTextViewSize:[textView minSize]];
260 - (NSSize)constrainRows:(int *)r columns:(int *)c toSize:(NSSize)size
262     NSSize textViewSize = [self textViewRectForVimViewSize:size].size;
263     textViewSize = [textView constrainRows:r columns:c toSize:textViewSize];
264     return [self vimViewSizeForTextViewSize:textViewSize];
267 - (void)setDesiredRows:(int)r columns:(int)c
269     [textView setMaxRows:r columns:c];
272 - (IBAction)addNewTab:(id)sender
274     [vimController sendMessage:AddNewTabMsgID data:nil];
277 - (void)updateTabsWithData:(NSData *)data
279     const void *p = [data bytes];
280     const void *end = p + [data length];
281     int tabIdx = 0;
283     // HACK!  Current tab is first in the message.  This way it is not
284     // necessary to guess which tab should be the selected one (this can be
285     // problematic for instance when new tabs are created).
286     int curtabIdx = *((int*)p);  p += sizeof(int);
288     NSArray *tabViewItems = [[self tabBarControl] representedTabViewItems];
290     while (p < end) {
291         //int wincount = *((int*)p);  p += sizeof(int);
292         int length = *((int*)p);  p += sizeof(int);
294         NSString *label = [[NSString alloc]
295                 initWithBytes:(void*)p length:length
296                      encoding:NSUTF8StringEncoding];
297         p += length;
299         // Set the label of the tab;  add a new tab when needed.
300         NSTabViewItem *tvi = [[self tabView] numberOfTabViewItems] <= tabIdx
301                 ? [self addNewTabViewItem]
302                 : [tabViewItems objectAtIndex:tabIdx];
304         [tvi setLabel:label];
306         [label release];
308         ++tabIdx;
309     }
311     // Remove unused tabs from the NSTabView.  Note that when a tab is closed
312     // the NSTabView will automatically select another tab, but we want Vim to
313     // take care of which tab to select so set the vimTaskSelectedTab flag to
314     // prevent the tab selection message to be passed on to the VimTask.
315     vimTaskSelectedTab = YES;
316     int i, count = [[self tabView] numberOfTabViewItems];
317     for (i = count-1; i >= tabIdx; --i) {
318         id tvi = [tabViewItems objectAtIndex:i];
319         //NSLog(@"Removing tab with index %d", i);
320         [[self tabView] removeTabViewItem:tvi];
321     }
322     vimTaskSelectedTab = NO;
324     [self selectTabWithIndex:curtabIdx];
327 - (void)selectTabWithIndex:(int)idx
329     //NSLog(@"%s%d", _cmd, idx);
331     NSArray *tabViewItems = [[self tabBarControl] representedTabViewItems];
332     if (idx < 0 || idx >= [tabViewItems count]) {
333         NSLog(@"WARNING: No tab with index %d exists.", idx);
334         return;
335     }
337     // Do not try to select a tab if already selected.
338     NSTabViewItem *tvi = [tabViewItems objectAtIndex:idx];
339     if (tvi != [[self tabView] selectedTabViewItem]) {
340         vimTaskSelectedTab = YES;
341         [[self tabView] selectTabViewItem:tvi];
342         vimTaskSelectedTab = NO;
344         // We might need to change the scrollbars that are visible.
345         [self placeScrollbars];
346     }
349 - (NSTabViewItem *)addNewTabViewItem
351     // NOTE!  A newly created tab is not by selected by default; Vim decides
352     // which tab should be selected at all times.  However, the AppKit will
353     // automatically select the first tab added to a tab view.
355     NSTabViewItem *tvi = [[NSTabViewItem alloc] initWithIdentifier:nil];
357     // NOTE: If this is the first tab it will be automatically selected.
358     vimTaskSelectedTab = YES;
359     [[self tabView] addTabViewItem:tvi];
360     vimTaskSelectedTab = NO;
362     [tvi autorelease];
364     return tvi;
367 - (void)createScrollbarWithIdentifier:(long)ident type:(int)type
369     //NSLog(@"Create scroller %d of type %d", ident, type);
371     MMScroller *scroller = [[MMScroller alloc] initWithIdentifier:ident
372                                                              type:type];
373     [scroller setTarget:self];
374     [scroller setAction:@selector(scroll:)];
376     [self addSubview:scroller];
377     [[self scrollbars] addObject:scroller];
378     [scroller release];
381 - (BOOL)destroyScrollbarWithIdentifier:(long)ident
383     //NSLog(@"Destroy scroller %d", ident);
385     unsigned idx = 0;
386     MMScroller *scroller = [self scrollbarForIdentifier:ident index:&idx];
387     if (!scroller) return NO;
389     [scroller removeFromSuperview];
390     [[self scrollbars] removeObjectAtIndex:idx];
392     // If a visible scroller was removed then the vim view must resize.  This
393     // is handled by the window controller (the vim view never resizes itself).
394     return ![scroller isHidden];
397 - (BOOL)showScrollbarWithIdentifier:(long)ident state:(BOOL)visible
399     MMScroller *scroller = [self scrollbarForIdentifier:ident index:NULL];
400     if (!scroller) return NO;
402     BOOL wasVisible = ![scroller isHidden];
403     //NSLog(@"%s scroller %d (was %svisible)", visible ? "Show" : "Hide",
404     //      ident, wasVisible ? "" : "in");
405     [scroller setHidden:!visible];
407     // If a scroller was hidden or shown then the vim view must resize.  This
408     // is handled by the window controller (the vim view never resizes itself).
409     return wasVisible != visible;
412 - (void)setScrollbarThumbValue:(float)val proportion:(float)prop
413                     identifier:(long)ident
415     MMScroller *scroller = [self scrollbarForIdentifier:ident index:NULL];
416     //NSLog(@"Set thumb value %.2f proportion %.2f for scroller %d",
417     //        val, prop, ident);
418     [scroller setFloatValue:val knobProportion:prop];
419     [scroller setEnabled:prop != 1.f];
423 - (void)scroll:(id)sender
425     NSMutableData *data = [NSMutableData data];
426     long ident = [(MMScroller*)sender identifier];
427     int hitPart = [sender hitPart];
428     float value = [sender floatValue];
430     [data appendBytes:&ident length:sizeof(long)];
431     [data appendBytes:&hitPart length:sizeof(int)];
432     [data appendBytes:&value length:sizeof(float)];
434     [vimController sendMessage:ScrollbarEventMsgID data:data];
437 - (void)setScrollbarPosition:(int)pos length:(int)len identifier:(long)ident
439     MMScroller *scroller = [self scrollbarForIdentifier:ident index:NULL];
440     NSRange range = NSMakeRange(pos, len);
441     if (!NSEqualRanges(range, [scroller range])) {
442         //NSLog(@"Set range %@ for scroller %d",
443         //        NSStringFromRange(range), ident);
444         [scroller setRange:range];
445         // TODO!  Should only do this once per update.
447         // This could be sent because a text window was created or closed, so
448         // we might need to update which scrollbars are visible.
449         [self placeScrollbars];
450     }
453 - (void)setDefaultColorsBackground:(NSColor *)back foreground:(NSColor *)fore
455     [textView setDefaultColorsBackground:back foreground:fore];
459 // -- PSMTabBarControl delegate ----------------------------------------------
462 - (BOOL)tabView:(NSTabView *)theTabView shouldSelectTabViewItem:
463     (NSTabViewItem *)tabViewItem
465     // NOTE: It would be reasonable to think that 'shouldSelect...' implies
466     // that this message only gets sent when the user clicks the tab.
467     // Unfortunately it is not so, which is why we need the
468     // 'vimTaskSelectedTab' flag.
469     //
470     // HACK!  The selection message should not be propagated to Vim if Vim
471     // selected the tab (e.g. as opposed the user clicking the tab).  The
472     // delegate method has no way of knowing who initiated the selection so a
473     // flag is set when Vim initiated the selection.
474     if (!vimTaskSelectedTab) {
475         // Propagate the selection message to Vim.
476         int idx = [self representedIndexOfTabViewItem:tabViewItem];
477         if (NSNotFound != idx) {
478             NSData *data = [NSData dataWithBytes:&idx length:sizeof(int)];
479             [vimController sendMessage:SelectTabMsgID data:data];
480         }
481     }
483     // Unless Vim selected the tab, return NO, and let Vim decide if the tab
484     // should get selected or not.
485     return vimTaskSelectedTab;
488 - (BOOL)tabView:(NSTabView *)theTabView shouldCloseTabViewItem:
489         (NSTabViewItem *)tabViewItem
491     // HACK!  This method is only called when the user clicks the close button
492     // on the tab.  Instead of letting the tab bar close the tab, we return NO
493     // and pass a message on to Vim to let it handle the closing.
494     int idx = [self representedIndexOfTabViewItem:tabViewItem];
495     //NSLog(@"Closing tab with index %d", idx);
496     NSData *data = [NSData dataWithBytes:&idx length:sizeof(int)];
497     [vimController sendMessage:CloseTabMsgID data:data];
499     return NO;
502 - (void)tabView:(NSTabView *)theTabView didDragTabViewItem:
503         (NSTabViewItem *)tabViewItem toIndex:(int)idx
505     NSMutableData *data = [NSMutableData data];
506     [data appendBytes:&idx length:sizeof(int)];
508     [vimController sendMessage:DraggedTabMsgID data:data];
512 // -- NSView customization ---------------------------------------------------
515 - (void)viewWillStartLiveResize
517     id windowController = [[self window] windowController];
518     [windowController liveResizeWillStart];
520     [super viewWillStartLiveResize];
523 - (void)viewDidEndLiveResize
525     id windowController = [[self window] windowController];
526     [windowController liveResizeDidEnd];
528     [super viewDidEndLiveResize];
531 - (void)setFrameSize:(NSSize)size
533     // NOTE: Instead of only acting when a frame was resized, we do some
534     // updating each time a frame may be resized.  (At the moment, if we only
535     // respond to actual frame changes then typing ":set lines=1000" twice in a
536     // row will result in the vim view holding more rows than the can fit
537     // inside the window.)
538     [super setFrameSize:size];
539     [self frameSizeMayHaveChanged];
542 - (void)setFrame:(NSRect)frame
544     // See comment in setFrameSize: above.
545     [super setFrame:frame];
546     [self frameSizeMayHaveChanged];
549 @end // MMVimView
554 @implementation MMVimView (Private)
556 - (BOOL)bottomScrollbarVisible
558     unsigned i, count = [scrollbars count];
559     for (i = 0; i < count; ++i) {
560         MMScroller *scroller = [scrollbars objectAtIndex:i];
561         if ([scroller type] == MMScrollerTypeBottom && ![scroller isHidden])
562             return YES;
563     }
565     return NO;
568 - (BOOL)leftScrollbarVisible
570     unsigned i, count = [scrollbars count];
571     for (i = 0; i < count; ++i) {
572         MMScroller *scroller = [scrollbars objectAtIndex:i];
573         if ([scroller type] == MMScrollerTypeLeft && ![scroller isHidden])
574             return YES;
575     }
577     return NO;
580 - (BOOL)rightScrollbarVisible
582     unsigned i, count = [scrollbars count];
583     for (i = 0; i < count; ++i) {
584         MMScroller *scroller = [scrollbars objectAtIndex:i];
585         if ([scroller type] == MMScrollerTypeRight && ![scroller isHidden])
586             return YES;
587     }
589     return NO;
592 - (void)placeScrollbars
594     NSRect textViewFrame = [textView frame];
595     BOOL lsbVisible = [self leftScrollbarVisible];
597     // HACK!  Find the lowest left&right vertical scrollbars, as well as the
598     // rightmost horizontal scrollbar.  This hack continues further down.
599     //
600     // TODO!  Can there be no more than one horizontal scrollbar?  If so, the
601     // code can be simplified.
602     unsigned lowestLeftSbIdx = (unsigned)-1;
603     unsigned lowestRightSbIdx = (unsigned)-1;
604     unsigned rightmostSbIdx = (unsigned)-1;
605     unsigned rowMaxLeft = 0, rowMaxRight = 0, colMax = 0;
606     unsigned i, count = [scrollbars count];
607     for (i = 0; i < count; ++i) {
608         MMScroller *scroller = [scrollbars objectAtIndex:i];
609         if (![scroller isHidden]) {
610             NSRange range = [scroller range];
611             if ([scroller type] == MMScrollerTypeLeft
612                     && range.location >= rowMaxLeft) {
613                 rowMaxLeft = range.location;
614                 lowestLeftSbIdx = i;
615             } else if ([scroller type] == MMScrollerTypeRight
616                     && range.location >= rowMaxRight) {
617                 rowMaxRight = range.location;
618                 lowestRightSbIdx = i;
619             } else if ([scroller type] == MMScrollerTypeBottom
620                     && range.location >= colMax) {
621                 colMax = range.location;
622                 rightmostSbIdx = i;
623             }
624         }
625     }
627     // Place the scrollbars.
628     for (i = 0; i < count; ++i) {
629         MMScroller *scroller = [scrollbars objectAtIndex:i];
630         if ([scroller isHidden])
631             continue;
633         NSRect rect;
634         if ([scroller type] == MMScrollerTypeBottom) {
635             rect = [textView rectForColumnsInRange:[scroller range]];
636             rect.size.height = [NSScroller scrollerWidth];
637             if (lsbVisible)
638                 rect.origin.x += [NSScroller scrollerWidth];
640             // HACK!  Make sure the rightmost horizontal scrollbar covers the
641             // text view all the way to the right, otherwise it looks ugly when
642             // the user drags the window to resize.
643             if (i == rightmostSbIdx) {
644                 float w = NSMaxX(textViewFrame) - NSMaxX(rect);
645                 if (w > 0)
646                     rect.size.width += w;
647             }
649             // Make sure scrollbar rect is bounded by the text view frame.
650             if (rect.origin.x < textViewFrame.origin.x)
651                 rect.origin.x = textViewFrame.origin.x;
652             else if (rect.origin.x > NSMaxX(textViewFrame))
653                 rect.origin.x = NSMaxX(textViewFrame);
654             if (NSMaxX(rect) > NSMaxX(textViewFrame))
655                 rect.size.width -= NSMaxX(rect) - NSMaxX(textViewFrame);
656             if (rect.size.width < 0)
657                 rect.size.width = 0;
658         } else {
659             rect = [textView rectForRowsInRange:[scroller range]];
660             // Adjust for the fact that text layout is flipped.
661             rect.origin.y = NSMaxY(textViewFrame) - rect.origin.y
662                     - rect.size.height;
663             rect.size.width = [NSScroller scrollerWidth];
664             if ([scroller type] == MMScrollerTypeRight)
665                 rect.origin.x = NSMaxX(textViewFrame);
667             // HACK!  Make sure the lowest vertical scrollbar covers the text
668             // view all the way to the bottom.  This is done because Vim only
669             // makes the scrollbar cover the (vim-)window it is associated with
670             // and this means there is always an empty gap in the scrollbar
671             // region next to the command line.
672             // TODO!  Find a nicer way to do this.
673             if (i == lowestLeftSbIdx || i == lowestRightSbIdx) {
674                 float h = rect.origin.y + rect.size.height
675                           - textViewFrame.origin.y;
676                 if (rect.size.height < h) {
677                     rect.origin.y = textViewFrame.origin.y;
678                     rect.size.height = h;
679                 }
680             }
682             // Vertical scrollers must not cover the resize box in the
683             // bottom-right corner of the window.
684             if ([[self window] showsResizeIndicator]  // XXX: make this a flag
685                 && rect.origin.y < [NSScroller scrollerWidth]) {
686                 rect.size.height -= [NSScroller scrollerWidth] - rect.origin.y;
687                 rect.origin.y = [NSScroller scrollerWidth];
688             }
690             // Make sure scrollbar rect is bounded by the text view frame.
691             if (rect.origin.y < textViewFrame.origin.y) {
692                 rect.size.height -= textViewFrame.origin.y - rect.origin.y;
693                 rect.origin.y = textViewFrame.origin.y;
694             } else if (rect.origin.y > NSMaxY(textViewFrame))
695                 rect.origin.y = NSMaxY(textViewFrame);
696             if (NSMaxY(rect) > NSMaxY(textViewFrame))
697                 rect.size.height -= NSMaxY(rect) - NSMaxY(textViewFrame);
698             if (rect.size.height < 0)
699                 rect.size.height = 0;
700         }
702         //NSLog(@"set scroller #%d frame = %@", i, NSStringFromRect(rect));
703         NSRect oldRect = [scroller frame];
704         if (!NSEqualRects(oldRect, rect)) {
705             [scroller setFrame:rect];
706             // Clear behind the old scroller frame, or parts of the old
707             // scroller might still be visible after setFrame:.
708             [[[self window] contentView] setNeedsDisplayInRect:oldRect];
709             [scroller setNeedsDisplay:YES];
710         }
711     }
714 - (int)representedIndexOfTabViewItem:(NSTabViewItem *)tvi
716     NSArray *tabViewItems = [[self tabBarControl] representedTabViewItems];
717     return [tabViewItems indexOfObject:tvi];
720 - (MMScroller *)scrollbarForIdentifier:(long)ident index:(unsigned *)idx
722     unsigned i, count = [[self scrollbars] count];
723     for (i = 0; i < count; ++i) {
724         MMScroller *scroller = [[self scrollbars] objectAtIndex:i];
725         if ([scroller identifier] == ident) {
726             if (idx) *idx = i;
727             return scroller;
728         }
729     }
731     return nil;
734 - (NSSize)vimViewSizeForTextViewSize:(NSSize)textViewSize
736     NSSize size = textViewSize;
738     if (![[self tabBarControl] isHidden])
739         size.height += [[self tabBarControl] frame].size.height;
741     if ([self bottomScrollbarVisible])
742         size.height += [NSScroller scrollerWidth];
743     if ([self leftScrollbarVisible])
744         size.width += [NSScroller scrollerWidth];
745     if ([self rightScrollbarVisible])
746         size.width += [NSScroller scrollerWidth];
748     return size;
751 - (NSRect)textViewRectForVimViewSize:(NSSize)contentSize
753     NSRect rect = { 0, 0, contentSize.width, contentSize.height };
755     if (![[self tabBarControl] isHidden])
756         rect.size.height -= [[self tabBarControl] frame].size.height;
758     if ([self bottomScrollbarVisible]) {
759         rect.size.height -= [NSScroller scrollerWidth];
760         rect.origin.y += [NSScroller scrollerWidth];
761     }
762     if ([self leftScrollbarVisible]) {
763         rect.size.width -= [NSScroller scrollerWidth];
764         rect.origin.x += [NSScroller scrollerWidth];
765     }
766     if ([self rightScrollbarVisible])
767         rect.size.width -= [NSScroller scrollerWidth];
769     return rect;
772 - (NSTabView *)tabView
774     return tabView;
777 - (void)frameSizeMayHaveChanged
779     // NOTE: Whenever a call is made that may have changed the frame size we
780     // take the opportunity to make sure all subviews are in place and that the
781     // (rows,columns) are constrained to lie inside the new frame.  We not only
782     // do this when the frame really has changed since it is possible to modify
783     // the number of (rows,columns) without changing the frame size.
785     // Give all superfluous space to the text view. It might be smaller or
786     // larger than it wants to be, but this is needed during live resizing.
787     NSRect textViewRect = [self textViewRectForVimViewSize:[self frame].size];
788     [textView setFrame:textViewRect];
790     [self placeScrollbars];
792     // It is possible that the current number of (rows,columns) is too big or
793     // too small to fit the new frame.  If so, notify Vim that the text
794     // dimensions should change, but don't actually change the number of
795     // (rows,columns).  These numbers may only change when Vim initiates the
796     // change (as opposed to the user dragging the window resizer, for
797     // example).
798     //
799     // Note that the message sent to Vim depends on whether we're in
800     // a live resize or not -- this is necessary to avoid the window jittering
801     // when the user drags to resize.
802     int constrained[2];
803     NSSize textViewSize = [textView frame].size;
804     [textView constrainRows:&constrained[0] columns:&constrained[1]
805                      toSize:textViewSize];
807     int rows, cols;
808     [textView getMaxRows:&rows columns:&cols];
810     if (constrained[0] != rows || constrained[1] != cols) {
811         NSData *data = [NSData dataWithBytes:constrained length:2*sizeof(int)];
812         int msgid = [self inLiveResize] ? LiveResizeMsgID
813                                         : SetTextDimensionsMsgID;
815         //NSLog(@"Notify Vim that text dimensions changed from %dx%d to %dx%d"
816         //       " (%s)", cols, rows, constrained[1], constrained[0],
817         //       MessageStrings[msgid]);
819         [vimController sendMessage:msgid data:data];
821         // We only want to set the window title if this resize came from
822         // a live-resize, not (for example) setting 'columns' or 'lines'.
823         if ([self inLiveResize]) {
824             [[self window] setTitle:[NSString stringWithFormat:@"%dx%d",
825                     constrained[1], constrained[0]]];
826         }
827     }
830 @end // MMVimView (Private)
835 @implementation NSTabView (MMExtras)
837 - (void)removeAllTabViewItems
839     NSArray *existingItems = [self tabViewItems];
840     NSEnumerator *e = [existingItems objectEnumerator];
841     NSTabViewItem *item;
842     while (item = [e nextObject]){
843         [self removeTabViewItem:item];
844     }
847 @end // NSTabView (MMExtras)
852 @implementation MMScroller
854 - (id)initWithIdentifier:(long)ident type:(int)theType
856     // HACK! NSScroller creates a horizontal scroller if it is init'ed with a
857     // frame whose with exceeds its height; so create a bogus rect and pass it
858     // to initWithFrame.
859     NSRect frame = theType == MMScrollerTypeBottom
860             ? NSMakeRect(0, 0, 1, 0)
861             : NSMakeRect(0, 0, 0, 1);
863     self = [super initWithFrame:frame];
864     if (!self) return nil;
866     identifier = ident;
867     type = theType;
868     [self setHidden:YES];
869     [self setEnabled:YES];
870     [self setAutoresizingMask:NSViewNotSizable];
872     return self;
875 - (long)identifier
877     return identifier;
880 - (int)type
882     return type;
885 - (NSRange)range
887     return range;
890 - (void)setRange:(NSRange)newRange
892     range = newRange;
895 - (void)scrollWheel:(NSEvent *)event
897     // HACK! Pass message on to the text view.
898     NSView *vimView = [self superview];
899     if ([vimView isKindOfClass:[MMVimView class]])
900         [[(MMVimView*)vimView textView] scrollWheel:event];
903 @end // MMScroller