forked from robbiehanson/XcodeColors
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathXcodeColors.m
368 lines (291 loc) · 11.7 KB
/
XcodeColors.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
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
//
// XcodeColors.m
// XcodeColors
//
// Created by Uncle MiF on 9/13/10.
// Copyright 2010 Deep IT. All rights reserved.
//
#import "XcodeColors.h"
#import <objc/runtime.h>
#define XCODE_COLORS "XcodeColors"
// How to apply color formatting to your log statements:
//
// To set the foreground color:
// Insert the ESCAPE_SEQ into your string, followed by "fg124,12,255;" where r=124, g=12, b=255.
//
// To set the background color:
// Insert the ESCAPE_SEQ into your string, followed by "bg12,24,36;" where r=12, g=24, b=36.
//
// To reset the foreground color (to default value):
// Insert the ESCAPE_SEQ into your string, followed by "fg;"
//
// To reset the background color (to default value):
// Insert the ESCAPE_SEQ into your string, followed by "bg;"
//
// To reset the foreground and background color (to default values) in one operation:
// Insert the ESCAPE_SEQ into your string, followed by ";"
//
//
// Feel free to copy the define statements below into your code.
// <COPY ME>
#define XCODE_COLORS_ESCAPE @"\033["
#define XCODE_COLORS_RESET_FG XCODE_COLORS_ESCAPE @"fg;" // Clear any foreground color
#define XCODE_COLORS_RESET_BG XCODE_COLORS_ESCAPE @"bg;" // Clear any background color
#define XCODE_COLORS_RESET XCODE_COLORS_ESCAPE @";" // Clear any foreground or background color
// </COPY ME>
static IMP IMP_NSTextStorage_fixAttributesInRange = nil;
@implementation XcodeColors_NSTextStorage
void ApplyANSIColors(NSTextStorage *textStorage, NSRange textStorageRange, NSString *escapeSeq)
{
NSRange range = [[textStorage string] rangeOfString:escapeSeq options:0 range:textStorageRange];
if (range.location == NSNotFound)
{
// No escape sequence(s) in the string.
return;
}
// Architecture:
//
// We're going to split the string into components separated by the given escape sequence.
// Then we're going to loop over the components, looking for color codes at the beginning of each component.
//
// For example, if the input string is "Hello__fg124,12,12;World" (with __ representing the escape sequence),
// then our components would be: (
// "Hello"
// "fg124,12,12;World"
// )
//
// We would find a color code (rgb 124, 12, 12) for the foreground color in the second component.
// At that point, the parsed foreground color goes into an attributes dictionary (attrs),
// and the foreground color stays in effect (for the current component & future components)
// until it gets cleared via a different foreground color, or a clear sequence.
//
// The attributes are applied to the entire range of the component, and then we move onto the next component.
//
// At the very end, we go back and apply "invisible" attributes (zero font, and clear text)
// to the escape and color sequences.
NSString *affectedString = [[textStorage string] substringWithRange:textStorageRange];
// Split the string into components separated by the given escape sequence.
NSArray *components = [affectedString componentsSeparatedByString:escapeSeq];
NSRange componentRange = textStorageRange;
componentRange.length = 0;
BOOL firstPass = YES;
NSMutableArray *seqRanges = [NSMutableArray arrayWithCapacity:[components count]];
NSMutableDictionary *attrs = [NSMutableDictionary dictionaryWithCapacity:2];
for (NSString *component in components)
{
if (firstPass)
{
// The first component in the array won't need processing.
// If there was an escape sequence at the very beginning of the string,
// then the first component in the array will be an empty string.
// Otherwise the first component is everything before the first escape sequence.
}
else
{
// componentSeqRange : Range of escape sequence within component, e.g. "fg124,12,12;"
NSColor *color = nil;
NSUInteger colorCodeSeqLength = 0;
BOOL stop = NO;
BOOL reset = !stop && (stop = [component hasPrefix:@";"]);
BOOL fg = !stop && (stop = [component hasPrefix:@"fg"]);
BOOL bg = !stop && (stop = [component hasPrefix:@"bg"]);
BOOL resetFg = fg && [component hasPrefix:@"fg;"];
BOOL resetBg = bg && [component hasPrefix:@"bg;"];
if (reset)
{
// Reset attributes
[attrs removeObjectForKey:NSForegroundColorAttributeName];
[attrs removeObjectForKey:NSBackgroundColorAttributeName];
// Mark the range of the sequence (escape sequence + reset color sequence).
NSRange seqRange = (NSRange){
.location = componentRange.location - [escapeSeq length],
.length = 1 + [escapeSeq length],
};
[seqRanges addObject:[NSValue valueWithRange:seqRange]];
}
else if (resetFg || resetBg)
{
// Reset attributes
if (resetFg)
[attrs removeObjectForKey:NSForegroundColorAttributeName];
else
[attrs removeObjectForKey:NSBackgroundColorAttributeName];
// Mark the range of the sequence (escape sequence + reset color sequence).
NSRange seqRange = (NSRange){
.location = componentRange.location - [escapeSeq length],
.length = 3 + [escapeSeq length],
};
[seqRanges addObject:[NSValue valueWithRange:seqRange]];
}
else if (fg || bg)
{
// Looking for something like this: "fg124,22,12;" or "bg17,24,210;".
// These are the rgb values for the foreground or background.
NSString *str_r = nil;
NSString *str_g = nil;
NSString *str_b = nil;
NSRange range_search = NSMakeRange(2, MIN(4, [component length] - 2));
NSRange range_separator;
NSRange range_value;
// Search for red separator
range_separator = [component rangeOfString:@"," options:0 range:range_search];
if (range_separator.location != NSNotFound)
{
// Extract red substring
range_value.location = range_search.location;
range_value.length = range_separator.location - range_search.location;
str_r = [component substringWithRange:range_value];
// Update search range
range_search.location = range_separator.location + range_separator.length;
range_search.length = MIN(4, [component length] - range_search.location);
// Search for green separator
range_separator = [component rangeOfString:@"," options:0 range:range_search];
if (range_separator.location != NSNotFound)
{
// Extract green substring
range_value.location = range_search.location;
range_value.length = range_separator.location - range_search.location;
str_g = [component substringWithRange:range_value];
// Update search range
range_search.location = range_separator.location + range_separator.length;
range_search.length = MIN(4, [component length] - range_search.location);
// Search for blue separator
range_separator = [component rangeOfString:@";" options:0 range:range_search];
if (range_separator.location != NSNotFound)
{
// Extract blue substring
range_value.location = range_search.location;
range_value.length = range_separator.location - range_search.location;
str_b = [component substringWithRange:range_value];
// Mark the length of the entire color code sequence.
colorCodeSeqLength = range_separator.location + range_separator.length;
}
}
}
if (str_r && str_g && str_b)
{
// Parse rgb values and create color
int r = MAX(0, MIN(255, [str_r intValue]));
int g = MAX(0, MIN(255, [str_g intValue]));
int b = MAX(0, MIN(255, [str_b intValue]));
color = [NSColor colorWithCalibratedRed:(r/255.0)
green:(g/255.0)
blue:(b/255.0)
alpha:1.0];
if (fg)
{
[attrs setObject:color forKey:NSForegroundColorAttributeName];
}
else
{
[attrs setObject:color forKey:NSBackgroundColorAttributeName];
}
//NSString *realString = [component substringFromIndex:colorCodeSeqLength];
// Mark the range of the entire sequence (escape sequence + color code sequence).
NSRange seqRange = (NSRange){
.location = componentRange.location - [escapeSeq length],
.length = colorCodeSeqLength + [escapeSeq length],
};
[seqRanges addObject:[NSValue valueWithRange:seqRange]];
}
else
{
// Wasn't able to parse a color code
[attrs removeObjectForKey:NSForegroundColorAttributeName];
[attrs removeObjectForKey:NSBackgroundColorAttributeName];
NSRange seqRange = (NSRange){
.location = componentRange.location - [escapeSeq length],
.length = [escapeSeq length],
};
[seqRanges addObject:[NSValue valueWithRange:seqRange]];
}
}
}
componentRange.length = [component length];
[textStorage addAttributes:attrs range:componentRange];
componentRange.location += componentRange.length + [escapeSeq length];
firstPass = NO;
} // END: for (NSString *component in components)
// Now loop over all the discovered sequences, and apply "invisible" attributes to them.
if ([seqRanges count] > 0)
{
NSDictionary *clearAttrs =
[NSDictionary dictionaryWithObjectsAndKeys:
[NSFont systemFontOfSize:0.001], NSFontAttributeName,
[NSColor clearColor], NSForegroundColorAttributeName, nil];
for (NSValue *seqRangeValue in seqRanges)
{
NSRange seqRange = [seqRangeValue rangeValue];
[textStorage addAttributes:clearAttrs range:seqRange];
}
}
}
- (void)fixAttributesInRange:(NSRange)aRange
{
// This method "overrides" the method within NSTextStorage.
// First we invoke the actual NSTextStorage method.
// This allows it to do any normal processing.
IMP_NSTextStorage_fixAttributesInRange(self, _cmd, aRange);
// Then we scan for our special escape sequences, and apply desired color attributes.
char *xcode_colors = getenv(XCODE_COLORS);
if (xcode_colors && (strcmp(xcode_colors, "YES") == 0))
{
ApplyANSIColors(self, aRange, XCODE_COLORS_ESCAPE);
}
}
@end
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark -
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
@implementation XcodeColors
IMP ReplaceInstanceMethod(Class sourceClass, SEL sourceSel, Class destinationClass, SEL destinationSel)
{
if (!sourceSel || !sourceClass || !destinationClass)
{
NSLog(@"XcodeColors: Missing parameter to ReplaceInstanceMethod");
return nil;
}
if (!destinationSel)
destinationSel = sourceSel;
Method sourceMethod = class_getInstanceMethod(sourceClass, sourceSel);
if (!sourceMethod)
{
NSLog(@"XcodeColors: Unable to get sourceMethod");
return nil;
}
IMP prevImplementation = method_getImplementation(sourceMethod);
Method destinationMethod = class_getInstanceMethod(destinationClass, destinationSel);
if (!destinationMethod)
{
NSLog(@"XcodeColors: Unable to get destinationMethod");
return nil;
}
IMP newImplementation = method_getImplementation(destinationMethod);
if (!newImplementation)
{
NSLog(@"XcodeColors: Unable to get newImplementation");
return nil;
}
method_setImplementation(sourceMethod, newImplementation);
return prevImplementation;
}
+ (void)load
{
NSLog(@"XcodeColors: %@ (v10.1)", NSStringFromSelector(_cmd));
char *xcode_colors = getenv(XCODE_COLORS);
if (xcode_colors && (strcmp(xcode_colors, "YES") != 0))
return;
IMP_NSTextStorage_fixAttributesInRange =
ReplaceInstanceMethod([NSTextStorage class], @selector(fixAttributesInRange:),
[XcodeColors_NSTextStorage class], @selector(fixAttributesInRange:));
setenv(XCODE_COLORS, "YES", 0);
}
+ (void)pluginDidLoad:(id)xcodeDirectCompatibility
{
NSLog(@"XcodeColors: %@", NSStringFromSelector(_cmd));
}
- (void)registerLaunchSystemDescriptions
{
NSLog(@"XcodeColors: %@", NSStringFromSelector(_cmd));
}
@end