3232static NSString * const IDESourceCodeEditorTextViewBoundsDidChangeNotification = @" IDESourceCodeEditorTextViewBoundsDidChangeNotification" ;
3333static NSString * const DVTFontAndColorSourceTextSettingsChangedNotification = @" DVTFontAndColorSourceTextSettingsChangedNotification" ;
3434
35-
36- @interface NSObject (SCXcodeMinimapDelayedLayoutManager)
37-
38- - (void )sc_performBlock : (void (^)(void ))block afterDelay : (NSTimeInterval )delay cancelPreviousRequest : (BOOL )cancel ;
39-
40- @end
41-
42-
43- @interface SCXcodeMinimapDelayedLayoutManager : DVTLayoutManager
44-
45- @property (nonatomic , strong ) NSValue *combinedRangeValue;
46-
47- @end
48-
49-
5035@interface SCXcodeMinimapView () <NSLayoutManagerDelegate >
5136
5237@property (nonatomic , strong ) IDESourceCodeEditor *editor;
@@ -58,6 +43,11 @@ @interface SCXcodeMinimapView () <NSLayoutManagerDelegate>
5843@property (nonatomic , strong ) SCXcodeMinimapSelectionView *selectionView;
5944@property (nonatomic , strong ) IDESourceCodeDocument *document;
6045
46+ @property (nonatomic , assign ) BOOL shouldAllowFullSyntaxHighlight;
47+
48+ @property (nonatomic , strong ) NSColor *commentColor;
49+ @property (nonatomic , strong ) NSColor *preprocessorColor;
50+
6151@end
6252
6353@implementation SCXcodeMinimapView
@@ -89,17 +79,15 @@ - (instancetype)initWithFrame:(NSRect)frame editor:(IDESourceCodeEditor *)editor
8979 [self addSubview: self .scrollView];
9080
9181 self.textView = [[DVTSourceTextView alloc ] initWithFrame: self .editorTextView.bounds];
92- SCXcodeMinimapDelayedLayoutManager *layoutManager = [[SCXcodeMinimapDelayedLayoutManager alloc ] init ];
93- [self .textView.textContainer replaceLayoutManager: layoutManager];
82+ [self .editorTextView.textStorage addLayoutManager: self .textView.layoutManager];
9483 [self .textView setEditable: NO ];
9584 [self .textView setSelectable: NO ];
9685
97- [self .editorTextView.textStorage addLayoutManager: layoutManager];
98-
9986 [self .scrollView setDocumentView: self .textView];
10087
10188 [self .scrollView setAllowsMagnification: YES ];
10289 [self .scrollView setMinMagnification: kDefaultZoomLevel ];
90+ [self .scrollView setMaxMagnification: kDefaultZoomLevel ];
10391 [self .scrollView setMagnification: kDefaultZoomLevel ];
10492
10593
@@ -125,7 +113,7 @@ - (instancetype)initWithFrame:(NSRect)frame editor:(IDESourceCodeEditor *)editor
125113
126114 [[NSNotificationCenter defaultCenter ] addObserverForName: IDESourceCodeEditorTextViewBoundsDidChangeNotification object: nil queue: nil usingBlock: ^(NSNotification *note) {
127115 if ([note.object isEqual: weakSelf.editor]) {
128- [self updateOffset ];
116+ [weakSelf updateOffset ];
129117 }
130118 }];
131119 }
@@ -145,79 +133,99 @@ - (void)setVisible:(BOOL)visible
145133
146134 if (visible) {
147135 [self updateOffset ];
136+
137+ [self .textView.layoutManager setDelegate: self ];
138+ } else {
139+ [self .textView.layoutManager setDelegate: nil ];
148140 }
149-
150- // Ensure the layout manager's delegate is set to self. The DVTSourceTextView resets it if called to early.
151- [self .textView.layoutManager setDelegate: self ];
152- [self .textView.layoutManager setAllowsNonContiguousLayout: NO ];
153141}
154142
155143#pragma mark - NSLayoutManagerDelegate
156144
157- - (NSDictionary *)layoutManager : (NSLayoutManager *)layoutManager shouldUseTemporaryAttributes : (NSDictionary *)attrs forDrawingToScreen : (BOOL )toScreen atCharacterIndex : (NSUInteger )charIndex effectiveRange : (NSRangePointer )effectiveCharRange
145+ - (NSDictionary *)layoutManager : (NSLayoutManager *)layoutManager
146+ shouldUseTemporaryAttributes : (NSDictionary *)attrs
147+ forDrawingToScreen : (BOOL )toScreen
148+ atCharacterIndex : (NSUInteger )charIndex
149+ effectiveRange : (NSRangePointer )effectiveCharRange
158150{
159151 if (!toScreen || self.hidden ) {
160152 return nil ;
161153 }
162154
163- DVTTextStorage *storage = [self .editorTextView textStorage ];
155+ // Prevent full range invalidation for performance reasons.
156+ if (!self.shouldAllowFullSyntaxHighlight ) {
157+ NSRange visibleEditorRange = [self .editorTextView visibleCharacterRange ];
158+ if (charIndex > visibleEditorRange.location + visibleEditorRange.length ) {
159+ *effectiveCharRange = NSMakeRange (visibleEditorRange.location + visibleEditorRange.length ,
160+ layoutManager.textStorage .length - visibleEditorRange.location - visibleEditorRange.length );
161+
162+ return @{NSForegroundColorAttributeName : [[DVTFontAndColorTheme currentTheme ] sourcePlainTextColor ]};
163+ }
164+
165+ if (charIndex < visibleEditorRange.location ) {
166+ *effectiveCharRange = NSMakeRange (0 , visibleEditorRange.location );
167+ return @{NSForegroundColorAttributeName : [[DVTFontAndColorTheme currentTheme ] sourcePlainTextColor ]};
168+ }
169+ }
164170
165- short currentNodeId = [storage nodeTypeAtCharacterIndex: charIndex effectiveRange: effectiveCharRange context: nil ];
171+ // Attempt a full range invalidation after all temporary attributes are set
172+ __weak typeof (self) weakSelf = self;
173+ [self performBlock: ^{
174+ weakSelf.shouldAllowFullSyntaxHighlight = YES ;
175+ NSRange visibleMinimapRange = [weakSelf.textView visibleCharacterRange ];
176+ [weakSelf.textView.layoutManager invalidateDisplayForCharacterRange: visibleMinimapRange];
177+ } afterDelay: 0 .5f cancelPreviousRequest: YES ];
166178
179+ // Rely on the colorAtCharacterIndex: method to update the effective range
180+ DVTTextStorage *storage = [self .editorTextView textStorage ];
167181 NSColor *color = [storage colorAtCharacterIndex: charIndex effectiveRange: effectiveCharRange context: nil ];
168- NSColor *backgroundColor = nil ;
169182
183+ // Background color for comments and preprocessor directives
184+ short currentNodeId = [storage nodeTypeAtCharacterIndex: charIndex effectiveRange: NULL context: nil ];
170185 if (currentNodeId == [DVTSourceNodeTypes registerNodeTypeNamed: kXcodeSyntaxCommentNodeName ] ||
171186 currentNodeId == [DVTSourceNodeTypes registerNodeTypeNamed: kXcodeSyntaxCommentDocNodeName ] ||
172- currentNodeId == [DVTSourceNodeTypes registerNodeTypeNamed: kXcodeSyntaxCommentDocKeywordNodeName ])
173- {
174- NSColor *color = [[[DVTFontAndColorTheme currentTheme ] syntaxColorsByNodeType ] pointerAtIndex: [DVTSourceNodeTypes registerNodeTypeNamed: kXcodeSyntaxCommentNodeName ]];
175- backgroundColor = [NSColor colorWithCalibratedRed: color.redComponent green: color.greenComponent blue: color.blueComponent alpha: kHighlightColorAlphaLevel ];
176- } else if (currentNodeId == [DVTSourceNodeTypes registerNodeTypeNamed: kXcodeSyntaxPreprocessorNodeName ])
177- {
178- NSColor *color = [[[DVTFontAndColorTheme currentTheme ] syntaxColorsByNodeType ] pointerAtIndex: [DVTSourceNodeTypes registerNodeTypeNamed: kXcodeSyntaxPreprocessorNodeName ]];
179- backgroundColor = [NSColor colorWithCalibratedRed: color.redComponent green: color.greenComponent blue: color.blueComponent alpha: kHighlightColorAlphaLevel ];
187+ currentNodeId == [DVTSourceNodeTypes registerNodeTypeNamed: kXcodeSyntaxCommentDocKeywordNodeName ]) {
188+ return @{NSForegroundColorAttributeName : [[DVTFontAndColorTheme currentTheme ] sourceTextBackgroundColor ], NSBackgroundColorAttributeName : self.commentColor };
189+ } else if (currentNodeId == [DVTSourceNodeTypes registerNodeTypeNamed: kXcodeSyntaxPreprocessorNodeName ]) {
190+ return @{NSForegroundColorAttributeName : [[DVTFontAndColorTheme currentTheme ] sourceTextBackgroundColor ], NSBackgroundColorAttributeName : self.preprocessorColor };
180191 }
181192
182- if (backgroundColor) {
183- NSColor *foregroundColor = [[DVTFontAndColorTheme currentTheme ] sourceTextBackgroundColor ];
184- return @{NSForegroundColorAttributeName : foregroundColor, NSBackgroundColorAttributeName : backgroundColor};
185- } else {
186- return @{NSForegroundColorAttributeName : color};
187- }
193+ return @{NSForegroundColorAttributeName : color};
188194}
189195
190- - (void )layoutManager : (NSLayoutManager *)layoutManager didCompleteLayoutForTextContainer : (NSTextContainer *)textContainer atEnd : (BOOL )layoutFinished
196+ - (void )layoutManager : (NSLayoutManager *)layoutManager didCompleteLayoutForTextContainer : (NSTextContainer *)textContainer atEnd : (BOOL )layoutFinishedFlag
191197{
192- if (layoutFinished) {
193- [self updateOffset ];
194- }
198+ self.shouldAllowFullSyntaxHighlight = NO ;
195199}
196200
197201#pragma mark - Navigation
198202
199203- (void )updateOffset
200204{
201- if ([ self isHidden ] ) {
205+ if (self. isHidden ) {
202206 return ;
203207 }
204208
205- CGFloat editorContentHeight = [self .editorScrollView.documentView frame ].size .height - self.editorScrollView .bounds .size .height ;
209+ CGFloat editorTextHeight = CGRectGetHeight ([self .editorTextView.layoutManager usedRectForTextContainer: self .editorTextView.textContainer]);
210+ CGFloat minimapTextHeight = CGRectGetHeight ([self .textView.layoutManager usedRectForTextContainer: self .textView.textContainer]);
211+
212+ CGFloat adjustedEditorContentHeight = editorTextHeight - CGRectGetHeight (self.editorScrollView .bounds );
213+ CGFloat adjustedMinimapContentHeight = minimapTextHeight - (CGRectGetHeight (self.scrollView .bounds ) * (1 / self.scrollView .magnification ));
206214
207215 NSRect selectionViewFrame = NSMakeRect (0 , 0 , self.bounds .size .width * (1 / self.scrollView .magnification ), self.editorScrollView .visibleRect .size .height );
208216
209- if (editorContentHeight == 0 .0f ) {
217+ if (adjustedEditorContentHeight == 0 .0f ) {
210218 [self .selectionView setFrame: selectionViewFrame];
211219 return ;
212220 }
213221
214- CGFloat ratio = (CGRectGetHeight ([self .scrollView.documentView frame ]) - CGRectGetHeight (self.scrollView .bounds ) * (1 / self.scrollView .magnification )) / editorContentHeight * (1 / self.scrollView .magnification );
215-
222+ CGFloat ratio = (adjustedMinimapContentHeight / adjustedEditorContentHeight) * (1 / self.scrollView .magnification );
216223 CGPoint offset = NSMakePoint (0 , MAX (0 , floorf (self.editorScrollView .contentView .bounds .origin .y * ratio * self.scrollView .magnification )));
224+
217225 [self .scrollView.documentView scrollPoint: offset];
218226
219- CGFloat textHeight = [ self .textView.layoutManager usedRectForTextContainer: self .textView.textContainer]. size . height ;
220- ratio = (textHeight - self.selectionView .bounds .size .height ) / editorContentHeight ;
227+
228+ ratio = (minimapTextHeight - self.selectionView .bounds .size .height ) / adjustedEditorContentHeight ;
221229 selectionViewFrame.origin .y = self.editorScrollView .contentView .bounds .origin .y * ratio;
222230
223231 [self .selectionView setFrame: selectionViewFrame];
@@ -245,7 +253,7 @@ - (void)handleMouseEvent:(NSEvent *)theEvent
245253{
246254 NSPoint point = [self .textView convertPoint: theEvent.locationInWindow fromView: nil ];
247255 NSUInteger characterIndex = [self .textView characterIndexForInsertionAtPoint: point];
248- [self .editorTextView scrollRangeToVisible: NSMakeRange (characterIndex, 0 )];
256+ [self .editorTextView scrollRangeToVisible: NSMakeRange (characterIndex, 0 ) animate: YES ];
249257}
250258
251259#pragma mark - Theme
@@ -263,6 +271,20 @@ - (void)updateTheme
263271 blue: (1 .0f - [backgroundColor blueComponent ])
264272 alpha: kHighlightColorAlphaLevel ];
265273
274+ DVTPointerArray *colors = [[DVTFontAndColorTheme currentTheme ] syntaxColorsByNodeType ];
275+ self.commentColor = [colors pointerAtIndex: [DVTSourceNodeTypes registerNodeTypeNamed: kXcodeSyntaxCommentNodeName ]];
276+ self.commentColor = [NSColor colorWithCalibratedRed: self .commentColor.redComponent
277+ green: self .commentColor.greenComponent
278+ blue: self .commentColor.blueComponent
279+ alpha: kHighlightColorAlphaLevel ];
280+
281+
282+ self.preprocessorColor = [colors pointerAtIndex: [DVTSourceNodeTypes registerNodeTypeNamed: kXcodeSyntaxPreprocessorNodeName ]];
283+ self.preprocessorColor = [NSColor colorWithCalibratedRed: self .commentColor.redComponent
284+ green: self .commentColor.greenComponent
285+ blue: self .commentColor.blueComponent
286+ alpha: kHighlightColorAlphaLevel ];
287+
266288 [self .selectionView setSelectionColor: selectionColor];
267289}
268290
@@ -274,75 +296,19 @@ - (void)resizeWithOldSuperviewSize:(NSSize)oldSize
274296 [self updateOffset ];
275297}
276298
277- @end
278-
279-
280- @implementation SCXcodeMinimapDelayedLayoutManager
281-
282- - (void )delayedAddOperation : (NSOperation *)operation {
283- [[NSOperationQueue currentQueue ] addOperation: operation];
284- }
285-
286- - (void )performBlock : (void (^)(void ))block afterDelay : (NSTimeInterval )delay {
287- [self performSelector: @selector (delayedAddOperation: )
288- withObject: [NSBlockOperation blockOperationWithBlock: block]
289- afterDelay: delay];
290- }
299+ #pragma mark - Helpers
291300
292301- (void )performBlock : (void (^)(void ))block afterDelay : (NSTimeInterval )delay cancelPreviousRequest : (BOOL )cancel {
293302 if (cancel) {
294303 [NSObject cancelPreviousPerformRequestsWithTarget: self ];
295304 }
296- [self performBlock: block afterDelay: delay];
297- }
298-
299- - (void )invalidateDisplayForCharacterRange : (NSRange )charRange
300- {
301- if (self.combinedRangeValue ) {
302- self.combinedRangeValue = [NSValue valueWithRange: NSUnionRange (self .combinedRangeValue.rangeValue, charRange)];
303- } else {
304- self.combinedRangeValue = [NSValue valueWithRange: charRange];
305- }
306-
307- [self performBlock: ^{
308-
309- NSRange range = NSIntersectionRange (self.combinedRangeValue .rangeValue , NSMakeRange (0 , self.textStorage .length ));
310- [super invalidateDisplayForCharacterRange: range];
311- self.combinedRangeValue = nil ;
312- } afterDelay: 0 .5f cancelPreviousRequest: YES ];
313- }
314-
315- - (void )_invalidateLayoutForExtendedCharacterRange : (NSRange )charRange isSoft : (BOOL )isSoft
316- {
317- if (isSoft) {
318- [super _invalidateLayoutForExtendedCharacterRange: charRange isSoft: isSoft];
319- }
320- }
321-
322- - (void )textStorage : (id )arg1 edited : (unsigned long long )arg2 range : (struct _NSRange)arg3 changeInLength : (long long )arg4 invalidatedRange : (struct _NSRange)arg5
323- {
324305
325- }
326-
327- @end
328-
329-
330- @implementation NSObject (SCXcodeMinimapDelayedLayoutManager)
331-
332- - (void )sc_performBlock : (void (^)(void ))block afterDelay : (NSTimeInterval )delay {
333306 [self performSelector: @selector (delayedAddOperation: )
334307 withObject: [NSBlockOperation blockOperationWithBlock: block]
335308 afterDelay: delay];
336309}
337310
338- - (void )sc_performBlock : (void (^)(void ))block afterDelay : (NSTimeInterval )delay cancelPreviousRequest : (BOOL )cancel {
339- if (cancel) {
340- [NSObject cancelPreviousPerformRequestsWithTarget: self ];
341- }
342- [self sc_performBlock: block afterDelay: delay];
343- }
344-
345- - (void )sc_delayedAddOperation : (NSOperation *)operation {
311+ - (void )delayedAddOperation : (NSOperation *)operation {
346312 [[NSOperationQueue currentQueue ] addOperation: operation];
347313}
348314
0 commit comments