robbiehanson / XcodeColors

XcodeColors allows you to use colors in the Xcode debugging console. It's designed to aid in the debugging process.
2.23k stars 264 forks source link

Xcode 10 seems to brake the plugin completely #93

Open gklka opened 6 years ago

gklka commented 6 years ago

Has anyone luck with Xcode 10?

basvankuijck commented 6 years ago

No, no luck whatsoever. usigning Xcode and adding the uuid in the plugin worked on Xcode9. But Xcode10 (the GM at least) is not working as I was hoping it would.

sunnyyoung commented 6 years ago

Yes, the same issue here, with unsigned Xcode 10 Release version. 💔

tanranran commented 6 years ago

me too Xcode Version 10.0 (10A255)

absolutlabs commented 6 years ago

actually it's still working in the Report navigator > debug session , but less useful than directly in the console :/

sbwilson commented 6 years ago

I wonder whether the console view's base class has changed. I spent a little time playing with this tonight, and can't get the new console to trigger the method [NSTextStorage fixAttributesInRange:]. From digging through the class-dump'ed source for a variety of the frameworks. The most obvious (found in IDEKit.framework) is this:

IDEConsoleTextView * consoleTextView = [[[[[[[NSApplication sharedApplication] _workspaceWindowControllers] firstObject] editorArea] valueForKey: @"_activeDebuggerArea"] consoleArea] valueForKey: @"_consoleView"]

I've played a little with setting breakpoints and trying to inject the xc_fixAttributesInRange: method into this class, and neither seems to have any success. Further, printing the contents of this view suggests it's empty.

There's a second ivar of the consoleArea (of type IDEConsoleArea), _consoleViewSwift, which returns a Swift class IDEConsoleEditorView (found in PlugIns/IDESourceEditor.framework). Printing the -string method of this class into the console gives the correct output. It's full path is given as:

[[[[[[[[NSApplication sharedApplication] _workspaceWindowControllers] firstObject] editorArea] valueForKey: @"_activeDebuggerArea"] valueForKey: @"_rightReplacementView"] valueForKey: @"_installedViewController"] valueForKey: @"_consoleViewSwift"]

And we can confirm that this the new console view:

po [[[[[[[[[[[NSApplication sharedApplication] _workspaceWindowControllers] firstObject] editorArea] valueForKey: @"_activeDebuggerArea"] valueForKey: @"_rightReplacementView"] subviews] firstObject] subviews] firstObject] subviews]
<__NSArrayM 0x608001049e40>(
<DVTScopeBarView: 0x12410adf0>,
IDEConsoleEditorView: Frame: (0.0, 28.0, 967.0, 389.0), Bounds: (0.0, 0.0, 967.0, 389.0) contentViewOffset: 0.0
)

So I suspect this is the starting point for the Xcode 10 version of this implementation. My attempts to go any further here are hampered by my lack of Swift experience, such as an inability to access the ivars of the IDESourceEditorView. I'll keep hacking tonight, but I don't suspect I'll get much further...

Edit: It may actually be worth looking at the swift class SourceEditorLineLayer inside SourceEditor.framework. It's a CALayer-based class, and it looks like the -contents of these are CGImages. I suspect the -renderInContext: method generates these images, so maybe swizzling this method could be ticket to a solution?

basvankuijck commented 6 years ago

Can someone confirm that other plugins still work and it's just this one.
Or that Apple has actually closed the door completely on xcplugin.

gklka commented 6 years ago

I think they work fine (including this one), only the console has been changed, and XcodeColors cannot patch it the old way.

absolutlabs commented 6 years ago

yep, exactly

sbwilson commented 6 years ago

I think they work fine (including this one), only the console has been changed, and XcodeColors cannot patch it the old way.

Except it also looks like the editor view has been rewritten in Swift as well — perusing the view hierarchy shows that there's now a IDESourceEditor.SourceCodeEditorContainerView_ControlledBy_IDESourceEditor.SourceCodeEditor view, which could mean that many other plugins are likely to be affected...

basvankuijck commented 6 years ago

I made a dump of the view hierarchy of Xcode10: https://gist.github.com/basvankuijck/32693fd517c641f94c13295344f9620e

sbwilson commented 6 years ago

