diff --git a/XCFixin_UserScripts/UserScripts.org b/XCFixin_UserScripts/UserScripts.org index 3010c26..2254824 100644 --- a/XCFixin_UserScripts/UserScripts.org +++ b/XCFixin_UserScripts/UserScripts.org @@ -25,8 +25,11 @@ symlinks to them. Any it finds have their names added to the new Select script from the menu, and the script runs. The current selection is passed to the script as its standard input. -The script's standard output replaces the current selection. Any -standard error output is ignored. +If the script's exit code is 0, the script's standard output replaces +the current selection. + +If the script's exit code is anything else, an alert box is shown with +the first 1K or so of the script's standard error output. * Setting selection from a script @@ -116,9 +119,6 @@ The current option names are: - The key equivalent emacs notation 'thing' is lame and I need to find some better way of doing it. -- Error reporting is not great. If you run Console.app, you might find - that the fixin has printed something useful there. - - If you run a script on text that includes the string "%%%{PBXSelection}%%%", that string will disappear. (Presumably this happened in Xcode3 too?) diff --git a/XCFixin_UserScripts/XCFixin_UserScripts.m b/XCFixin_UserScripts/XCFixin_UserScripts.m index ec5f81d..3d0c9fd 100644 --- a/XCFixin_UserScripts/XCFixin_UserScripts.m +++ b/XCFixin_UserScripts/XCFixin_UserScripts.m @@ -8,6 +8,19 @@ #import "XCFixin.h" +////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////// + +// if true, extra verbose logging via NSLog. +#define VERBOSE_LOGGING 0 + +////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////// + +// if true, an extra test menu item for whatever I'm testing at the +// moment. +#define TEST_UI 0 + //////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////// @@ -19,6 +32,27 @@ return [NSString stringWithFormat:@"(%s *)%p: %@",class_getName([obj class]),obj,obj]; } +////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////// + +#if VERBOSE_LOGGING + +static void Log(NSString *fmt,...) NS_FORMAT_FUNCTION(1,2); + +static void Log(NSString *fmt,...) +{ + va_list v; + va_start(v,fmt); + NSLogv(fmt,v); + va_end(v); +} + +#else//VERBOSE_LOGGING + +#define Log(...) ((void)0) + +#endif//VERBOSE_LOGGING + //////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////// @@ -28,7 +62,7 @@ if(!mainWindow) { if(log) - NSLog(@"Can't find IDE text view - no main window.\n"); + Log(@"Can't find IDE text view - no main window.\n"); return nil; } @@ -37,7 +71,7 @@ if(!DVTCompletingTextView) { if(log) - NSLog(@"Can't find IDE text view - DVTCompletingTextView class unavailable.\n"); + Log(@"Can't find IDE text view - DVTCompletingTextView class unavailable.\n"); return nil; } @@ -56,7 +90,7 @@ if(!textView) { if(log) - NSLog(@"Can't find IDE text view - no DVTCompletingTextView in the responder chain.\n"); + Log(@"Can't find IDE text view - no DVTCompletingTextView in the responder chain.\n"); return nil; } @@ -149,31 +183,28 @@ static NSRange NSMakeRangeFromStartAndEnd(NSUInteger start,NSUInteger end) -(void)run { - NSLog(@"%s: path=%@\n",__FUNCTION__,fileName_); + Log(@"%s: path=%@\n",__FUNCTION__,fileName_); NSTextView *textView=FindIDETextView(YES); if(!textView) { - NSLog(@"Not running scripts - can't find IDE text view.\n"); + Log(@"Not running scripts - can't find IDE text view.\n"); return; } NSTextStorage *textStorage=[textView textStorage]; if(!textStorage) { - NSLog(@"Not running scripts - IDE text view has no text storage.\n"); + Log(@"Not running scripts - IDE text view has no text storage.\n"); return; } NSArray *inputRanges=[textView selectedRanges]; - NSLog(@"%s: %zu selected ranges:\n",__FUNCTION__,(size_t)[inputRanges count]); + Log(@"%s: %zu selected ranges:\n",__FUNCTION__,(size_t)[inputRanges count]); for(NSUInteger i=0;i<[inputRanges count];++i) - { - NSRange range=[[inputRanges objectAtIndex:i] rangeValue]; - NSLog(@" %zu. %@\n",(size_t)i,NSStringFromRange(range)); - } + Log(@" %zu. %@\n",(size_t)i,NSStringFromRange([[inputRanges objectAtIndex:i] rangeValue])); - NSLog(@"%s: select range: %@\n",__FUNCTION__,NSStringFromRange([textView selectedRange])); + Log(@"%s: select range: %@\n",__FUNCTION__,NSStringFromRange([textView selectedRange])); NSString *inputStr=nil; NSData *inputData=nil; @@ -215,7 +246,7 @@ -(void)run NSTask *task=[[[NSTask alloc] init] autorelease]; [task setLaunchPath:fileName_]; - NSLog(@"%s: [task launchPath] = %@\n",__FUNCTION__,[task launchPath]); + Log(@"%s: [task launchPath] = %@\n",__FUNCTION__,[task launchPath]); NSPipe *stdinPipe=[NSPipe pipe]; NSPipe *stdoutPipe=[NSPipe pipe]; @@ -225,27 +256,26 @@ -(void)run [task setStandardInput:stdinPipe]; [task setStandardError:stderrPipe]; - int exitCode=0; - NSData *outputData=nil; + NSData *stdoutData=nil; @try { - NSLog(@"%s: launching task...\n",__FUNCTION__); + Log(@"%s: launching task...\n",__FUNCTION__); [task launch]; - NSLog(@"%s: task launched.\n",__FUNCTION__); + Log(@"%s: task launched.\n",__FUNCTION__); if(inputData) { @try { - NSLog(@"%s: writing %zu bytes to task's stdin...\n",__FUNCTION__,(size_t)[inputData length]); + Log(@"%s: writing %zu bytes to task's stdin...\n",__FUNCTION__,(size_t)[inputData length]); [[stdinPipe fileHandleForWriting] writeData:inputData]; - NSLog(@"%s: wrote to task's stdin.\n",__FUNCTION__); + Log(@"%s: wrote to task's stdin.\n",__FUNCTION__); } @catch(NSException *e) { // Maybe the task finished really quickly and it doesn't care what's in its stdin. - NSLog(@"%s: ignoring error (%@) writing to task's stdin.\n",__FUNCTION__,e); + Log(@"%s: ignoring error (%@) writing to task's stdin.\n",__FUNCTION__,e); } } @@ -253,30 +283,70 @@ -(void)run @try { - NSLog(@"%s: reading from task's stdout...\n",__FUNCTION__); - outputData=[[stdoutPipe fileHandleForReading] readDataToEndOfFile]; - NSLog(@"%s: read %zu bytes from task's stdout.\n",__FUNCTION__,(size_t)[outputData length]); + Log(@"%s: reading from task's stdout...\n",__FUNCTION__); + stdoutData=[[stdoutPipe fileHandleForReading] readDataToEndOfFile]; + Log(@"%s: read %zu bytes from task's stdout.\n",__FUNCTION__,(size_t)[stdoutData length]); } @catch(NSException *e) { - NSLog(@"%s: error (%@) reading from task's stdout.\n",__FUNCTION__,e); + Log(@"%s: error (%@) reading from task's stdout.\n",__FUNCTION__,e); } - - NSLog(@"%s: waiting for task exit...\n",__FUNCTION__); + + Log(@"%s: waiting for task exit...\n",__FUNCTION__); [task waitUntilExit]; - NSLog(@"%s: task exit.\n",__FUNCTION__); + Log(@"%s: task exit.\n",__FUNCTION__); + + int exitCode=[task terminationStatus]; - exitCode=[task terminationStatus]; if(exitCode!=0) - NSLog(@"Script failed - exit code %d.\n",exitCode); + { + NSData *stderrData=nil; + + @try + { + Log(@"%s: reading from task's stderr...\n",__FUNCTION__); + stderrData=[[stderrPipe fileHandleForReading] readDataToEndOfFile]; + Log(@"%s: read %zu bytes from task's stderr.\n",__FUNCTION__,(size_t)[stderrData length]); + } + @catch(NSException *e) + { + Log(@"%s: ignoring error (%@) reading from task's stderr.\n",__FUNCTION__,e); + stderrData=nil; + } + + NSString *alertBody=@""; + if(stderrData) + { + alertBody=[[[NSString alloc] initWithData:stderrData + encoding:NSUTF8StringEncoding] autorelease]; + } + + NSUInteger maxLen=1000; + if([alertBody length]>maxLen) + alertBody=[[alertBody substringToIndex:maxLen] stringByAppendingString:[NSString stringWithFormat:@"\n\n(%u chars snipped)",(unsigned)([alertBody length]-maxLen)]]; + + if([alertBody length]==0) + alertBody=@"No error output was provided."; + + [[NSAlert alertWithMessageText:[NSString stringWithFormat:@"Script failed - exit code %d",exitCode] + defaultButton:@"OK" + alternateButton:nil + otherButton:nil + informativeTextWithFormat:@"%@",alertBody] runModal]; + return; + } } @catch(NSException *e) { if([[e name] isEqualToString:@"NSInvalidArgumentException"]) { - exitCode=-1; - - NSLog(@"Script launch failed.\n"); + [[NSAlert alertWithMessageText:@"Script launch failed" + defaultButton:@"OK" + alternateButton:nil + otherButton:nil + informativeTextWithFormat:@"Failed to launch \"%@\".\n\nReason: %@.",[task launchPath],[e reason]] runModal]; + + return; } else @throw e; @@ -285,14 +355,12 @@ -(void)run { } - if(exitCode!=0) - outputData=nil; - - if(outputData) + if(stdoutData) { NSString *selectionMarker=@"%%\x25{PBXSelection}%%%"; - NSString *outputStr=[[[NSString alloc] initWithData:outputData encoding:NSUTF8StringEncoding] autorelease]; + NSString *stdoutStr=[[[NSString alloc] initWithData:stdoutData + encoding:NSUTF8StringEncoding] autorelease]; NSRange a;//before 1st marker NSRange b;//between 1st marker and 2nd marker (selection goes here) @@ -303,24 +371,24 @@ -(void)run case SRM_ALL: { a=NSMakeRange(0,0); - b=NSMakeRange(0,[outputStr length]); - c=NSMakeRange([outputStr length],0); + b=NSMakeRange(0,[stdoutStr length]); + c=NSMakeRange([stdoutStr length],0); } break; case SRM_MARKER: { - NSRange r1=[outputStr rangeOfString:selectionMarker + NSRange r1=[stdoutStr rangeOfString:selectionMarker options:NSLiteralSearch range:NSMakeRangeFromStartAndEnd(0, - [outputStr length])]; + [stdoutStr length])]; if(r1.location==NSNotFound) { // no selection anywhere a=NSMakeRangeFromStartAndEnd(0, - [outputStr length]); - c=b=NSMakeRange([outputStr length], + [stdoutStr length]); + c=b=NSMakeRange([stdoutStr length], 0); } else @@ -328,24 +396,24 @@ -(void)run a=NSMakeRangeFromStartAndEnd(0, r1.location); - NSRange r2=[outputStr rangeOfString:selectionMarker + NSRange r2=[stdoutStr rangeOfString:selectionMarker options:NSLiteralSearch range:NSMakeRangeFromStartAndEnd(r1.location+[selectionMarker length], - [outputStr length])]; + [stdoutStr length])]; if(r2.location==NSNotFound) { b=NSMakeRange(r1.location+[selectionMarker length], 0); c=NSMakeRangeFromStartAndEnd(r1.location+[selectionMarker length], - [outputStr length]); + [stdoutStr length]); } else { b=NSMakeRangeFromStartAndEnd(r1.location+[selectionMarker length], r2.location); c=NSMakeRangeFromStartAndEnd(r2.location+[selectionMarker length], - [outputStr length]); + [stdoutStr length]); } } } @@ -354,15 +422,15 @@ -(void)run default: case SRM_NONE: { - a=NSMakeRange(0,[outputStr length]); - b=NSMakeRange([outputStr length],0); - c=NSMakeRange([outputStr length],0); + a=NSMakeRange(0,[stdoutStr length]); + b=NSMakeRange([stdoutStr length],0); + c=NSMakeRange([stdoutStr length],0); } break; } - outputStr=[[[outputStr substringWithRange:a] stringByAppendingString:[outputStr substringWithRange:b]] stringByAppendingString:[outputStr substringWithRange:c]]; + stdoutStr=[[[stdoutStr substringWithRange:a] stringByAppendingString:[stdoutStr substringWithRange:b]] stringByAppendingString:[stdoutStr substringWithRange:c]]; [textView breakUndoCoalescing]; @@ -370,9 +438,9 @@ -(void)run // // this is a fix for scripts that just pop a %%%{PBXSeleciton}%%% // into their input to put the cursor somewhere. - if(![outputStr isEqualToString:inputStr]) + if(![stdoutStr isEqualToString:inputStr]) { - [textView insertText:outputStr + [textView insertText:stdoutStr replacementRange:inputRange]; } @@ -386,7 +454,7 @@ -(void)run -(void)dealloc { - NSLog(@"%s: (%s *)%p: %@\n",__FUNCTION__,class_getName([self class]),self,fileName_); + Log(@"%s: (%s *)%p: %@\n",__FUNCTION__,class_getName([self class]),self,fileName_); [fileName_ release]; fileName_=nil; @@ -404,6 +472,9 @@ -(void)dealloc @interface XCFixin_ScriptsHandler:NSObject { +#if TEST_UI + NSMenuItem *testUIMenuItem_; +#endif//TEST_UI NSMenuItem *refreshMenuItem_; } @end @@ -437,14 +508,14 @@ -(void)refreshScriptsMenu NSMenu *mainMenu=[NSApp mainMenu]; if(!mainMenu) { - NSLog(@"%s: main menu not found.\n",__FUNCTION__); + Log(@"%s: main menu not found.\n",__FUNCTION__); return; } int scriptsMenuIndex=[mainMenu indexOfItemWithTitle:@"Scripts"]; if(scriptsMenuIndex<0) { - NSLog(@"%s: Scripts menu not found.\n",__FUNCTION__); + Log(@"%s: Scripts menu not found.\n",__FUNCTION__); return; } @@ -454,18 +525,18 @@ -(void)refreshScriptsMenu [scriptsMenu removeAllItems]; NSString *appSupportFolderName=SystemFolderName(kApplicationSupportFolderType,kUserDomain); - NSLog(@"appSupportFolderName=%@\n",appSupportFolderName); + Log(@"appSupportFolderName=%@\n",appSupportFolderName); NSString *scriptsFolderName=[NSString pathWithComponents:[NSArray arrayWithObjects:appSupportFolderName,@"Developer/Shared/Xcode/Scripts",nil]]; - NSLog(@"scriptsFolderName=%@\n",scriptsFolderName); + Log(@"scriptsFolderName=%@\n",scriptsFolderName); NSString *scriptsPListName=[NSString pathWithComponents:[NSArray arrayWithObjects:scriptsFolderName,@"Scripts.xml",nil]]; - NSLog(@"scriptsPListName=%@\n",scriptsPListName); + Log(@"scriptsPListName=%@\n",scriptsPListName); NSDictionary *scriptsProperties=[NSDictionary dictionaryWithContentsOfFile:scriptsPListName]; if(!scriptsProperties) - NSLog(@"%s: No scripts plist loaded.\n",__FUNCTION__); + Log(@"%s: No scripts plist loaded.\n",__FUNCTION__); else - NSLog(@"%s: Scripts plist: %@\n",__FUNCTION__,scriptsProperties); + Log(@"%s: Scripts plist: %@\n",__FUNCTION__,scriptsProperties); NSArray *scriptsFolderContents=[[NSFileManager defaultManager] contentsOfDirectoryAtPath:scriptsFolderName error:nil]; @@ -483,19 +554,19 @@ -(void)refreshScriptsMenu struct stat st; if(stat([path UTF8String],&st)!=0) { - NSLog(@"%@: not a script (stat failed)\n",path); + Log(@"%@: not a script (stat failed)\n",path); continue; } if(!(st.st_mode&(S_IFLNK|S_IFREG))) { - NSLog(@"%@: not a script (not symlink or regular file)\n",path); + Log(@"%@: not a script (not symlink or regular file)\n",path); continue; } if(![defaultManager isExecutableFileAtPath:path]) { - NSLog(@"%@: not a script (not executable)\n",path); + Log(@"%@: not a script (not executable)\n",path); continue; } @@ -509,7 +580,7 @@ -(void)refreshScriptsMenu NSString *name=[scripts objectAtIndex:scriptIdx]; NSString *path=[NSString pathWithComponents:[NSArray arrayWithObjects:scriptsFolderName,name,nil]]; - NSLog(@"Creating XCFixin_Script for %@.\n",path); + Log(@"Creating XCFixin_Script for %@.\n",path); XCFixin_Script *script=[[[XCFixin_Script alloc] initWithFileName:path] autorelease]; NSMenuItem *scriptMenuItem=[[[NSMenuItem alloc] initWithTitle:name @@ -523,7 +594,7 @@ -(void)refreshScriptsMenu if(![scriptProperties isKindOfClass:[NSDictionary class]]) scriptProperties=nil; - NSLog(@" Script properties: %@\n",scriptProperties); + Log(@" Script properties: %@\n",scriptProperties); NSString *keyEquivalent=[scriptProperties objectForKey:@"keyEquivalent"]; if(keyEquivalent&&[keyEquivalent length]>0) @@ -604,6 +675,13 @@ -(void)refreshScriptsMenu [scriptsMenu addItem:[NSMenuItem separatorItem]]; } +#if TEST_UI + testUIMenuItem_=[scriptsMenu addItemWithTitle:@"Test Item" + action:@selector(testUIAction:) + keyEquivalent:@""]; + [testUIMenuItem_ setTarget:self]; +#endif//TEST_UI + refreshMenuItem_=[scriptsMenu addItemWithTitle:@"Refresh" action:@selector(refreshScriptsMenuAction:) keyEquivalent:@""]; @@ -621,6 +699,22 @@ -(IBAction)runScriptAction:(id)arg //////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////// +#if TEST_UI +-(IBAction)testUIAction:(id)arg +{ + NSAlert *alert=[NSAlert alertWithMessageText:@"UI Test Text" + defaultButton:@"OK" + alternateButton:nil + otherButton:nil + informativeTextWithFormat:@"UI Test Body (%p)",self]; + int x=[alert runModal]; + Log(@"%s: result: %d\n",__FUNCTION__,x); +} +#endif//TEST_UI + +////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////// + -(IBAction)refreshScriptsMenuAction:(id)arg { [self refreshScriptsMenu]; @@ -634,7 +728,7 @@ -(BOOL)install NSMenu *mainMenu=[NSApp mainMenu]; if(!mainMenu) { - NSLog(@"%s: main menu not found!\n",__FUNCTION__); + Log(@"%s: main menu not found!\n",__FUNCTION__); return NO; } @@ -693,7 +787,7 @@ static BOOL GetClasses(const char *name0,...) *c=objc_getClass(name); if(!*c) { - NSLog(@"FATAL: class %s not found.\n",name); + Log(@"FATAL: class %s not found.\n",name); return NO; } } @@ -723,11 +817,12 @@ + (void)pluginDidLoad: (NSBundle *)plugin XCFixin_ScriptsHandler *handler=[[XCFixin_ScriptsHandler alloc] init]; if(!handler) - NSLog(@"%s: handler init failed.\n",__FUNCTION__); + Log(@"%s: handler init failed.\n",__FUNCTION__); else { BOOL goodInstall=[handler install]; - NSLog(@"%s: handler installed: %s\n",__FUNCTION__,goodInstall?"YES":"NO"); + (void)goodInstall; + Log(@"%s: handler installed: %s\n",__FUNCTION__,goodInstall?"YES":"NO"); } XCFixinPostflight();