diff --git a/Applications/TextMate/resources/Default.tmProperties b/Applications/TextMate/resources/Default.tmProperties index 3fe325ef..ba458b93 100644 --- a/Applications/TextMate/resources/Default.tmProperties +++ b/Applications/TextMate/resources/Default.tmProperties @@ -11,11 +11,12 @@ windowTitleSCM = '${TM_SCM_BRANCH:+ ($TM_SCM_NAME: $TM_SCM_BRANCH)}' windowTitleProject = '${projectDirectory:+ — ${projectDirectory/^.*\///}}' windowTitle = '$TM_DISPLAYNAME$windowTitleProject$windowTitleSCM' -LANG = "en_US.UTF-8" -LC_CTYPE = "en_US.UTF-8" -TM_APP_PATH = "${CWD/\/Contents\/Resources$//}" -TM_MATE = "$CWD/mate" -TM_QUERY = "$CWD/tm_query" +LANG = "en_US.UTF-8" +LC_CTYPE = "en_US.UTF-8" +TM_APP_PATH = "${CWD/\/Contents\/Resources$//}" +TM_MATE = "$CWD/mate" +TM_QUERY = "$CWD/tm_query" +TM_SCM_COMMIT_WINDOW = "$CWD/commit" [ attr.untitled ] fileType = text.plain diff --git a/Applications/TextMate/src/AppController.mm b/Applications/TextMate/src/AppController.mm index 754ecac2..524609f0 100644 --- a/Applications/TextMate/src/AppController.mm +++ b/Applications/TextMate/src/AppController.mm @@ -8,6 +8,7 @@ #import #import #import +#import #import #import #import @@ -297,6 +298,8 @@ BOOL HasDocumentWindow (NSArray* windows) [[CrashReporter sharedInstance] applicationDidFinishLaunching:aNotification]; [[CrashReporter sharedInstance] postNewCrashReportsToURLString:REST_API @"/crashes"]; + [OakCommitWindowServer sharedInstance]; // Setup server + self.didFinishLaunching = YES; } diff --git a/Applications/TextMate/target b/Applications/TextMate/target index b2d377d5..2d557514 100644 --- a/Applications/TextMate/target +++ b/Applications/TextMate/target @@ -1,11 +1,12 @@ SOURCES = src/*.{cc,mm} -CP_Resources = resources/* icons/*.icns about/* @PrivilegedTool @mate @tm_query +CP_Resources = resources/* icons/*.icns about/* @PrivilegedTool @mate @tm_query @commit CP_SharedSupport = support/* CP_PlugIns = @Dialog @Dialog2 CP_Library/QuickLook = @TextMateQL FLAGS += -DREST_API='@"$rest_api"' LINK += bundles cf command document editor io network ns plist settings text kvdb LINK += BundleMenu BundleEditor BundlesManager CrashReporter DocumentWindow Find HTMLOutputWindow OakAppKit OakFilterList OakFoundation OakSystem OakTextView Preferences SoftwareUpdate updater license +LINK += CommitWindow FRAMEWORKS = Cocoa HTML_HEADER = templates/header.html HTML_FOOTER = templates/footer.html diff --git a/Applications/commit/src/commit.mm b/Applications/commit/src/commit.mm new file mode 100644 index 00000000..02841e13 --- /dev/null +++ b/Applications/commit/src/commit.mm @@ -0,0 +1,85 @@ +#include +#include + +static double const AppVersion = 1.0; +static size_t const AppRevision = APP_REVISION; + +@interface OakCommitWindowClient : NSObject +@property (nonatomic) NSString* portName; +@property (nonatomic) NSConnection* connection; +@property (nonatomic) NSInteger returnCode; +@end + +@implementation OakCommitWindowClient +- (id)init +{ + if(self = [super init]) + { + _portName = [NSString stringWithFormat:@"com.macromates.commit-window-client.%d", getpid()]; + _connection = [NSConnection new]; + + [_connection setRootObject:self]; + if([_connection registerName:_portName] == NO) + { + fprintf(stderr, "%s: failed vending object as ‘%s’\n", getprogname(), [_portName UTF8String]); + return nil; + } + } + return self; +} + +- (void)connectFromServerWithOptions:(NSDictionary*)someOptions +{ + if(NSString* err = someOptions[kOakCommitWindowStandardError]) + fprintf(stderr, "%s", [err UTF8String]); + + if(NSString* out = someOptions[kOakCommitWindowStandardOutput]) + fprintf(stdout, "%s", [out UTF8String]); + + _returnCode = [someOptions[kOakCommitWindowReturnCode] intValue]; + + // Tear down the connection in next event loop iteration. + // This should allow the sender to get a reply. + [self performSelector:@selector(setConnection:) withObject:nil afterDelay:0]; +} +@end + +int main (int argc, char* argv[]) +{ + if(argc == 2 && (strcmp(argv[1], "-v") == 0 || strcmp(argv[1], "--version") == 0)) + { + fprintf(stderr, "%1$s %2$.1f (" COMPILE_DATE " revision %3$zu)\n", getprogname(), AppVersion, AppRevision); + return 0; + } + + @autoreleasepool { + if(OakCommitWindowClient* client = [[OakCommitWindowClient alloc] init]) + { + NSMutableArray* arg = [NSMutableArray array]; + for(size_t i = 0; i < argc; ++i) + [arg addObject:[NSString stringWithUTF8String:argv[i]]]; + + NSDictionary* plist = @{ + kOakCommitWindowClientPortName : client.portName, + kOakCommitWindowArguments : arg, + kOakCommitWindowEnvironment : [[NSProcessInfo processInfo] environment], + }; + + if(id proxy = [NSConnection rootProxyForConnectionWithRegisteredName:kOakCommitWindowServerConnectionName host:nil]) + { + [proxy setProtocolForProxy:@protocol(OakCommitWindowServerProtocol)]; + [proxy connectFromClientWithOptions:plist]; + + while(client.connection) + [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]; + + return client.returnCode; + } + else + { + fprintf(stderr, "%s: failed connecting to ‘%s’\n", getprogname(), [kOakCommitWindowServerConnectionName UTF8String]); + } + } + } + return 0; +} diff --git a/Applications/commit/target b/Applications/commit/target new file mode 100644 index 00000000..3a5723fc --- /dev/null +++ b/Applications/commit/target @@ -0,0 +1 @@ +SOURCES = src/*.mm diff --git a/Frameworks/CommitWindow/src/CommitWindow.h b/Frameworks/CommitWindow/src/CommitWindow.h new file mode 100644 index 00000000..e2541ddd --- /dev/null +++ b/Frameworks/CommitWindow/src/CommitWindow.h @@ -0,0 +1,21 @@ +#import + +static NSString* const kOakCommitWindowServerConnectionName = @"com.macromates.textmate.commit-window-server"; +static NSString* const kOakCommitWindowClientPortName = @"clientPortName"; +static NSString* const kOakCommitWindowArguments = @"arguments"; +static NSString* const kOakCommitWindowEnvironment = @"environment"; +static NSString* const kOakCommitWindowStandardOutput = @"stdout"; +static NSString* const kOakCommitWindowStandardError = @"stderr"; +static NSString* const kOakCommitWindowReturnCode = @"returnCode"; + +@protocol OakCommitWindowClientProtocol +- (void)connectFromServerWithOptions:(NSDictionary*)someOptions; +@end + +@protocol OakCommitWindowServerProtocol +- (void)connectFromClientWithOptions:(NSDictionary*)someOptions; +@end + +PUBLIC @interface OakCommitWindowServer : NSObject ++ (instancetype)sharedInstance; +@end diff --git a/Frameworks/CommitWindow/src/CommitWindow.mm b/Frameworks/CommitWindow/src/CommitWindow.mm new file mode 100644 index 00000000..62188e67 --- /dev/null +++ b/Frameworks/CommitWindow/src/CommitWindow.mm @@ -0,0 +1,192 @@ +#import "CommitWindow.h" +#import + +@interface OakCommitWindow : NSWindowController +@property (nonatomic) NSArray* paths; +@property (nonatomic) NSString* clientPortName; +@property (nonatomic) NSScrollView* scrollView; +@property (nonatomic) NSTableView* tableView; +@property (nonatomic) OakCommitWindow* retainedSelf; +@end + +@implementation OakCommitWindow +- (id)init +{ + if((self = [super init])) + { + _paths = @[ + @{ @"path" : @"/path/to/foo" }, + @{ @"path" : @"/path/to/bar" }, + ]; + + NSTableColumn* tableColumn = [[NSTableColumn alloc] initWithIdentifier:@"path"]; + tableColumn.editable = NO; + tableColumn.dataCell = [[NSTextFieldCell alloc] initTextCell:@""]; + [tableColumn.dataCell setLineBreakMode:NSLineBreakByTruncatingMiddle]; + + NSTableView* tableView = [[NSTableView alloc] initWithFrame:NSZeroRect]; + [tableView addTableColumn:tableColumn]; + tableView.headerView = nil; + tableView.focusRingType = NSFocusRingTypeNone; + tableView.usesAlternatingRowBackgroundColors = YES; + tableView.doubleAction = @selector(didDoubleClickTableView:); + tableView.target = self; + tableView.dataSource = self; + _tableView = tableView; + + _scrollView = [[NSScrollView alloc] initWithFrame:NSZeroRect]; + _scrollView.hasVerticalScroller = YES; + _scrollView.hasHorizontalScroller = NO; + _scrollView.autohidesScrollers = YES; + _scrollView.borderType = NSNoBorder; + _scrollView.documentView = _tableView; + + self.window = [[NSWindow alloc] initWithContentRect:NSMakeRect(600, 700, 400, 500) styleMask:(NSTitledWindowMask|NSClosableWindowMask|NSResizableWindowMask|NSMiniaturizableWindowMask|NSTexturedBackgroundWindowMask) backing:NSBackingStoreBuffered defer:NO]; + self.window.delegate = self; + self.window.level = NSFloatingWindowLevel; + self.window.releasedWhenClosed = NO; + self.window.title = @"Commit"; + + NSButton* commitButton = OakCreateButton(@"Commit", NSTexturedRoundedBezelStyle); + NSButton* cancelButton = OakCreateButton(@"Cancel", NSTexturedRoundedBezelStyle); + + commitButton.action = @selector(performCommit:); + cancelButton.action = @selector(cancel:); + + NSDictionary* views = @{ + @"scrollView" : self.scrollView, + @"bottomDivider" : OakCreateHorizontalLine([NSColor grayColor], [NSColor lightGrayColor]), + @"cancel" : cancelButton, + @"commit" : commitButton, + }; + + NSView* contentView = self.window.contentView; + for(NSView* view in [views allValues]) + { + [view setTranslatesAutoresizingMaskIntoConstraints:NO]; + [contentView addSubview:view]; + } + + [contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[scrollView(==bottomDivider)]|" options:0 metrics:nil views:views]]; + [contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:[cancel]-[commit]-(8)-|" options:NSLayoutFormatAlignAllBaseline metrics:nil views:views]]; + [contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-[scrollView(>=50)][bottomDivider]-(5)-[commit]-(6)-|" options:0 metrics:nil views:views]]; + + self.window.defaultButtonCell = commitButton.cell; + } + return self; +} + +- (void)showWindow:(id)sender +{ + [self.window recalculateKeyViewLoop]; + [super showWindow:sender]; + + self.retainedSelf = self; +} + +- (void)windowWillClose:(NSNotification*)aNotification +{ + [self sendCommitMessageToClient:YES]; +} + +- (void)sendCommitMessageToClient:(BOOL)success +{ + if(!self.clientPortName) // Reply already sent + return; + + if(id proxy = [NSConnection rootProxyForConnectionWithRegisteredName:self.clientPortName host:nil]) + { + [proxy setProtocolForProxy:@protocol(OakCommitWindowClientProtocol)]; + + if(success) + { + [proxy connectFromServerWithOptions:@{ + kOakCommitWindowStandardOutput : @"Hello world", + kOakCommitWindowStandardError : @"", + kOakCommitWindowReturnCode : @0, + }]; + } + else + { + [proxy connectFromServerWithOptions:@{ + kOakCommitWindowReturnCode : @1, + }]; + } + + self.clientPortName = nil; + } + + [self performSelector:@selector(setRetainedSelf:) withObject:nil afterDelay:0]; +} + +// ================== +// = Action Methods = +// ================== + +- (void)didDoubleClickTableView:(id)sender +{ + if(_tableView.clickedRow == -1) + return; + + NSDictionary* row = _paths[_tableView.clickedRow]; + NSLog(@"%s show diff for %@", sel_getName(_cmd), row); +} + +- (void)performCommit:(id)sender +{ + [self sendCommitMessageToClient:YES]; + [self close]; +} + +- (void)cancel:(id)sender +{ + [self sendCommitMessageToClient:NO]; + [self close]; +} + +// ========================= +// = NSTableViewDataSource = +// ========================= + +- (NSInteger)numberOfRowsInTableView:(NSTableView*)aTableView +{ + return [_paths count]; +} + +- (id)tableView:(NSTableView*)aTableView objectValueForTableColumn:(NSTableColumn*)aTableColumn row:(NSInteger)rowIndex +{ + NSDictionary* row = _paths[rowIndex]; + return row[aTableColumn.identifier]; +} +@end + +@interface OakCommitWindowServer () +@property (nonatomic) NSConnection* connection; +@end + +@implementation OakCommitWindowServer ++ (instancetype)sharedInstance +{ + static OakCommitWindowServer* sharedInstance = [self new]; + return sharedInstance; +} + +- (id)init +{ + if(self = [super init]) + { + _connection = [NSConnection new]; + [_connection setRootObject:self]; + if([_connection registerName:kOakCommitWindowServerConnectionName] == NO) + NSLog(@"failed to setup connection ‘%@’", kOakCommitWindowServerConnectionName); + } + return self; +} + +- (void)connectFromClientWithOptions:(NSDictionary*)someOptions +{ + OakCommitWindow* commitWindow = [[OakCommitWindow alloc] init]; + commitWindow.clientPortName = someOptions[kOakCommitWindowClientPortName]; + [commitWindow showWindow:self]; +} +@end diff --git a/Frameworks/CommitWindow/target b/Frameworks/CommitWindow/target new file mode 100644 index 00000000..72397aa8 --- /dev/null +++ b/Frameworks/CommitWindow/target @@ -0,0 +1,4 @@ +SOURCES = src/*.mm +EXPORT = src/CommitWindow.h +LINK += OakAppKit OakFoundation +FRAMEWORKS = Cocoa