forked from jerrykrinock/CategoriesObjC
-
Notifications
You must be signed in to change notification settings - Fork 0
/
GCUndoManager+Debug.m
285 lines (238 loc) · 7.76 KB
/
GCUndoManager+Debug.m
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
#import "GCUndoManager+Debug.h"
#import <objc/runtime.h>
#import "NSObject+MoreDescriptions.h"
#if GCUNDOMANAGER_DEBUG
// Note: You get the ivar offset from running
// otool -ov "/System/Library/Frameworks/AppKit.framework/AppKit" > ~/Desktop/AppKitClassDump.txt
// and searching the result
#define _CHANGE_COUNT_IVAR_OFFSET 0x00000018
@interface NSDocument (SSYDebugChangeAndUndo)
@end
@implementation NSDocument (SSYDebugChangeAndUndo)
- (NSInteger)changeCount {
// Must cast pointers to integers before adding or they
// get multiplied by sizeof() their type.
return *(NSInteger*)((NSInteger)self + (NSInteger)_CHANGE_COUNT_IVAR_OFFSET) ;
}
+ (void)load {
// Swap the implementations of one method with another.
// When the message Xxx is sent to the object (either instance or class),
// replacement_Xxx will be invoked instead. Conversely,
// replacement_Xxx will invoke Xxx.
// NOTE: Below, use class_getInstanceMethod or class_getClassMethod as appropriate!!
Method originalMethod = class_getInstanceMethod(self, @selector(updateChangeCount:)) ;
Method replacedMethod = class_getInstanceMethod(self, @selector(replacement_updateChangeCount:)) ;
method_exchangeImplementations(originalMethod, replacedMethod) ;
NSLog(@"Replaced -updateChangeCount in %@ for debugging.", [self class]) ;
}
- (void)replacement_updateChangeCount:(NSDocumentChangeType)changeType {
NSInteger oldChangeCount = [self changeCount] ;
// Due to the swap, this calls the original method
[self replacement_updateChangeCount:changeType] ;
NSInteger newChangeCount = [self changeCount] ;
NSString* changeDesc ;
switch (changeType) {
case NSChangeDone:
changeDesc = @"Do" ;
break;
case NSChangeUndone:
changeDesc = @"Undo" ;
break;
case NSChangeCleared:
changeDesc = @"Clear" ;
break;
case NSChangeReadOtherContents:
changeDesc = @"ReadOther" ;
break;
case NSChangeAutosaved:
changeDesc = @"Autosave" ;
break;
case NSChangeRedone:
changeDesc = @"Redo" ;
break;
default:
changeDesc = @"Undef" ;
break;
}
NSLog(@"Did change type %@. changeCount: %d:%d",
changeDesc,
oldChangeCount,
newChangeCount) ;
}
@end
@interface GCUndoTask (Debug)
- (NSInteger)depth ;
@end
@implementation GCUndoTask (Debug)
- (NSString*)lineage {
GCUndoTask* parent = self ;
NSMutableString* lineage = [NSMutableString stringWithString:@"-(self)"] ;
do {
NSString* s = [NSString stringWithFormat:@"-%p", parent] ;
[lineage insertString:s
atIndex:0] ;
} while ((parent = [parent parentGroup]) != nil) ;
[lineage insertString:@"(root ancestor)"
atIndex:0] ;
return [NSString stringWithString:lineage] ;
}
- (NSInteger)depth {
NSInteger depth = 0 ;
GCUndoTask* parent = self ;
while ((parent = [parent parentGroup]) != nil) {
depth++ ;
}
return depth ;
}
@end
@implementation GCConcreteUndoTask (Debug)
- (NSString*)descriptionWithSelector:(SEL)selector {
NSString* targetClause ;
if (selector == @selector(shortDescription)) {
targetClause = [[self target] className] ;
}
else {
// The invocation's longDescription will show the target,
// so we don't want to show it twice
targetClause = @"" ;
}
return [NSString stringWithFormat:@"<%@ %p %@%p> invocn=%@>",
[self className],
self,
targetClause,
[self target],
[mInvocation performSelector:selector]] ;
}
- (NSString*)shortDescription {
return [self descriptionWithSelector:@selector(shortDescription)] ;
}
- (NSString*)longDescription {
return [self descriptionWithSelector:@selector(longDescription)] ;
}
@end
@implementation GCUndoGroup (Debug)
- (NSString*)descriptionWithSelector:(SEL)selector {
NSInteger count = [mTasks count] ;
NSInteger depth = [self depth] ;
NSMutableString* indentation = [NSMutableString string] ;
NSInteger i ;
for (i=0; i<depth; i++) {
[indentation appendString:@" "] ;
}
NSMutableString* ms = [NSMutableString stringWithFormat:
@"<%@ %p actionName='%@' %d tasks at depth %d",
[self className],
self,
[self actionName],
count,
depth] ;
for (i=0; i<count; i++) {
[ms appendFormat:
@"\n%@%p's Task %d/%d: %@",
indentation,
self,
i,
count,
[[self taskAtIndex:i] performSelector:selector]] ;
}
[ms appendString:@"\n>"] ;
return [NSString stringWithString:ms] ;
}
- (NSString*)shortDescription {
return [self descriptionWithSelector:@selector(shortDescription)] ;
}
- (NSString*)longDescription {
return [self descriptionWithSelector:@selector(longDescription)] ;
}
@end
@implementation GCUndoManager (SSYDebug)
+ (void)load {
// Swap the implementations of methods.
// When the message Xxx is sent to the object (either instance or class),
// replacement_Xxx will be invoked instead. Conversely,
// replacement_Xxx will invoke Xxx.
// NOTE: Below, use class_getInstanceMethod or class_getClassMethod as appropriate!!
Method originalMethod ;
Method replacedMethod ;
originalMethod = class_getInstanceMethod(self, @selector(beginUndoGrouping)) ;
replacedMethod = class_getInstanceMethod(self, @selector(replacement_beginUndoGrouping)) ;
method_exchangeImplementations(originalMethod, replacedMethod) ;
originalMethod = class_getInstanceMethod(self, @selector(endUndoGrouping)) ;
replacedMethod = class_getInstanceMethod(self, @selector(replacement_endUndoGrouping)) ;
method_exchangeImplementations(originalMethod, replacedMethod) ;
originalMethod = class_getInstanceMethod(self, @selector(removeAllActions)) ;
replacedMethod = class_getInstanceMethod(self, @selector(replacement_removeAllActions)) ;
method_exchangeImplementations(originalMethod, replacedMethod) ;
NSLog(@"Replaced some methods in %@ for debugging.", [self class]) ;
}
- (NSString*)stateDescription {
NSString* desc ;
switch ([self undoManagerState]) {
case kGCUndoCollectingTasks:
desc = @"Collecting" ;
break;
case kGCUndoIsUndoing:
desc = @"Undoing" ;
break;
case kGCUndoIsRedoing:
desc = @"Redoing" ;
break;
default:
desc = @"Undef" ;
break;
}
return desc ;
}
- (NSDocument*)myDocument {
NSArray* documents = [[NSDocumentController sharedDocumentController] documents] ;
NSDocument* document = nil ;
for (document in documents) {
if ((GCUndoManager*)[document undoManager] == self) {
break ;
}
}
return document ;
}
// The undo manager state and grouping level are now exposed, thanks to GCUndoManager :)
- (void)replacement_beginUndoGrouping {
NSInteger oldGroupingLevel = [self groupingLevel] ;
NSString* oldStateDescription = [self stateDescription] ;
NSInteger oldChangeCount = [[self myDocument] changeCount] ;
// Due to the swap, this calls the original method
[self replacement_beginUndoGrouping] ;
NSLog(@"began undo grp: grpLvl: %d:%d state: %@:%@ chgCnt: %d:%d",
oldGroupingLevel,
[self groupingLevel],
oldStateDescription,
[self stateDescription],
oldChangeCount,
[[self myDocument] changeCount]
) ;
}
- (void)replacement_endUndoGrouping {
NSInteger oldGroupingLevel = [self groupingLevel] ;
NSString* oldStateDescription = [self stateDescription] ;
NSInteger oldChangeCount = [[self myDocument] changeCount] ;
// Due to the swap, this calls the original method
[self replacement_endUndoGrouping] ;
NSLog(@"ended undo grp: grpLvl: %d:%d state: %@:%@ chgCnt: %d:%d",
oldGroupingLevel,
[self groupingLevel],
oldStateDescription,
[self stateDescription],
oldChangeCount,
[[self myDocument] changeCount]
) ;
}
- (void)replacement_removeAllActions {
NSLog(@">>> %s", __PRETTY_FUNCTION__) ;
// Due to the swap, this calls the original method
[self replacement_removeAllActions] ;
NSLog(@"<<< %s", __PRETTY_FUNCTION__) ;
}
- (void)logPeekUndo {
NSString* desc = [[self peekUndo] longDescription] ;
NSLog(@"*** Begin logPeekUndo. Top Undo Group: %@*** End logPeekUndo", desc) ;
}
@end
#endif