I've had a little more of a dig, now that I have figured out how to set breakpoints on these system Swift classes (b SourceEditor.SourceEditorLineLayer.init was my first success)...

The line layers are created by SourceEditorLayoutManager.makeLineLayerForLine(..). Early in the disassembly, it calls SourceEditor.SourceEditorLayoutManager.attributedStringAndImagesForLine(..). If we were able to swizzle this method, first calling the original method, followed by most of the meat from ApplyANSIColors(), would we get back our beloved colours?

sbwilson commented 6 years ago

Some further exploration. My class-dump'ed version of SourceEditor doesn't include any methods inside the SourceEditorLayoutManager class — though I'd wager that it may be due to the fact that this method returns a tuple instead of a single value, and hence class-dump can't interpret it properly. But I can find the IMP for this method:

(lldb) b SourceEditor.SourceEditorLayoutManager.attributedStringAndImagesForLine
Breakpoint 2: where = SourceEditor`SourceEditor.SourceEditorLayoutManager.attributedStringAndImagesForLine(_: Swift.Int, context: SourceEditor.SourceEditorTextAttributeContext) -> (__C.NSAttributedString, Swift.Array<(image: __C.CGImageRef, columnRange: Swift.Range<Swift.Int>)>), address = 0x0000000119a94f70

That address can be cast to an IMP and displayed:

(lldb) po (IMP)0x0000000119a94f70
(SourceEditor`SourceEditor.SourceEditorLayoutManager.attributedStringAndImagesForLine(_: Swift.Int, context: SourceEditor.SourceEditorTextAttributeContext) -> (__C.NSAttributedString, Swift.Array<(image: __C.CGImageRef, columnRange: Swift.Range<Swift.Int>)>))

I'm not certain we can necessarily work backwards from here though, nor how to go about swizzling this method. So I think I'll pause here, remove my XcodeColor bits from my current logger for now, and hopefully somebody Swiftier than I will be able to take this from here...

basvankuijck commented 6 years ago

For those concerning, here is a class dump of SourceEditor for Xcode10: https://github.com/basvankuijck/Xcode-headers/tree/master/SourceEditor

mooch443 commented 6 years ago

I'm afraid I can't offer much help with this (other than emotional support), but I don't want to live without my colors... It's so puzzling that they wouldn't just add this as a feature. Seems like I need to learn Swift after all? Please do tell if anyone solves this first.

sedwo commented 6 years ago

@sbwilson @basvankuijck Your investigations in trying to get plugins working is very hopeful. I (and perhaps others) might even be inclined to support your efforts by tipping a patreon account. As this type of visual logging is still far superior then anything Apple has delivered. I'd say keep up the great work to revive Xcode plugins!

sbwilson commented 6 years ago

You need to stop saying all these encouraging words — I spent another few hours playing this morning when I shouldn't have...

The one success I can report is that one can add methods to SourceEditorLayoutManager using class_addMethod, and they can be called from both objc and Swift:

void myMethodIMP(id self, SEL _cmd)
{
    NSLog( @"calling -stupid" );
}

@implementation XcodeColors

+ (void)load
{
    ...
    Class cl = NSClassFromString( @"SourceEditor.SourceEditorLayoutManager" );
    class_addMethod( cl, @selector(stupid), (IMP) myMethodIMP,  "V@:" )
}

At my trusty breakpoint (attributedStringAndImagesForLine), I can call e [$r13 stupid] (where $r13 is the register for self — try register read to see them all, including the IMP) and I get the correct response -- the log message. Through some gymnastics, we can do the same within swift:

(lldb) p/x $r13 
(unsigned long) $21 = 0x0000000126719b30
(lldb) e -l swift -- (unsafeBitCast(0x0000000126719b30, to: AnyObject.self)).perform(Selector("stupid"))
2018-09-26 12:38:03.370609+1000 Xcode[27812:11154274] calling -stupid
(Unmanaged<AnyObject>?) $R22 = nil

It is possible to access some of the ivars from this class though, both in the debugger as well as the objc runtime methods, though there doesn't seem to be any interesting things there.

