Skip to content

Commit a5b21db

Browse files
committed
Breakpoint KVO fixes.
1 parent 1606fcc commit a5b21db

File tree

3 files changed

+219
-15
lines changed

3 files changed

+219
-15
lines changed

SCXcodeMinimap/IDEBreakpointManager+SCXcodeMinimap.h

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,23 @@
88

99
#import "IDEBreakpointManager.h"
1010

11+
typedef void (^SCKVONotificationBlock)(id object, NSDictionary *change);
12+
13+
@interface SCKVOController : NSObject
14+
15+
- (void)observeObject:(id)object forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options block:(SCKVONotificationBlock)block;
16+
- (void)unobserveObject:(id)object forKeyPath:(NSString *)keyPath;
17+
- (void)unobserveObject:(id)object;
18+
- (void)unobserveAll;
19+
20+
@end
21+
1122
@protocol IDEBreakpointManagerDelegate;
1223

1324
@interface IDEBreakpointManager (SCXcodeMinimap)
1425

1526
@property (nonatomic, weak) id<IDEBreakpointManagerDelegate> delegate;
27+
@property (nonatomic, strong) SCKVOController *kvoController;
1628

1729
@end
1830

SCXcodeMinimap/IDEBreakpointManager+SCXcodeMinimap.m

Lines changed: 198 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,6 @@
1010
#import "IDEFileBreakpoint.h"
1111
#import <objc/runtime.h>
1212

13-
static void *SCXcodeMinimapBreakpointObserverContext = &SCXcodeMinimapBreakpointObserverContext;
14-
1513
@implementation IDEBreakpointManager (SCXcodeMinimap)
1614

