Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Add the following options to tm_dialog:

 -a, --async-window           Displays the window and returns a reference token for it
                              in the output property list.
 -x, --close-window <token>   Close and release an async window.
 -t, --update-window <token>  Update an async window with new parameter values.
                              Use the --parameters argument (or stdin) to specify the
                              updated parameters.
 -l, --list-windows           List async window tokens.
 -w, --wait-for-input <token> Wait for user input from the given async window.

I've tested every existing invocation of tm_dialog in the bundles. Everything seems to work as well as before, including the menu option.
  • Loading branch information...
commit 30e194c83045e908b9505975e629e0e96018e42e 1 parent b209e5f
Chris Thomas authored
13 Dialog.h
View
@@ -8,10 +8,19 @@
- (float)version;
@end
-#define TextMateDialogServerProtocolVersion 5
+#define TextMateDialogServerProtocolVersion 6
@protocol TextMateDialogServerProtocol
- (int)textMateDialogServerProtocolVersion;
-- (id)showNib:(NSString*)aNibPath withParameters:(id)someParameters andInitialValues:(NSDictionary*)initialValues modal:(BOOL)flag center:(BOOL)shouldCenter;
+- (id)showNib:(NSString*)aNibPath withParameters:(id)someParameters andInitialValues:(NSDictionary*)initialValues modal:(BOOL)flag center:(BOOL)shouldCenter async:(BOOL)async;
+
+// Async window support
+- (id)listNibTokens;
+
+- (id)updateNib:(id)token withParameters:(id)someParameters;
+- (id)closeNib:(id)token;
+- (id)retrieveNibResults:(id)token;
+
+// Menu
- (id)showMenuWithOptions:(NSDictionary*)someOptions;
@end
249 Dialog.mm
View
@@ -1,8 +1,8 @@
#import <Carbon/Carbon.h>
#import <string>
+#import <sys/stat.h>
#import "Dialog.h"
-
-NSLock* Lock = [NSLock new];
+#import "TMDSemaphore.h"
// Apple ought to document this <rdar://4821265>
@interface NSMethodSignature (Undocumented)
@@ -22,48 +22,147 @@ @interface NibLoader : NSObject
NSWindow* window;
BOOL isModal;
BOOL center;
- BOOL didLock;
+ BOOL async;
+// BOOL didLock;
BOOL didCleanup;
+ int token;
}
+
- (NSDictionary*)instantiateNib:(NSNib*)aNib;
+- (void)updateParameters:(NSMutableDictionary *)params;
+
+// return unique ID for this NibLoader instance
+- (int)token;
+- (NSString*)windowTitle;
++ (NibLoader*)nibLoaderForToken:(int)token;
++ (NSArray*)nibDescriptions;
+- (BOOL)isAsync;
+- (NSMutableDictionary*)returnResult;
+
@end
@implementation NibLoader
-- (id)initWithParameters:(NSMutableDictionary*)someParameters modal:(BOOL)flag center:(BOOL)shouldCenter
+
+static NSMutableArray * sNibLoaders = nil;
+static int sNextNibLoaderToken = 1;
+
+- (id)initWithParameters:(NSMutableDictionary*)someParameters modal:(BOOL)flag center:(BOOL)shouldCenter aysnc:(BOOL)inAsync
{
if(self = [super init])
{
+ if(sNibLoaders == nil)
+ sNibLoaders = [[NSMutableArray alloc] init];
+
parameters = [someParameters retain];
[parameters setObject:self forKey:@"controller"];
isModal = flag;
center = shouldCenter;
+ async = inAsync;
+
+ token = sNextNibLoaderToken;
+ sNextNibLoaderToken += 1;
+
+ [sNibLoaders addObject:self];
}
return self;
}
+// Return the result; if there is no result, return the parameters
+- (NSMutableDictionary *)returnResult
+{
+ id result = [parameters objectForKey:@"result"];
+ if(result == nil)
+ {
+ result = [[parameters mutableCopy] autorelease];
+ [result removeObjectForKey:@"controller"];
+ }
+ return result;
+}
+
+// Async param updates
+- (void)updateParameters:(NSMutableDictionary *)updatedParams
+{
+ NSArray * keys = [updatedParams allKeys];
+
+ enumerate(keys, id key)
+ {
+ [parameters setValue:[updatedParams valueForKey:key] forKey:key];
+ }
+}
+
- (void)dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver:self];
-
+
enumerate(topLevelObjects, id object)
[object release];
[topLevelObjects release];
[parameters release];
-
[super dealloc];
}
-- (void)lock
+- (BOOL)isAsync
+{
+ return async;
+}
+
+- (int)token
+{
+ return token;
+}
+
+- (NSString*)windowTitle
{
- [Lock lock];
- didLock = YES;
+ return [window title];
}
-- (void)unlock
++ (NSArray*)nibDescriptions
{
- if(didLock)
- [Lock unlock];
- didLock = NO;
+ NSMutableArray* outNibArray = [NSMutableArray array];
+
+ enumerate(sNibLoaders, NibLoader* nibLoader)
+ {
+// if( [nibLoader isAsync] )
+ {
+ NSMutableDictionary* nibDict = [NSMutableDictionary dictionary];
+ NSString* nibTitle = [nibLoader windowTitle];
+
+ [nibDict setObject:[NSNumber numberWithInt:[nibLoader token]] forKey:@"token"];
+ if(nibTitle != nil)
+ {
+ [nibDict setObject:nibTitle forKey:@"windowTitle"];
+ }
+ [outNibArray addObject:nibDict];
+ }
+ }
+
+ return outNibArray;
+}
+
++ (NibLoader*)nibLoaderForToken:(int)token
+{
+ NibLoader * outLoader = nil;
+
+ enumerate(sNibLoaders, NibLoader* loader)
+ {
+ if([loader token] == token)
+ {
+ outLoader = loader;
+ break;
+ }
+ }
+
+ return outLoader;
+}
+
+
+- (void)wakeClient
+{
+ if(isModal)
+ [NSApp stopModal];
+
+ TMDSemaphore * semaphore = [TMDSemaphore semaphoreForTokenInt:token];
+ [semaphore stopWaiting];
}
- (void)setWindow:(NSWindow*)aWindow
@@ -79,6 +178,8 @@ - (void)setWindow:(NSWindow*)aWindow
- (void)cleanupAndRelease:(id)sender
{
+ [sNibLoaders removeObject:self];
+
if(didCleanup)
return;
didCleanup = YES;
@@ -101,7 +202,7 @@ - (void)cleanupAndRelease:(id)sender
if(isModal)
[NSApp stopModal];
- [self unlock];
+ [self wakeClient];
[self performSelector:@selector(delayedRelease:) withObject:self afterDelay:0];
}
@@ -121,11 +222,11 @@ - (void)performButtonClick:(id)sender
[parameters setObject:[sender title] forKey:@"returnButton"];
if([sender respondsToSelector:@selector(tag)])
[parameters setObject:[NSNumber numberWithInt:[sender tag]] forKey:@"returnCode"];
-
- [window orderOut:self];
- [self cleanupAndRelease:self];
+
+ [self wakeClient];
}
+// returnArgument: implementation. See <http://lists.macromates.com/pipermail/textmate/2006-November/015321.html>
- (NSMethodSignature*)methodSignatureForSelector:(SEL)aSelector
{
NSString* str = NSStringFromSelector(aSelector);
@@ -145,6 +246,7 @@ - (NSMethodSignature*)methodSignatureForSelector:(SEL)aSelector
return [super methodSignatureForSelector:aSelector];
}
+// returnArgument: implementation. See <http://lists.macromates.com/pipermail/textmate/2006-November/015321.html>
- (void)forwardInvocation:(NSInvocation*)invocation
{
NSString* str = NSStringFromSelector([invocation selector]);
@@ -160,9 +262,9 @@ - (void)forwardInvocation:(NSInvocation*)invocation
[dict setObject:arg forKey:[argNames objectAtIndex:i - 2]];
}
[parameters setObject:dict forKey:@"result"];
-
- [window orderOut:self];
- [self cleanupAndRelease:self];
+
+ // unblock the connection thread
+ [self wakeClient];
}
else
{
@@ -181,7 +283,10 @@ - (void)connectionDidDie:(NSNotification*)aNotification
- (NSDictionary*)instantiateNib:(NSNib*)aNib
{
- [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(connectionDidDie:) name:NSPortDidBecomeInvalidNotification object:nil];
+ if(not async)
+ {
+ [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(connectionDidDie:) name:NSPortDidBecomeInvalidNotification object:nil];
+ }
BOOL didInstantiate = NO;
@try {
@@ -201,8 +306,6 @@ - (NSDictionary*)instantiateNib:(NSNib*)aNib
if(window)
{
- [self lock];
-
if(center)
{
if(NSWindow* keyWindow = [NSApp keyWindow])
@@ -216,9 +319,19 @@ - (NSDictionary*)instantiateNib:(NSNib*)aNib
}
}
- [window makeKeyAndOrderFront:self];
- if(isModal && window)
- [NSApp runModalForWindow:window];
+ if(window != nil)
+ {
+ // Show the window
+ [window makeKeyAndOrderFront:self];
+
+ // TODO: When TextMate is capable of running script I/O in it's own thread(s), modal blocking
+ // can go away altogether.
+ if(isModal && window)
+ {
+ [NSApp runModalForWindow:window];
+ [self cleanupAndRelease:self];
+ }
+ }
}
else
{
@@ -248,8 +361,10 @@ - (int)textMateDialogServerProtocolVersion
return TextMateDialogServerProtocolVersion;
}
-- (id)showNib:(NSString*)aNibPath withParameters:(id)someParameters andInitialValues:(NSDictionary*)initialValues modal:(BOOL)flag center:(BOOL)shouldCenter
+- (id)showNib:(NSString*)aNibPath withParameters:(id)someParameters andInitialValues:(NSDictionary*)initialValues modal:(BOOL)flag center:(BOOL)shouldCenter async:(BOOL)async
{
+ id output;
+
if(![[NSFileManager defaultManager] fileExistsAtPath:aNibPath])
{
NSLog(@"%s nib file not found: %@", _cmd, aNibPath);
@@ -266,16 +381,88 @@ - (id)showNib:(NSString*)aNibPath withParameters:(id)someParameters andInitialVa
return nil;
}
- NibLoader* nibOwner = [[NibLoader alloc] initWithParameters:someParameters modal:flag center:shouldCenter];
+ NibLoader* nibOwner = [[NibLoader alloc] initWithParameters:someParameters modal:flag center:shouldCenter aysnc:async];
if(!nibOwner)
NSLog(@"%s couldn't create nib loader", _cmd);
[nibOwner performSelectorOnMainThread:@selector(instantiateNib:) withObject:nib waitUntilDone:YES];
- [Lock lock];
- [Lock unlock];
+
+ if(async)
+ {
+ output = [NSMutableDictionary dictionaryWithObjectsAndKeys:
+ [NSNumber numberWithUnsignedInt:[nibOwner token]], @"token",
+ [NSNumber numberWithUnsignedInt:0], @"returnCode",
+ nil];
+ }
+ else
+ {
+ output = someParameters;
+ }
+ return output;
+}
- return someParameters;
+// Async updates of parameters
+- (id)updateNib:(id)token withParameters:(id)someParameters
+{
+ NibLoader* nibLoader = [NibLoader nibLoaderForToken:[token intValue]];
+ int resultCode = -43;
+
+ if((nibLoader != nil) && [nibLoader isAsync])
+ {
+ [nibLoader updateParameters:someParameters];
+ resultCode = 0;
+ }
+
+ return [NSDictionary dictionaryWithObject:[NSNumber numberWithUnsignedInt:resultCode] forKey:@"returnCode"];
+}
+
+// Async close
+- (id)closeNib:(id)token
+{
+ NibLoader* nibLoader = [NibLoader nibLoaderForToken:[token intValue]];
+ int resultCode = -43;
+
+ if((nibLoader != nil) /*&& [nibLoader isAsync]*/)
+ {
+ [nibLoader connectionDidDie:nil];
+ resultCode = 0;
+ }
+
+ return [NSDictionary dictionaryWithObject:[NSNumber numberWithUnsignedInt:resultCode] forKey:@"returnCode"];
+}
+
+// Async get results
+- (id)retrieveNibResults:(id)token
+{
+ NibLoader* nibLoader = [NibLoader nibLoaderForToken:[token intValue]];
+ int resultCode = -43;
+ id results;
+
+ if((nibLoader != nil) /*&& [nibLoader isAsync]*/)
+ {
+ results = [nibLoader returnResult];
+ resultCode = 0;
+ }
+ else
+ {
+ results = [NSDictionary dictionaryWithObject:[NSNumber numberWithUnsignedInt:resultCode] forKey:@"returnCode"];
+ }
+
+ return results;
+}
+
+// Async list
+- (id)listNibTokens
+{
+ NSMutableDictionary* dict = [NSMutableDictionary dictionary];
+ NSArray* outNibArray = [NibLoader nibDescriptions];
+ int resultCode = 0;
+
+ [dict setObject:outNibArray forKey:@"nibs"];
+ [dict setObject:[NSNumber numberWithUnsignedInt:resultCode] forKey:@"returnCode"];
+ return dict;
}
+
- (id)showMenuWithOptions:(NSDictionary*)someOptions
{
NSMutableDictionary* res = [[someOptions mutableCopy] autorelease];
9 Dialog.xcodeproj/project.pbxproj
View
@@ -10,6 +10,7 @@
174DE8E30AF5A1A60060BD80 /* Carbon.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 174DE8E20AF5A1A60060BD80 /* Carbon.framework */; };
177E4DA309132A0F0064163D /* Dialog.mm in Sources */ = {isa = PBXBuildFile; fileRef = 177E4DA209132A0F0064163D /* Dialog.mm */; };
178E031A0AED82FE0005685F /* ValueTransformers.mm in Sources */ = {isa = PBXBuildFile; fileRef = 178E03190AED82FE0005685F /* ValueTransformers.mm */; };
+ 831625970B2752AF002857D1 /* TMDSemaphore.mm in Sources */ = {isa = PBXBuildFile; fileRef = 831625950B2752AF002857D1 /* TMDSemaphore.mm */; };
8D5B49B0048680CD000E48DA /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 089C167DFE841241C02AAC07 /* InfoPlist.strings */; };
8D5B49B4048680CD000E48DA /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1058C7ADFEA557BF11CA2CBB /* Cocoa.framework */; };
/* End PBXBuildFile section */
@@ -25,6 +26,8 @@
178E03180AED82FE0005685F /* ValueTransformers.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ValueTransformers.h; sourceTree = "<group>"; };
178E03190AED82FE0005685F /* ValueTransformers.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ValueTransformers.mm; sourceTree = "<group>"; };
32DBCF630370AF2F00C91783 /* Dialog_Prefix.pch */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Dialog_Prefix.pch; sourceTree = "<group>"; };
+ 831625950B2752AF002857D1 /* TMDSemaphore.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = TMDSemaphore.mm; sourceTree = "<group>"; };
+ 831625960B2752AF002857D1 /* TMDSemaphore.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TMDSemaphore.h; sourceTree = "<group>"; };
8D5B49B6048680CD000E48DA /* Dialog.tmplugin */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = Dialog.tmplugin; sourceTree = BUILT_PRODUCTS_DIR; };
8D5B49B7048680CD000E48DA /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xml; path = Info.plist; sourceTree = "<group>"; };
D2F7E65807B2D6F200F64583 /* CoreData.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreData.framework; path = /System/Library/Frameworks/CoreData.framework; sourceTree = "<absolute>"; };
@@ -76,6 +79,8 @@
08FB77AFFE84173DC02AAC07 /* Classes */ = {
isa = PBXGroup;
children = (
+ 831625950B2752AF002857D1 /* TMDSemaphore.mm */,
+ 831625960B2752AF002857D1 /* TMDSemaphore.h */,
177E4DA209132A0F0064163D /* Dialog.mm */,
177E4DA109132A0F0064163D /* Dialog.h */,
178E03190AED82FE0005685F /* ValueTransformers.mm */,
@@ -147,9 +152,12 @@
089C1669FE841209C02AAC07 /* Project object */ = {
isa = PBXProject;
buildConfigurationList = 1DEB913E08733D840010E9CD /* Build configuration list for PBXProject "Dialog" */;
+ compatibilityVersion = "Xcode 2.4";
hasScannedForEncodings = 1;
mainGroup = 089C166AFE841209C02AAC07 /* Dialog */;
projectDirPath = "";
+ projectRoot = "";
+ shouldCheckCompatibility = 1;
targets = (
8D5B49AC048680CD000E48DA /* Dialog */,
);
@@ -190,6 +198,7 @@
files = (
177E4DA309132A0F0064163D /* Dialog.mm in Sources */,
178E031A0AED82FE0005685F /* ValueTransformers.mm in Sources */,
+ 831625970B2752AF002857D1 /* TMDSemaphore.mm in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
24 TMDSemaphore.h
View
@@ -0,0 +1,24 @@
+//
+// TMDSemaphore.mm
+// TM dialog server
+//
+// Created by Chris Thomas on 2006-12-06.
+//
+
+#include <semaphore.h>
+
+@interface TMDSemaphore : NSObject
+{
+ sem_t* semaphore;
+ NSString* name;
+}
+
++ (TMDSemaphore*)semaphoreForTokenInt:(int)token;
++ (TMDSemaphore*)semaphoreForTokenString:(const char *)token;
++ (TMDSemaphore*)semaphoreWithName:(NSString *)name;
+
+- (id)initWithName:(NSString *)name;
+
+- (void)wait;
+- (void)stopWaiting;
+@end
73 TMDSemaphore.mm
View
@@ -0,0 +1,73 @@
+//
+// TMDSemaphore.mm
+// TM dialog server
+//
+// Created by Chris Thomas on 2006-12-06.
+//
+
+#import "TMDSemaphore.h"
+
+@implementation TMDSemaphore
+
++ (NSString *)nameForToken:(int)token
+{
+ return [NSString stringWithFormat:@"/tm_dialog async/%@/%d", NSUserName(), token];
+}
+
++ (NSString *)nameForTokenString:(const char *)token
+{
+ return [NSString stringWithFormat:@"/tm_dialog async/%@/%s", NSUserName(), token];
+}
+
++ (TMDSemaphore*)semaphoreForTokenString:(const char *)token
+{
+ return [self semaphoreWithName:[self nameForTokenString:token]];
+}
+
++ (TMDSemaphore*)semaphoreForTokenInt:(int)token
+{
+ return [self semaphoreWithName:[self nameForToken:token]];
+}
+
++ (TMDSemaphore*)semaphoreWithName:(NSString *)name
+{
+ return [[[self alloc] initWithName:name] autorelease];
+}
+
+- (id)initWithName:(NSString *)inName;
+{
+ name = [inName copy];
+ if(self = [super init])
+ {
+ semaphore = sem_open([name UTF8String], O_CREAT, 0600, 0);
+ if(semaphore == (sem_t*)SEM_FAILED)
+ {
+ int error = errno;
+ fprintf(stderr, "error %d (%s) opening sem %s.\n", error, strerror(error), [name UTF8String]);
+ fflush(stderr);
+ }
+ }
+ return self;
+}
+
+- (void)dealloc
+{
+ if(semaphore != (sem_t*)SEM_FAILED)
+ {
+ sem_close(semaphore);
+ sem_unlink([name UTF8String]);
+ }
+ [name release];
+ [super dealloc];
+}
+
+- (void)wait
+{
+ sem_wait(semaphore);
+}
+
+- (void)stopWaiting
+{
+ sem_post(semaphore);
+}
+@end
388 tm_dialog.mm
View
@@ -1,5 +1,11 @@
/*
- g++ -Wmost -arch ppc -arch i386 -isysroot /Developer/SDKs/MacOSX10.4u.sdk -DDATE=\"`date +%Y-%m-%d`\" -Os "$TM_FILEPATH" -o "$TM_SUPPORT_PATH/bin/tm_dialog" -framework Foundation && strip "$TM_SUPPORT_PATH/bin/tm_dialog"
+ Weird options added by chris:
+ -mdynamic-no-pic removes useless symbol indirection code, reducing executable size. It does _not_ work for code that may need relocation at runtime, i.e. bundles and frameworks.
+ In theory -Wl,-s would avoid a separate invocation of the strip tool, but it ends up stripping bits we actually need.
+ -dead_strip tells the linker to remove unused functions and data.
+
+ g++ -Wmost -arch ppc -arch i386 -mdynamic-no-pic -dead_strip -isysroot /Developer/SDKs/MacOSX10.4u.sdk -DDATE=\"`date +%Y-%m-%d`\" -Os "$TM_FILEPATH" -o "$TM_SUPPORT_PATH/bin/tm_dialog" -framework Foundation && strip "$TM_SUPPORT_PATH/bin/tm_dialog"
+
*/
#import <Cocoa/Cocoa.h>
#import <getopt.h>
@@ -13,6 +19,8 @@
#import <string>
#import <sys/stat.h>
+#import "TMDSemaphore.h"
+#include "TMDSemaphore.mm" // TODO we should really export this from the plugin instead and link against the plugin
#import "Dialog.h"
char const* AppName = "tm_dialog";
@@ -23,6 +31,29 @@
return sscanf("$Revision$", "$%*[^:]: %s $", res) == 1 ? res : "???";
}
+id read_property_list_from_file (int fd)
+{
+ NSMutableData* data = [NSMutableData data];
+ id plist;
+
+ char buf[1024];
+ while(size_t len = read(fd, buf, sizeof(buf)))
+ [data appendBytes:buf length:len];
+
+ plist = [data length] ? [NSPropertyListSerialization propertyListFromData:data mutabilityOption:NSPropertyListMutableContainersAndLeaves format:nil errorDescription:NULL] : [NSMutableDictionary dictionary];
+ return plist;
+}
+
+id read_property_list_from_string (const char* parameters)
+{
+ NSMutableData* data = [NSMutableData data];
+ [data appendBytes:parameters length:strlen(parameters)];
+
+ id plist = [data length] ? [NSPropertyListSerialization propertyListFromData:data mutabilityOption:NSPropertyListMutableContainersAndLeaves format:nil errorDescription:NULL] : [NSMutableDictionary dictionary];
+
+ return plist;
+}
+
bool output_property_list (id plist)
{
bool res = false;
@@ -43,43 +74,202 @@ bool output_property_list (id plist)
return res;
}
-int contact_server (std::string nibName, NSMutableDictionary* someParameters, NSDictionary* initialValues, bool center, bool modal, bool quiet)
+// validate_proxy: return an instance of the TM dialog server proxy object. Return false (and write details to stderr)
+// if the TM dialog server is unavailable or the protocol version doesn't match.
+bool validate_proxy (id & outProxy)
{
- int res = -1;
+ static bool proxyValid = false;
+ static id proxy = nil;
+
+ // One shot validate -- if it isn't valid now, presumably it won't be ever
+ // (during the very short life of an instance of this tool)
+ if(not proxyValid)
+ {
+ proxy = [NSConnection rootProxyForConnectionWithRegisteredName:@"TextMate dialog server" host:nil];
+ [proxy setProtocolForProxy:@protocol(TextMateDialogServerProtocol)];
+
+ if([proxy textMateDialogServerProtocolVersion] == TextMateDialogServerProtocolVersion)
+ {
+ proxyValid = true;
+ }
+ else
+ {
+ if(proxy)
+ {
+ int pluginVersion = [proxy textMateDialogServerProtocolVersion];
+ int toolVersion = TextMateDialogServerProtocolVersion;
+ if(pluginVersion < toolVersion)
+ {
+ fprintf(stderr, "%s: you have updated the tm_dialog tool to v%d but the Dialog plug-in running is still at v%d.\n", AppName, toolVersion, pluginVersion);
+ fprintf(stderr, "%s: either checkout the PlugIns folder from the repository or remove your checkout of the Support folder.\n", AppName);
+ fprintf(stderr, "%s: if you did checkout the PlugIns folder, you need to relaunch TextMate to load the new plug-in.\n", AppName);
+ }
+ else
+ {
+ fprintf(stderr, "%s: you have updated the Dialog plug-in to v%d but the tm_dialog tool is still at v%d\n", AppName, pluginVersion, toolVersion);
+ }
+ }
+ else
+ {
+ fprintf(stderr, "%s: failed to establish connection with TextMate.\n", AppName);
+ }
+ }
+ }
+
+ outProxy = proxy;
+ return proxyValid;
+}
- id proxy = [NSConnection rootProxyForConnectionWithRegisteredName:@"TextMate dialog server" host:nil];
- [proxy setProtocolForProxy:@protocol(TextMateDialogServerProtocol)];
+// contact_server_async_update: update (a subset of) the binding values.
+int contact_server_async_update (const char* token, NSMutableDictionary* someParameters)
+{
+ id proxy;
+ int returnCode = -1;
- if([proxy textMateDialogServerProtocolVersion] == TextMateDialogServerProtocolVersion)
+ if(validate_proxy(proxy))
{
- NSString* aNibPath = [NSString stringWithUTF8String:nibName.c_str()];
+ id result = [proxy updateNib:[NSString stringWithUTF8String:token] withParameters:someParameters];
+ returnCode = [[result objectForKey:@"returnCode"] intValue];
- NSDictionary* parameters = (NSDictionary*)[proxy showNib:aNibPath withParameters:someParameters andInitialValues:initialValues modal:modal center:center];
- if(!quiet)
- output_property_list(parameters);
- res = [[parameters objectForKey:@"returnCode"] intValue];
+ if(returnCode == -43)
+ {
+ fprintf(stderr, "%s (async_update): Window '%s' doesn't exist\n", AppName, token);
+ }
}
- else
+ return returnCode;
+}
+
+// contact_server_async_close: close the window
+int contact_server_async_close (const char* token)
+{
+ id proxy;
+ int returnCode = -1;
+
+ if(validate_proxy(proxy))
+ {
+ id result = [proxy closeNib:[NSString stringWithUTF8String:token]];
+ returnCode = [[result objectForKey:@"returnCode"] intValue];
+
+ if(returnCode == -43)
+ {
+ fprintf(stderr, "%s (async_close): Window '%s' doesn't exist\n", AppName, token);
+ }
+ }
+
+ return returnCode;
+}
+
+// contact_server_async_wait: block until returnArgument:, performButtonClick:, or the window is closed,
+// then output the resulting plist returned by the dialog server.
+int contact_server_async_wait (const char* token)
+{
+ id proxy;
+ int returnCode = -1;
+
+ if(validate_proxy(proxy))
+ {
+ id result;
+ TMDSemaphore * semaphore = [TMDSemaphore semaphoreForTokenString:token];
+
+// fprintf(stderr, "%s blocking for window '%s' \n", AppName, token);
+// fflush(stdout);
+ [semaphore wait];
+// fprintf(stderr, "%s awake for window '%s' \n", AppName, token);
+// fflush(stdout);
+ result = [proxy retrieveNibResults:[NSString stringWithUTF8String:token]];
+ returnCode = [[result objectForKey:@"returnCode"] intValue];
+
+ if(returnCode == -43)
+ {
+ fprintf(stderr, "%s (async_wait): Window '%s' doesn't exist\n", AppName, token);
+ }
+
+ output_property_list(result);
+ }
+
+ return returnCode;
+}
+
+
+// contact_server_async_list: print a list of windows to stdout
+int contact_server_async_list ()
+{
+ id proxy;
+ int returnCode = -1;
+
+ if(validate_proxy(proxy))
{
- if(proxy)
+ id result = [proxy listNibTokens];
+ returnCode = [[result objectForKey:@"returnCode"] intValue];
+
+ if(returnCode != 0)
{
- int pluginVersion = [proxy textMateDialogServerProtocolVersion];
- int toolVersion = TextMateDialogServerProtocolVersion;
- if(pluginVersion < toolVersion)
+ fprintf(stderr, "%s: Unknown error code '%d'\n", AppName, returnCode);
+ }
+ else
+ {
+ NSArray* nibs = [result objectForKey:@"nibs"];
+ enumerate(nibs, NSDictionary * nib)
+ {
+ NSString* windowTitleString = [nib objectForKey:@"windowTitle"];
+ const char* windowTitleC = "<no title>";
+ int windowToken = [[nib objectForKey:@"token"] intValue];
+
+ if(windowTitleString != nil
+ && not [windowTitleString isEqualToString:@""])
+ {
+ windowTitleC = [windowTitleString UTF8String];
+ }
+ fprintf(stdout, "%d (%s)\n", windowToken, windowTitleC);
+ }
+ }
+ }
+
+ return returnCode;
+}
+
+// contact_server_show_nib: instantiate the nib inside TM
+int contact_server_show_nib (std::string nibName, NSMutableDictionary* someParameters, NSDictionary* initialValues, bool center, bool modal, bool quiet, bool async)
+{
+ int res = -1;
+ id proxy;
+
+ if(validate_proxy(proxy))
+ {
+ NSString* aNibPath = [NSString stringWithUTF8String:nibName.c_str()];
+ NSDictionary* parameters = (NSDictionary*)[proxy showNib:aNibPath withParameters:someParameters andInitialValues:initialValues modal:modal center:center async:async];
+
+ const char* token = [[NSString stringWithFormat:@"%@", [parameters objectForKey:@"token"]] UTF8String];
+
+ res = [[parameters objectForKey:@"returnCode"] intValue];
+
+ // Async mode: print just the token for the new window
+ if(async)
+ {
+ if(res == 0)
{
- fprintf(stderr, "%s: you have updated the tm_dialog tool to v%d but the Dialog plug-in running is still at v%d.\n", AppName, toolVersion, pluginVersion);
- fprintf(stderr, "%s: either checkout the PlugIns folder from the repository or remove your checkout of the Support folder.\n", AppName);
- fprintf(stderr, "%s: if you did checkout the PlugIns folder, you need to relaunch TextMate to load the new plug-in.\n", AppName);
+ fprintf(stdout, "%d\n", [[parameters objectForKey:@"token"] intValue]);
}
else
{
- fprintf(stderr, "%s: you have updated the Dialog plug-in to v%d but the tm_dialog tool is still at v%d\n", AppName, pluginVersion, toolVersion);
+ fprintf(stderr, "Error %d creating window\n", res);
+ }
+ }
+ else if(modal)
+ {
+ if(not quiet)
+ {
+ output_property_list(parameters);
}
}
else
{
- fprintf(stderr, "%s: failed to establish connection with TextMate.\n", AppName);
+ // Not async, not modal. The task had better be detached from TM, or TM will hang
+ // until the task is killed. Wait until something happens.
+ res = contact_server_async_wait(token);
+ contact_server_async_close(token);
}
+
}
return res;
}
@@ -88,15 +278,33 @@ void usage ()
{
fprintf(stderr,
"%1$s r%2$s (" DATE ")\n"
- "Usage: %1$s [-cmqp] nib_file\n"
+ "Usage: %1$s [-cmqpaxt] nib_file\n"
"Usage: %1$s [-p] -u\n"
+ "\n"
"Options:\n"
- " -c, --center Center the window on screen.\n"
- " -d, --defaults <plist> Register initial values for user defaults.\n"
- " -m, --modal Show window as modal.\n"
- " -q, --quiet Do not write result to stdout.\n"
- " -p, --parameters <plist> Provide parameters as a plist.\n"
- " -u, --menu Treat parameters as a menu structure.\n"
+ " -c, --center Center the window on screen.\n"
+ " -d, --defaults <plist> Register initial values for user defaults.\n"
+ " -m, --modal Show window as modal (other windows will be inaccessible).\n"
+ " -q, --quiet Do not write result to stdout.\n"
+ " -p, --parameters <plist> Provide parameters as a plist.\n"
+ " -u, --menu Treat parameters as a menu structure.\n"
+ "\n Async Window Subcommands\n"
+ " -a, --async-window Displays the window and returns a reference token for it\n"
+ " in the output property list.\n"
+ " -x, --close-window <token> Close and release an async window.\n"
+ " -t, --update-window <token> Update an async window with new parameter values.\n"
+ " Use the --parameters argument (or stdin) to specify the\n"
+ " updated parameters.\n"
+ " -l, --list-windows List async window tokens.\n"
+ " -w, --wait-for-input <token> Wait for user input from the given async window.\n"
+ "\nImportant Note\n"
+ "If you DO NOT use the -m/--modal option,\n"
+ "OR you create an async window and then use the wait-for-input subcommand,\n"
+ "you must run tm_dialog in a detached/backgrounded process (`mycommand 2&>1 &` in bash).\n"
+ "Otherwise, TextMate's UI thread will hang, waiting for your command to complete.\n"
+ "You can recover from the hang by killing the tm_dialog process in Terminal.\n"
+ "Better not to cause it in the first place, though. :)\n"
+ "\n"
"", AppName, current_version());
}
@@ -135,6 +343,24 @@ void usage ()
return NULL;
}
+id read_property_list_argument(const char* parameters)
+{
+ id plist = nil;
+ if(parameters)
+ {
+ plist = read_property_list_from_string(parameters);
+ }
+ else
+ {
+// if(isatty(STDIN_FILENO) == 0)
+ {
+ plist = read_property_list_from_file(STDIN_FILENO);
+ }
+ }
+
+ return plist;
+}
+
int main (int argc, char* argv[])
{
NSAutoreleasePool* pool = [NSAutoreleasePool new];
@@ -142,6 +368,16 @@ int main (int argc, char* argv[])
extern int optind;
extern char* optarg;
+ enum AsyncWindowAction
+ {
+ kSyncCreate,
+ kAsyncCreate,
+ kAsyncClose,
+ kAsyncUpdate,
+ kAsyncList,
+ kAsyncWait
+ };
+
static struct option const longopts[] = {
{ "center", no_argument, 0, 'c' },
{ "defaults", required_argument, 0, 'd' },
@@ -149,14 +385,22 @@ int main (int argc, char* argv[])
{ "parameters", required_argument, 0, 'p' },
{ "quiet", no_argument, 0, 'q' },
{ "menu", no_argument, 0, 'u' },
+ { "async-window", no_argument, 0, 'a' },
+ { "close-window", required_argument, 0, 'x' },
+ { "update-window", required_argument, 0, 't' },
+ { "wait-for-input", required_argument, 0, 'w' },
+ { "list-windows", no_argument, 0, 'l' },
{ 0, 0, 0, 0 }
};
bool center = false, modal = false, quiet = false, menu = false;
char const* parameters = NULL;
char const* defaults = NULL;
+ char const* token = NULL;
char ch;
- while((ch = getopt_long(argc, argv, "cd:mp:qu", longopts, NULL)) != -1)
+ AsyncWindowAction asyncWindowAction = kSyncCreate;
+
+ while((ch = getopt_long(argc, argv, "cd:mp:quax:t:w:l", longopts, NULL)) != -1)
{
switch(ch)
{
@@ -166,6 +410,13 @@ int main (int argc, char* argv[])
case 'p': parameters = optarg; break;
case 'q': quiet = true; break;
case 'u': menu = true; break;
+
+ case 'a': asyncWindowAction = kAsyncCreate; break;
+ case 'x': asyncWindowAction = kAsyncClose; token = optarg; break;
+ case 't': asyncWindowAction = kAsyncUpdate; token = optarg; break;
+ case 'w': asyncWindowAction = kAsyncWait; token = optarg; break;
+ case 'l': asyncWindowAction = kAsyncList; break;
+
default: usage(); break;
}
}
@@ -173,48 +424,61 @@ int main (int argc, char* argv[])
argc -= optind;
argv += optind;
- // ===================
- // = read parameters =
- // ===================
-
- NSMutableData* data = [NSMutableData data];
- if(parameters)
- {
- [data appendBytes:parameters length:strlen(parameters)];
- }
- else
+ int res = -1;
+ if(not menu)
{
- if(isatty(STDIN_FILENO) == 0)
+ id initialValues = defaults ? [NSPropertyListSerialization propertyListFromData:[NSData dataWithBytes:defaults length:strlen(defaults)] mutabilityOption:NSPropertyListImmutable format:nil errorDescription:NULL] : nil;
+
+ if(asyncWindowAction != kSyncCreate)
{
- char buf[1024];
- while(size_t len = read(STDIN_FILENO, buf, sizeof(buf)))
- [data appendBytes:buf length:len];
+ if(modal)
+ fprintf(stderr, "%s: warning: Ignoring 'modal' option; async windows cannot be modal\n", AppName);
+
+ if(quiet)
+ fprintf(stderr, "%s: warning: Ignoring 'quiet' option for async window; use a normal window instead.\n", AppName);
}
- }
-
- id plist = [data length] ? [NSPropertyListSerialization propertyListFromData:data mutabilityOption:NSPropertyListMutableContainersAndLeaves format:nil errorDescription:NULL] : [NSMutableDictionary dictionary];
- int res = -1;
- if(argc == 1)
- {
- id initialValues = defaults ? [NSPropertyListSerialization propertyListFromData:[NSData dataWithBytes:defaults length:strlen(defaults)] mutabilityOption:NSPropertyListImmutable format:nil errorDescription:NULL] : nil;
- res = contact_server(find_nib(argv[0]), plist, initialValues, center, modal, quiet);
+ switch(asyncWindowAction)
+ {
+ case kSyncCreate:
+ case kAsyncCreate:
+ if(argc == 1)
+ {
+ id plist = read_property_list_argument(parameters);
+ res = contact_server_show_nib(find_nib(argv[0]), plist, initialValues, center, modal, quiet, (asyncWindowAction == kAsyncCreate));
+ }
+ else
+ {
+ usage();
+ }
+ break;
+ case kAsyncUpdate:
+ {
+ id plist = read_property_list_argument(parameters);
+ res = contact_server_async_update(token, plist);
+ } break;
+ case kAsyncClose:
+ res = contact_server_async_close(token);
+ break;
+ case kAsyncWait:
+ res = contact_server_async_wait(token);
+ break;
+ case kAsyncList:
+ res = contact_server_async_list();
+ break;
+ default:
+ usage();
+ break;
+ }
}
- else if(menu)
+ else if(argc == 0)
{
- id proxy = [NSConnection rootProxyForConnectionWithRegisteredName:@"TextMate dialog server" host:nil];
- [proxy setProtocolForProxy:@protocol(TextMateDialogServerProtocol)];
-
- if([proxy textMateDialogServerProtocolVersion] == TextMateDialogServerProtocolVersion)
+ id proxy;
+ if(validate_proxy(proxy))
{
+ id plist = read_property_list_argument(parameters);
output_property_list([proxy showMenuWithOptions:plist]);
}
- else
- {
- if(proxy)
- fprintf(stderr, "%s: server version at v%d, this tool at v%d (they need to match)\n", AppName, [proxy textMateDialogServerProtocolVersion], TextMateDialogServerProtocolVersion);
- else fprintf(stderr, "%s: failed to establish connection with TextMate.\n", AppName);
- }
}
else
{
Please sign in to comment.
Something went wrong with that request. Please try again.