Has this achieved anything though? I doubt it. In order to do the swizzle, we need to get some handle to the method. From my explorations, it seems that a method must be tagged with @objc in order to use swizzling. I don't think that occurred, especially from looking at the output of class-dump for SourceEditorLayoutManager (see @basvankuijck's link above). Perhaps there's some hidden gems in the Swift runtime that we can leverage, but it doesn't look promising.

:(

sandychales commented 5 years ago

Excuse me, am I the only one who can't use this plugin in Xcode 9.2 after trying all suggestted method? I added DVTPlugInCompatibilityUUIDs, but it still not work.

gklka commented 5 years ago

No, it does not work anymore.

hahaGitHub commented 5 years ago

the same issue. I use xcode10.1 and the plugin still does not work.

rakeyang commented 5 years ago

How to fix it?

sedwo commented 5 years ago

I think this plug-in is done. :/ Unless Apple opens up Xcode console manipulation.

rakeyang commented 5 years ago

I think this plug-in is done. :/ Unless Apple opens up Xcode console manipulation.

Hope so.

dbquarrel commented 5 years ago

I didn't want to implement or change to another framework for logging, and I wanted the color functionality that is now long dead. I'm tired of apple deprecating things to death and frameworks that implement Swift breaking with every XCode release... so I wanted something just very simple and usable that would never break.

I implemented something using the bones of Xcode logger but not using any Xcode plugin. It is pretty simple to use this, all you need to do is call the logging functions and it will dump ANSI coded log output to /tmp/xcode.log as well as continue logging to the Xcode console (but without colors).

Furthermore in the color log I'm injecting information about the file and class and method that is being called. So if you use it like this:

for (NSString *key in other.allKeys){
    id val = other[key];
    XLog_FB(yellow,blue,@"%@: %@",key,val);
    self[key] = val;
}

You get output that looks like this:

Screen Shot 2019-07-24 at 20 14 39

Or this:

Screen Shot 2019-07-24 at 20 21 11

The first two params are short forms, where say "yellow" will turn into a call to [UIColor yellowColor] and I tried (but didn't really test) to support older ways XCodelogger was logging colors.

So things like:

XLog_ERROR("This error happened: %@",error)

Should work as before. I hope anyway.

It will automatically remove itself when compiling optimized at the macro level. You can pass in various params as shown above to format and log strings.

To view the color log you'd just do this in a terminal window:

tail -f /tmp/xcode.log

And then you have some color logging functionality back on a lightweight basis without having to maintain anything.

All you really need to do is just use the top level functions, there are objects underneath on a singleton basis and using dispatch queues so multiple threads don't interfere and overwrite in the log.

I just slammed this together, so maybe there's bugs still. But you can build on it.

Xlogger.h:

#ifndef XLogger_h
#define XLogger_h

#import <UIKit/UIKit.h>

//  XLogger.h
//
//  Created by Razvan Tanase on 29/06/15.
//  Copyright (c) 2015 Codebringers Software Inc. All rights reserved.
//
//  Version 0.1

#import <Foundation/Foundation.h>

#define XCC_USE_VT 1
// for the future
#ifdef XCC_USE_VT
#  define XCC_ESCAPE @"\033["
#  define XCC_FG_COMMAND "38;2;"
#  define XCC_BG_COMMAND "48;2;"
#  define XCC_COMMIT "m"
#  define XCC_SEP ";"
#  define XCC_RESET XCC_ESCAPE "0" XCC_COMMIT
#  define XCC_RESET_FG  XCC_ESCAPE XCC_FG_COMMAND "0" XCC_COMMIT // Clear any foreground color
#  define XCC_RESET_BG  XCC_ESCAPE XCC_BG_COMMAND "0" XCC_COMMIT // Clear any background color
#endif

// !!!: CHANGE THE TIMESTAMP FORMAT IF NEEDED
static NSString *const kTIMESTAMP_FORMAT = @"HH:mm:ss";

// ???: ADD NEWLINE AFTER EACH OUTPUT?
static BOOL kXLOG_SHOULD_ADD_NEWLINE_AFTER_OUTPUT = NO;

// CHANGE COLORS: RGB FORMAT (fg: text / bg: background)
#pragma mark - COLORS
//default log color

#define XCC_COLOR(r,g,b) [UIColor colorWithRed:r/255. green:g/255. blue:b/255. alpha:1]

#pragma mark - DEFAULT Log Color
//#define kTEXT_COLOR_NO_HIGHLIGHT @"fg0,0,255;"
#define kTEXT_COLOR_NO_HIGHLIGHT XCC_FG(0,0,255)

//text & backgroud colors for INFO Log
#pragma mark - INFO Log Color
//#define kTEXT_COLOR_INFO      @"fg255,255,0;"
#define kTEXT_COLOR_INFO      XCC_COLOR(255,255,0)
#define kBGND_COLOR_INFO      XCC_COLOR(0,0,0)
#define kTEXT_COLOR_INFO2     XCC_COLOR(255,0,255)
#define kBGND_COLOR_INFO2     XCC_COLOR(0,0,0)

#define kTEXT_COLOR_INFO_1    XCC_COLOR(255,255,255)
#define kBGND_COLOR_INFO_1    XCC_COLOR(204,0,204)

//text & backgroud colors for HIGHLIGHT Log
#pragma mark HIGHLIGHT Log Color
#define kTEXT_COLOR_HIGHLIGHT XCC_COLOR(0,255,255)
#define kBGND_COLOR_HIGHLIGHT XCC_COLOR(0,0,0)
#define kTEXT_COLOR_HIGHLIGHT_1 XCC_COLOR(255,255,255)
#define kBGND_COLOR_HIGHLIGHT_1 XCC_COLOR(0,102,51)

//text & backgroud colors for WARNING Log
#pragma mark WARNING Log Color
#define kTEXT_COLOR_WARNING   XCC_COLOR(0,0,0)
#define kBGND_COLOR_WARNING   XCC_COLOR(255,255,0)

//text & backgroud colors for ERROR Log
#pragma mark ERROR Log Color
#define kTEXT_COLOR_ERROR     XCC_COLOR(255,255,255)
#define kBGND_COLOR_ERROR     XCC_COLOR(255,0,0)

#pragma mark MILD Log Color
#define kTEXT_COLOR_MILD XCC_COLOR(66,66,66)
#define kBGND_COLOR_MILD XCC_COLOR(0,0,0)

void _XLog2(UIColor *fg, UIColor *bg, const char *file, long line, const char *function, NSString *output);

#define XCC_NAME(short_name) [UIColor short_name##Color]

#define XLog_FB(fg,bg,output_format, ...) _XLog2(\
XCC_NAME(fg),\
XCC_NAME(bg),\
__FILE__,\
__LINE__,\
__PRETTY_FUNCTION__, \
[NSString stringWithFormat:output_format "", ## __VA_ARGS__]) /* userstuff */

#define XLog_Color(fg,bg,output_format, ...) _XLog2(\
(fg),\
(bg),\
__FILE__,\
__LINE__,\
__PRETTY_FUNCTION__, \
[NSString stringWithFormat:output_format "", ## __VA_ARGS__]) /* userstuff */

#define XLog_FB_nofunc(fg,bg,output_format, ...) _XLog2(\
XCC_NAME(fg),\
XCC_NAME(bg),\
NULL,\
0,\
NULL,\
[NSString stringWithFormat:output_format "", ## __VA_ARGS__]) /* userstuff */

#define XLog_RGB_nofunc(fg,bg,output_format, ...) _XLog2(\
XCC_COLOR(fg),\
XCC_COLOR(bg),\
NULL,\
0,\
NULL,\
[NSString stringWithFormat:output_format "", ## __VA_ARGS__]) /* userstuff */

NSString *timeStamp(void);

#pragma mark - XLOG

#define XLog(s, ...) XLog_Color(nil,nil, s, ##__VA_ARGS__)

//XLog ERROR
#define XLog_ERROR(s, ...) XLog_Color(kTEXT_COLOR_ERROR,kBGND_COLOR_ERROR,s,##__VA_ARGS__)

//XLog INFO
#define XLog_INFO(s, ...) XLog_Color(kTEXT_COLOR_INFO,kBGND_COLOR_INFO,s,##__VA_ARGS__)

#define XLog_INFO2(s, ...) XLog_Color(kTEXT_COLOR_INFO2,kBGND_COLOR_INFO2,s,##__VA_ARGS__)

//XLog HIGHLIGHT
#define XLog_HIGHLIGHT(s, ...) XLog_Color(kTEXT_COLOR_HIGHLIGHT,kBGND_COLOR_HIGHLIGHT,s,##__VA_ARGS__)

//XLog WARNING
#define XLog_WARNING(s, ...) XLog_Color(kTEXT_COLOR_WARNING,kBGND_COLOR_WARNING,s,##__VA_ARGS__)

#define LOGMETHOD     XLog_FB_nofunc(cyan,darkGray,@"-[%s(%p) %s]",object_getClassName(self),self,sel_getName(_cmd));
#define LOGRC()    XLog(@"[(%@ *)%p %s] %u[%c]",object_getClassName(self),self,sel_getName(_cmd),[self retainCount],(_cmd==@selector(retain)?'+':(_cmd==@selector(release)?'-':' ')))

#if defined __OPTIMIZE__
#undef XLog_HIGHLIGHT
#undef XLog_INFO2
#undef XLog_FB
#undef XLog_Color
#undef XLog_INFO
#undef XLog_FB_nofunc
#undef LOGMETHOD
#define LOGMETHOD
#define XLog_FB(a,b,...) if ((0)) NSLog(__VA_ARGS__)
#define XLog_Color(a,b,...) if ((0)) NSLog(__VA_ARGS__)
#define XLog_HIGHLIGHT(...) if ((0)) NSLog(__VA_ARGS__)
#define XLog_INFO2(...) if ((0)) NSLog(__VA_ARGS__)
#define XLog_INFO(...) if ((0)) NSLog(__VA_ARGS__)
#define XLog_FB_nofunc(a,b,...) if ((0)) NSLog(__VA_ARGS__)
#else
#endif

#endif

XLogger.m

#import <Foundation/Foundation.h>

//
//  XLogger.m
//
//  Created by Razvan Tanase on 29/06/15.
//  Copyright (c) 2015 Codebringers Software Inc. All rights reserved.
//

#import "XLogger.h"
#import <UIKit/UIColor.h>

@interface NSString (XLoggerColor)
+ (NSString *)xLoggerRGB:(uint32_t)rgb forTier:(NSString *)tier;
+ (NSString *)xLoggerColor:(UIColor *)color forTier:(NSString *)tier;
@end

@implementation NSString (XLoggerColor)

+ (NSString *)xLoggerRGB:(uint32_t)rgb forTier:(NSString *)tier
{
#if !defined(XCC_DISABLE)
    uint32_t r = ((rgb & 0xff0000) >> 16);
    uint32_t g = ((rgb & 0x00ff00) >> 8);
    uint32_t b = (rgb & 0x0000ff);
    return [NSString stringWithFormat:XCC_ESCAPE@"%@%d" XCC_SEP "%d" XCC_SEP "%d" XCC_COMMIT,tier,r,g,b];
#else
    return @"";
#endif
}

+ (NSString *)xLoggerColor:(UIColor *)color forTier:(NSString *)tier
{
#if !defined(XCC_DISABLE)
    uint32_t rgb = 0;
    CGFloat red, green, blue, alpha;
    [color getRed:&red green:&green blue:&blue alpha:&alpha];
    rgb |= ((unsigned)(blue*255.));
    rgb |=(((unsigned)(green*255.))<<8);
    rgb |=(((unsigned)(red*255.))<<16);
    return [self xLoggerRGB:rgb forTier:tier];
#else
    return @"";
#endif
}

#define XCC_ANSI_FG XCC_ESCAPE "38;5;"
#define XCC_ANSI_BG XCC_ESCAPE "48;5;"

static inline NSString *
xLoggerANSI(uint32_t ansi, NSString *tier)
{
#if !defined(XCC_DISABLE)
    return [NSString stringWithFormat:@"%@%d" XCC_COMMIT,tier,ansi];
#else
    return @"";
#endif
}

static inline unsigned
ansiForRedGreenBlue(unsigned r, unsigned g, unsigned b)
{
    double result;
    if (r == g && g == b) {
        if (r < 8) {
            return 16;
        }
        if (r > 248) {
            return 231;
        }
        result = round(((r - 8) / 247.) * 24) + 232;
    } else {
        result = 16
        + (36 * round(r / 255. * 5))
        + (6 * round(g / 255. * 5))
        + round(b / 255. * 5);
    }
    return (unsigned)result;
}

+ (NSString *)xLoggerANSI:(UIColor *)color forTier:(NSString *)tier
{
#if !defined(XCC_DISABLE)
    CGFloat red, green, blue, alpha;
    [color getRed:&red green:&green blue:&blue alpha:&alpha];
    red*=256;
    green*=256;
    blue*=256;
    return [self xLoggerANSI:red :green :blue forTier:tier];
#else
    return @"";
#endif
}

+ (NSString *)xLoggerANSI:(unsigned)red :(unsigned)green :(unsigned)blue forTier:(NSString *)tier
{
#if !defined(XCC_DISABLE)
    unsigned ansi = ansiForRedGreenBlue(red,green,blue);
    NSString *code = xLoggerANSI(ansi,tier);
    return code;
#else
    return @"";
#endif
}

@end

@interface XLogger : NSObject
{
    FILE *_logfile;
    dispatch_queue_t _queue;
}
@end

@implementation XLogger

#pragma mark - Singleton Junk

+ (instancetype)sharedLogger;
{
    static XLogger *logger = nil;
    static dispatch_once_t otoken;
    dispatch_once(&otoken, ^{
        logger = [[XLogger alloc] init];
    });
    return logger;
}

- (unsigned long)retainCount {
    return UINT_MAX;  // denotes an object that cannot be released
}

- (oneway void)release {
    // do nothing
}

- (id)autorelease {
    return self;
}

- (instancetype)init;
{
    if ((self = [super init])){
        _queue = dispatch_queue_create("XLogger", DISPATCH_QUEUE_CONCURRENT);
        _logfile =  fopen("/tmp/xcode.log","a");
    }
    return self;
}
- (void)_log:(NSString *)fgc :(NSString *)bgc :(NSString *)output :(FILE *)standardLog :(FILE *)colorLog
{
    if (!output) {
        return;
    }
    dispatch_sync(_queue, ^{
        const char *msg = NULL;
        if (standardLog) {
            msg = output.UTF8String;
            fputs(msg, stdout);
            fputs("\n",stdout);
        }
        if (colorLog) {
            if (fgc || bgc) {
                NSString *formatted = [NSString stringWithFormat:@"%@%@%@" XCC_RESET "\n",fgc?fgc:@"",bgc?bgc:@"",output];
                msg = formatted.UTF8String;
            } else if (!msg) {
                msg = output.UTF8String;
            }
            fputs(msg,colorLog);
            fflush(colorLog);
        }
    });
}

- (void)logfileAndXCode:(NSString *)fgc :(NSString *)bgc :(NSString *)output;
{
    [self _log:fgc :bgc :output :stdout :_logfile];
}

- (void)logfileOnly:(NSString *)fgc :(NSString *)bgc :(NSString *)output;
{
    [self _log:fgc :bgc :output :NULL :_logfile];
}

- (void)xcodeOnly:(NSString *)fgc :(NSString *)bgc :(NSString *)output;
{
    [self _log:fgc :bgc :output :stdout :NULL];
}

void _XLog2(UIColor *fg, UIColor *bg, const char *file, long line, const char *function, NSString *output)
{
    XLogger *log = XLogger.sharedLogger;
    if (function) {
        NSString *color = [NSString xLoggerANSI:66 :66 :66 forTier:XCC_ANSI_FG];
        NSString *trimmedFile = [[NSString stringWithUTF8String:file] lastPathComponent];
        NSString *fileinfo = [NSString stringWithFormat:@"%@:%ld:%s", trimmedFile,line,function];
        [log logfileOnly:color :nil :fileinfo];
    }
    NSString *fgc = [NSString xLoggerANSI:fg forTier:XCC_ANSI_FG];
    NSString *bgc = [NSString xLoggerANSI:bg forTier:XCC_ANSI_BG];
    [log logfileAndXCode:fgc :bgc :output];
}

NSString *timeStamp(void) {
    NSDate *currentTime = [NSDate date];

    static NSDateFormatter *dateFormatter;

    if (!dateFormatter) {
        dateFormatter = [[NSDateFormatter alloc] init];
        [dateFormatter setDateFormat:kTIMESTAMP_FORMAT];
    }

    return [dateFormatter stringFromDate: currentTime];
}

@end