1715
static void sc_swizzleInstanceMethod(Class class, SEL originalSelector, SEL swizzledSelector) {
@@ -26,6 +24,7 @@ static void sc_swizzleInstanceMethod(Class class, SEL originalSelector, SEL swiz
2624

2725
+ (void)load
2826
{
27+
sc_swizzleInstanceMethod(self, @selector(_handleWorkspaceContainerRemoved:), @selector(sc_handleWorkspaceContainerRemoved:));
2928
sc_swizzleInstanceMethod(self, @selector(addBreakpoint:), @selector(sc_addBreakpoint:));
3029
sc_swizzleInstanceMethod(self, @selector(removeBreakpoint:), @selector(sc_removeBreakpoint:));
3130
}
@@ -40,14 +39,39 @@ - (void)setDelegate:(id<IDEBreakpointManagerDelegate>)delegate
4039
return objc_getAssociatedObject(self, @selector(delegate));
4140
}
4241

42+
- (void)setKvoController:(SCKVOController *)kvoController
43+
{
44+
objc_setAssociatedObject(self, @selector(kvoController), kvoController, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
45+
}
46+
47+
- (SCKVOController *)kvoController
48+
{
49+
SCKVOController *kvoController = objc_getAssociatedObject(self, @selector(kvoController));
50+
51+
if(kvoController == nil) {
52+
kvoController = [[SCKVOController alloc] init];
53+
[self setKvoController:kvoController];
54+
55+
for(IDEBreakpoint *breakpoint in self.breakpoints) {
56+
if([breakpoint isKindOfClass:[IDEFileBreakpoint class]]) {
57+
[self _observeBreakpoint:breakpoint];
58+
}
59+
}
60+
}
61+
62+
return kvoController;
63+
}
64+
65+
- (void)sc_handleWorkspaceContainerRemoved:(id)arg1
66+
{
67+
[self.kvoController unobserveAll];
68+
}
69+
4370
- (void)sc_addBreakpoint:(IDEBreakpoint *)breakpoint
4471
{
4572
[self sc_addBreakpoint:breakpoint];
4673

47-
if([breakpoint isKindOfClass:[IDEFileBreakpoint class]]) {
48-
[breakpoint addObserver:self forKeyPath:@"location" options:NSKeyValueObservingOptionNew context:SCXcodeMinimapBreakpointObserverContext];
49-
[breakpoint addObserver:self forKeyPath:@"shouldBeEnabled" options:NSKeyValueObservingOptionNew context:SCXcodeMinimapBreakpointObserverContext];
50-
}
74+
[self _observeBreakpoint:breakpoint];
5175

5276
if([self.delegate respondsToSelector:@selector(breakpointManagerDidAddBreakpoint:)]) {
5377
[self.delegate breakpointManagerDidAddBreakpoint:self];
@@ -56,10 +80,7 @@ - (void)sc_addBreakpoint:(IDEBreakpoint *)breakpoint
5680

5781
- (void)sc_removeBreakpoint:(IDEBreakpoint *)breakpoint
5882
{
59-
if([breakpoint isKindOfClass:[IDEFileBreakpoint class]]) {
60-
[breakpoint removeObserver:self forKeyPath:@"location" context:SCXcodeMinimapBreakpointObserverContext];
61-
[breakpoint removeObserver:self forKeyPath:@"shouldBeEnabled" context:SCXcodeMinimapBreakpointObserverContext];
62-
}
83+
[self.kvoController unobserveObject:breakpoint];
6384

6485
[self sc_removeBreakpoint:breakpoint];
6586

@@ -68,13 +89,178 @@ - (void)sc_removeBreakpoint:(IDEBreakpoint *)breakpoint
6889
}
6990
}
7091

71-
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
92+
- (void)_observeBreakpoint:(IDEBreakpoint *)breakpoint
7293
{
73-
if(context == SCXcodeMinimapBreakpointObserverContext) {
94+
[self.kvoController observeObject:breakpoint forKeyPath:@"location" options:NSKeyValueObservingOptionNew block:^(id object, NSDictionary *change) {
95+
if([self.delegate respondsToSelector:@selector(breakpointManagerDidChangeBreakpoint:)]) {
96+
[self.delegate breakpointManagerDidChangeBreakpoint:self];
97+
}
98+
}];
99+
100+
[self.kvoController observeObject:breakpoint forKeyPath:@"shouldBeEnabled" options:NSKeyValueObservingOptionNew block:^(id object, NSDictionary *change) {
74101
if([self.delegate respondsToSelector:@selector(breakpointManagerDidChangeBreakpoint:)]) {
75102
[self.delegate breakpointManagerDidChangeBreakpoint:self];
76103
}
104+
}];
105+
}
106+
107+
@end
108+
109+
110+
@interface SCKVOKeyPathInfo : NSObject
111+
112+
@property (nonatomic, strong) NSMutableSet *blocks;
113+
114+
@end
115+
116+
117+
@interface SCKVOInfo : NSObject
118+
119+
@property (nonatomic, weak) id observer;
120+
@property (nonatomic, strong) NSMutableDictionary *keyPathInfos;
121+
122+
- (instancetype)initWithObserver:(id)observer;
123+
124+
@end
125+
126+
127+
@interface SCKVOController()
128+
129+
@property (nonatomic, strong) NSMapTable *mapTable;
130+
131+
@end
132+
133+
134+
@implementation SCKVOController
135+
136+
- (void)dealloc
137+
{
138+
[self unobserveAll];
139+
}
140+
141+
- (void)observeObject:(id)object forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options block:(SCKVONotificationBlock)block
142+
{
143+
NSParameterAssert(object);
144+
NSParameterAssert(keyPath);
145+
NSParameterAssert(block);
146+
147+
SCKVOInfo *info = [self.mapTable objectForKey:object];
148+
149+
if (!info) {
150+
info = [[SCKVOInfo alloc] initWithObserver:object];
151+
[self.mapTable setObject:info forKey:object];
152+
}
153+
154+
SCKVOKeyPathInfo *kvoKeyPathInfo = [info.keyPathInfos objectForKey:keyPath];
155+
156+
BOOL registerObserver = NO;
157+
158+
if (!kvoKeyPathInfo) {
159+
kvoKeyPathInfo = [[SCKVOKeyPathInfo alloc] init];
160+
[info.keyPathInfos setValue:kvoKeyPathInfo forKey:keyPath];
161+
registerObserver = YES;
162+
}
163+
164+
[kvoKeyPathInfo.blocks addObject:[block copy]];
165+
166+
if (registerObserver) {
167+
[object addObserver:self forKeyPath:keyPath options:options context:NULL];
168+
}
169+
}
170+
171+
- (void)unobserveObject:(id)object forKeyPath:(NSString *)keyPath
172+
{
173+
NSParameterAssert(object);
174+
NSParameterAssert(keyPath);
175+
176+
SCKVOInfo *info = [self.mapTable objectForKey:object];
177+
178+
if (info) {
179+
if (info.keyPathInfos[keyPath]) {
180+
[info.keyPathInfos removeObjectForKey:keyPath];
181+
[object removeObserver:self forKeyPath:keyPath context:NULL];
182+
183+
if (info.keyPathInfos.count == 0) {
184+
[self.mapTable removeObjectForKey:object];
185+
}
186+
}
187+
}
188+
}
189+
190+
- (void)unobserveObject:(id)object
191+
{
192+
NSParameterAssert(object);
193+
194+
SCKVOInfo *info = [self.mapTable objectForKey:object];
195+
if (info) {
196+
for (NSString *keyPath in [info.keyPathInfos allKeys]) {
197+
[self unobserveObject:object forKeyPath:keyPath];
198+
}
199+
}
200+
}
201+
202+
- (void)unobserveAll
203+
{
204+
NSMapTable *mapTable = [self.mapTable copy];
205+
206+
for (id object in mapTable) {
207+
[self unobserveObject:object];
208+
}
209+
}
210+
211+
- (NSMapTable *)mapTable
212+
{
213+
if (!_mapTable) {
214+
_mapTable = [NSMapTable mapTableWithKeyOptions:NSPointerFunctionsWeakMemory|NSPointerFunctionsObjectPointerPersonality valueOptions:NSPointerFunctionsStrongMemory|NSPointerFunctionsObjectPersonality];
215+
}
216+
217+
return _mapTable;
218+
}
219+
220+
- (void)observeValueForKeyPath:(NSString *)keyPath
221+
ofObject:(id)object
222+
change:(NSDictionary *)change
223+
context:(void *)context
224+
{
225+
SCKVOInfo *info = [self.mapTable objectForKey:object];
226+
227+
if (info) {
228+
SCKVOKeyPathInfo *kvoKeyPathInfo = [info.keyPathInfos objectForKey:keyPath];
229+
230+
if (kvoKeyPathInfo) {
231+
for (SCKVONotificationBlock block in kvoKeyPathInfo.blocks) {
232+
block(object, change);
233+
}
234+
}
235+
}
236+
}
237+
238+
@end
239+
240+
241+
@implementation SCKVOKeyPathInfo
242+
243+
- (instancetype)init
244+
{
245+
if (self = [super init]) {
246+
self.blocks = [NSMutableSet set];
247+
}
248+
249+
return self;
250+
}
251+
252+
@end
253+
254+
255+
@implementation SCKVOInfo
256+
257+
- (instancetype)initWithObserver:(id)observer
258+
{
259+
if (self = [super init]) {
260+
self.observer = observer;
261+
self.keyPathInfos = [NSMutableDictionary dictionary];
77262
}
263+
return self;
78264
}
79265

80266
@end

SCXcodeMinimap/SCXcodeMinimapView.m

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -228,6 +228,7 @@ - (NSDictionary *)layoutManager:(NSLayoutManager *)layoutManager
228228

229229
// Delay invalidation for performance reasons and attempt a full range invalidation later
230230
if(!self.shouldAllowFullSyntaxHighlight && ![layoutManager isEqual:self.editorTextView.layoutManager]) {
231+
231232
[NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(invalidateDisplayForVisibleMinimapRange) object:nil];
232233
[self performSelector:@selector(invalidateDisplayForVisibleMinimapRange) withObject:nil afterDelay:kDurationBetweenInvalidations];
233234

@@ -303,7 +304,7 @@ - (void)foldingManager:(DVTFoldingManager *)foldingManager didUnfoldRange:(NSRan
303304
[self invalidateLayoutForVisibleMinimapRange];
304305
}
305306

306-
#pragma makr - IDEBreakpointManagerDelegate
307+
#pragma mark - IDEBreakpointManagerDelegate
307308

308309
- (void)breakpointManagerDidAddBreakpoint:(IDEBreakpointManager *)breakpointManager
309310
{
@@ -342,7 +343,7 @@ - (void)updateBreakpoints
342343

343344
if([fileBreakpoint.documentURL isEqualTo:self.editor.document.fileURL] && fileBreakpoint.location.startingLineNumber == lineNumber) {
344345
[self.breakpointDictionaries addObject:@{kBreakpointRangeKey : [NSValue valueWithRange:lineRange],
345-
kBreakpointEnabledKey : @(fileBreakpoint.shouldBeEnabled)}];
346+
kBreakpointEnabledKey : @(fileBreakpoint.shouldBeEnabled && breakpointManager.breakpointsActivated)}];
346347
}
347348
}
348349
}
@@ -351,6 +352,11 @@ - (void)updateBreakpoints
351352
}
352353

353354
[self invalidateDisplayForVisibleMinimapRange];
355+
356+
BOOL editorHighlightingEnabled = [[[NSUserDefaults standardUserDefaults] objectForKey:SCXcodeMinimapShouldHighlightEditorKey] boolValue];
357+
if(editorHighlightingEnabled) {
358+
[self invalidateDisplayForVisibleEditorRange];
359+
}
354360
}
355361

356362
#pragma mark - Navigation
@@ -417,7 +423,6 @@ - (void)updateTheme
417423
{
418424
self.editorTheme = [self minimapThemeWithTheme:[DVTFontAndColorTheme currentTheme]];
419425

420-
421426
DVTPreferenceSetManager *preferenceSetManager = [DVTFontAndColorTheme preferenceSetsManager];
422427
NSArray *preferenceSet = [preferenceSetManager availablePreferenceSets];
423428

@@ -487,6 +492,7 @@ - (void)resizeWithOldSuperviewSize:(NSSize)oldSize
487492
- (void)invalidateDisplayForVisibleMinimapRange
488493
{
489494
self.shouldAllowFullSyntaxHighlight = YES;
495+
490496
NSRange visibleMinimapRange = [self.textView visibleCharacterRange];
491497
[self.textView.layoutManager invalidateDisplayForCharacterRange:visibleMinimapRange];
492498
}

0 commit comments

Comments
 (0)