Skip to content

Commit d73259c

Browse files
committed
Trying out new approach
- allow the layout manager to only invalidate the visible editor range - force an invalidation on the visible minimap range afterwards
1 parent b3fe714 commit d73259c

File tree

2 files changed

+79
-113
lines changed

2 files changed

+79
-113
lines changed

SCXcodeMinimap/SCXcodeMinimap.m

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
#import "IDESourceCodeEditor.h"
1414
#import "DVTSourceTextView.h"
1515

16-
const CGFloat kDefaultZoomLevel = 0.15f;
16+
const CGFloat kDefaultZoomLevel = 0.1f;
1717

1818
static NSString * const IDESourceCodeEditorDidFinishSetupNotification = @"IDESourceCodeEditorDidFinishSetup";
1919

SCXcodeMinimap/SCXcodeMinimapView.m

Lines changed: 78 additions & 112 deletions
Original file line numberDiff line numberDiff line change
@@ -32,21 +32,6 @@
3232
static NSString * const IDESourceCodeEditorTextViewBoundsDidChangeNotification = @"IDESourceCodeEditorTextViewBoundsDidChangeNotification";
3333
static 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

Comments
 (0)