From 953cf333d558f911340d8467c219b7a770b2959b Mon Sep 17 00:00:00 2001 From: Etienne Samson Date: Tue, 29 Jan 2019 02:02:40 +0100 Subject: [PATCH] Support iTerm2 as a terminal handler --- Classes/Util/PBTerminalUtil.m | 78 +++++++++-- Classes/git/PBGitDefaults.h | 1 + Classes/git/PBGitDefaults.m | 8 ++ Classes/iTerm2GeneratedScriptingBridge.h | 158 +++++++++++++++++++++++ GitX.xcodeproj/project.pbxproj | 2 + Resources/Info.plist | 2 + 6 files changed, 241 insertions(+), 8 deletions(-) create mode 100644 Classes/iTerm2GeneratedScriptingBridge.h diff --git a/Classes/Util/PBTerminalUtil.m b/Classes/Util/PBTerminalUtil.m index f25692b36..cf373825d 100644 --- a/Classes/Util/PBTerminalUtil.m +++ b/Classes/Util/PBTerminalUtil.m @@ -7,23 +7,85 @@ #import "PBTerminalUtil.h" #import "Terminal.h" +#import "iTerm2GeneratedScriptingBridge.h" +#import "PBGitDefaults.h" + +@interface PBTerminalUtil () +@end @implementation PBTerminalUtil -+ (NSString *) initialCommand:(NSURL *)workingDirectory { - return [NSString stringWithFormat:@"cd \"%@\"; clear; echo '# Opened by GitX'; ", - workingDirectory.path]; ++ (void)runCommand:(NSString *)command inDirectory:(NSURL *)directory { + [[self terminalHandler] runCommand:command inDirectory:directory]; +} + ++ (instancetype)terminalHandler { + static dispatch_once_t onceToken; + static PBTerminalUtil *term = nil; + dispatch_once(&onceToken, ^{ + term = [[self alloc] init]; + }); + return term; +} + +- (void)runCommand:(NSString *)command inDirectory:(NSURL *)directory { + NSString *terminalHandler = [PBGitDefaults terminalHandler]; + BOOL ran = NO; + + if ([terminalHandler isEqualToString:@"com.googlecode.iterm2"]) { + ran = [self runiTerm2Command:command inDirectory:directory]; + } + + // Fall back to Apple Terminal. + if (!ran) { + if (![terminalHandler isEqualToString:@"com.apple.Terminal"]) + NSLog(@"Unexpected terminal handler %@, using Terminal.app", terminalHandler); + ran = [self runTerminalCommand:command inDirectory:directory]; + } + + if (!ran) { + NSLog(@"No usable terminal handler found"); + } } -+ (void) runCommand:(NSString *)command inDirectory:(NSURL *)directory { - NSString * initialCommand = [self initialCommand:directory]; - NSString * fullCommand = [initialCommand stringByAppendingString:command]; - +- (nullable id)eventDidFail:(const AppleEvent *)event withError:(NSError *)error { + NSLog(@"terminal handler error: %@", error); + return nil; +} + +- (BOOL)runTerminalCommand:(NSString *)command inDirectory:(NSURL *)directory { + NSString *fullCommand = [NSString stringWithFormat:@"cd \"%@\"; clear; echo '# Opened by GitX'; %@", directory.path, command]; + TerminalApplication *term = [SBApplication applicationWithBundleIdentifier: @"com.apple.Terminal"]; + if (!term) + return NO; + term.delegate = self; + [term doScript:fullCommand in: nil]; + + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ + [term activate]; + }); + + return YES; +} + +- (BOOL)runiTerm2Command:(NSString *)command inDirectory:(NSURL *)directory { + NSString *fullCommand = [NSString stringWithFormat:@"cd \"%@\"; clear; echo '# Opened by GitX'; %@", directory.path, command]; + + iTerm2Application *term = [SBApplication applicationWithBundleIdentifier: @"com.googlecode.iterm2"]; + if (!term) + return NO; + term.delegate = self; + + iTerm2Window *win = [term createWindowWithDefaultProfileCommand:nil]; + [win.currentSession writeContentsOfFile:nil text:fullCommand newline:YES]; + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ [term activate]; }); + + return YES; } -@end \ No newline at end of file +@end diff --git a/Classes/git/PBGitDefaults.h b/Classes/git/PBGitDefaults.h index 9798eea9a..d8489f95f 100644 --- a/Classes/git/PBGitDefaults.h +++ b/Classes/git/PBGitDefaults.h @@ -31,6 +31,7 @@ + (NSInteger)historySearchMode; + (void)setHistorySearchMode:(NSInteger)mode; + (BOOL)useRepositoryWatcher; ++ (NSString *)terminalHandler; // Suppressed Dialog Warnings diff --git a/Classes/git/PBGitDefaults.m b/Classes/git/PBGitDefaults.m index 1e3f53292..1f4fba536 100644 --- a/Classes/git/PBGitDefaults.m +++ b/Classes/git/PBGitDefaults.m @@ -30,6 +30,7 @@ #define kHistorySearchMode @"PBHistorySearchMode" #define kSuppressedDialogWarnings @"Suppressed Dialog Warnings" #define kUseRepositoryWatcher @"PBUseRepositoryWatcher" +#define kTerminalHandler @"PBTerminalHandler" @implementation PBGitDefaults @@ -64,6 +65,8 @@ + (void)initialize forKey:kHistorySearchMode]; [defaultValues setObject:[NSNumber numberWithBool:YES] forKey:kUseRepositoryWatcher]; + [defaultValues setObject:@"com.apple.Terminal" + forKey:kTerminalHandler]; [[NSUserDefaults standardUserDefaults] registerDefaults:defaultValues]; } @@ -226,4 +229,9 @@ + (BOOL) useRepositoryWatcher return [[NSUserDefaults standardUserDefaults] boolForKey:kUseRepositoryWatcher]; } ++ (NSString *)terminalHandler +{ + return [[NSUserDefaults standardUserDefaults] stringForKey:kTerminalHandler]; +} + @end diff --git a/Classes/iTerm2GeneratedScriptingBridge.h b/Classes/iTerm2GeneratedScriptingBridge.h new file mode 100644 index 000000000..991d0d9de --- /dev/null +++ b/Classes/iTerm2GeneratedScriptingBridge.h @@ -0,0 +1,158 @@ +/* + * iTerm2.h + */ + +#import +#import + + +@class iTerm2Application, iTerm2Window, iTerm2Tab, iTerm2Session; + +enum iTerm2SaveOptions { + iTerm2SaveOptionsYes = 'yes ' /* Save the file. */, + iTerm2SaveOptionsNo = 'no ' /* Do not save the file. */, + iTerm2SaveOptionsAsk = 'ask ' /* Ask the user whether or not to save the file. */ +}; +typedef enum iTerm2SaveOptions iTerm2SaveOptions; + +@protocol iTerm2GenericMethods + +- (void) delete; // Delete an object. +- (void) duplicateTo:(SBObject *)to withProperties:(NSDictionary *)withProperties; // Copy object(s) and put the copies at a new location. +- (BOOL) exists; // Verify if an object exists. +- (void) moveTo:(SBObject *)to; // Move object(s) to a new location. +- (void) close; // Close a document. +- (iTerm2Tab *) createTabWithProfile:(NSString *)withProfile command:(NSString *)command; // Create a new tab +- (iTerm2Tab *) createTabWithDefaultProfileCommand:(NSString *)command; // Create a new tab with the default profile +- (void) writeContentsOfFile:(NSURL *)contentsOfFile text:(NSString *)text newline:(BOOL)newline; // Send text as though it was typed. +- (void) select; // Make receiver visible and selected. +- (iTerm2Session *) splitVerticallyWithProfile:(NSString *)withProfile command:(NSString *)command; // Split a session vertically. +- (iTerm2Session *) splitVerticallyWithDefaultProfileCommand:(NSString *)command; // Split a session vertically, using the default profile for the new session +- (iTerm2Session *) splitVerticallyWithSameProfileCommand:(NSString *)command; // Split a session vertically, using the original session's profile for the new session +- (iTerm2Session *) splitHorizontallyWithProfile:(NSString *)withProfile command:(NSString *)command; // Split a session horizontally. +- (iTerm2Session *) splitHorizontallyWithDefaultProfileCommand:(NSString *)command; // Split a session horizontally, using the default profile for the new session +- (iTerm2Session *) splitHorizontallyWithSameProfileCommand:(NSString *)command; // Split a session horizontally, using the original session's profile for the new session +- (NSString *) variableNamed:(NSString *)named; // Returns the value of a session variable with the given name +- (NSString *) setVariableNamed:(NSString *)named to:(NSString *)to; // Sets the value of a session variable +- (void) revealHotkeyWindow; // Reveals a hotkey window. Only to be called on windows that are hotkey windows. +- (void) hideHotkeyWindow; // Hides a hotkey window. Only to be called on windows that are hotkey windows. +- (void) toggleHotkeyWindow; // Toggles the visibility of a hotkey window. Only to be called on windows that are hotkey windows. + +@end + + + +/* + * Standard Suite + */ + +// The application's top-level scripting object. +@interface iTerm2Application : SBApplication + +- (SBElementArray *) windows; + +@property (copy) iTerm2Window *currentWindow; // The frontmost window +@property (copy, readonly) NSString *name; // The name of the application. +@property (readonly) BOOL frontmost; // Is this the frontmost (active) application? +@property (copy, readonly) NSString *version; // The version of the application. + +- (iTerm2Window *) createWindowWithProfile:(NSString *)x command:(NSString *)command; // Create a new window +- (iTerm2Window *) createHotkeyWindowWithProfile:(NSString *)x; // Create a hotkey window +- (iTerm2Window *) createWindowWithDefaultProfileCommand:(NSString *)command; // Create a new window with the default profile + +@end + +// A window. +@interface iTerm2Window : SBObject + +- (SBElementArray *) tabs; + +- (NSInteger) id; // The unique identifier of the session. +@property (copy, readonly) NSString *alternateIdentifier; // The alternate unique identifier of the session. +@property (copy, readonly) NSString *name; // The full title of the window. +@property NSInteger index; // The index of the window, ordered front to back. +@property NSRect bounds; // The bounding rectangle of the window. +@property (readonly) BOOL closeable; // Whether the window has a close box. +@property (readonly) BOOL miniaturizable; // Whether the window can be minimized. +@property BOOL miniaturized; // Whether the window is currently minimized. +@property (readonly) BOOL resizable; // Whether the window can be resized. +@property BOOL visible; // Whether the window is currently visible. +@property (readonly) BOOL zoomable; // Whether the window can be zoomed. +@property BOOL zoomed; // Whether the window is currently zoomed. +@property BOOL frontmost; // Whether the window is currently the frontmost window. +@property (copy) iTerm2Tab *currentTab; // The currently selected tab +@property (copy) iTerm2Session *currentSession; // The current session in a window +@property BOOL isHotkeyWindow; // Whether the window is a hotkey window. +@property (copy) NSString *hotkeyWindowProfile; // If the window is a hotkey window, this gives the name of the profile that created the window. +@property NSPoint position; // The position of the window, relative to the upper left corner of the screen. +@property NSPoint origin; // The position of the window, relative to the lower left corner of the screen. +@property NSPoint size; // The width and height of the window +@property NSRect frame; // The bounding rectangle, relative to the lower left corner of the screen. + + +@end + + + +/* + * iTerm2 Suite + */ + +// A terminal tab +@interface iTerm2Tab : SBObject + +- (SBElementArray *) sessions; + +@property (copy) iTerm2Session *currentSession; // The current session in a tab +@property NSInteger index; // Index of tab in parent tab view control + + +@end + +// A terminal session +@interface iTerm2Session : SBObject + +- (NSString *) id; // The unique identifier of the session. +@property BOOL isProcessing; // The session has received output recently. +@property BOOL isAtShellPrompt; // The terminal is at the shell prompt. Requires shell integration. +@property NSInteger columns; +@property NSInteger rows; +@property (copy, readonly) NSString *tty; +@property (copy) NSString *contents; // The currently visible contents of the session. +@property (copy, readonly) NSString *text; // The currently visible contents of the session. +@property (copy) NSString *colorPreset; +@property (copy) NSColor *backgroundColor; +@property (copy) NSColor *boldColor; +@property (copy) NSColor *cursorColor; +@property (copy) NSColor *cursorTextColor; +@property (copy) NSColor *foregroundColor; +@property (copy) NSColor *selectedTextColor; +@property (copy) NSColor *selectionColor; +@property (copy) NSColor *ANSIBlackColor; +@property (copy) NSColor *ANSIRedColor; +@property (copy) NSColor *ANSIGreenColor; +@property (copy) NSColor *ANSIYellowColor; +@property (copy) NSColor *ANSIBlueColor; +@property (copy) NSColor *ANSIMagentaColor; +@property (copy) NSColor *ANSICyanColor; +@property (copy) NSColor *ANSIWhiteColor; +@property (copy) NSColor *ANSIBrightBlackColor; +@property (copy) NSColor *ANSIBrightRedColor; +@property (copy) NSColor *ANSIBrightGreenColor; +@property (copy) NSColor *ANSIBrightYellowColor; +@property (copy) NSColor *ANSIBrightBlueColor; +@property (copy) NSColor *ANSIBrightMagentaColor; +@property (copy) NSColor *ANSIBrightCyanColor; +@property (copy) NSColor *ANSIBrightWhiteColor; +@property (copy) NSColor *underlineColor; +@property BOOL useUnderlineColor; // Whether the use a dedicated color for underlining. +@property (copy) NSString *backgroundImage; +@property (copy) NSString *name; +@property double transparency; +@property (copy, readonly) NSString *uniqueID; +@property (copy, readonly) NSString *profileName; // The session's profile name +@property (copy) NSString *answerbackString; // ENQ Answerback string + + +@end + diff --git a/GitX.xcodeproj/project.pbxproj b/GitX.xcodeproj/project.pbxproj index 8470dcb3d..796a4eafc 100644 --- a/GitX.xcodeproj/project.pbxproj +++ b/GitX.xcodeproj/project.pbxproj @@ -615,6 +615,7 @@ 97CF01F618A6C5BB00E30F2B /* deleted_file.pdf */ = {isa = PBXFileReference; lastKnownFileType = image.pdf; path = deleted_file.pdf; sourceTree = ""; }; 97CF01F718A6C5BB00E30F2B /* empty_file.pdf */ = {isa = PBXFileReference; lastKnownFileType = image.pdf; path = empty_file.pdf; sourceTree = ""; }; 97CF01F818A6C5BB00E30F2B /* new_file.pdf */ = {isa = PBXFileReference; lastKnownFileType = image.pdf; path = new_file.pdf; sourceTree = ""; }; + A1021294218C5A2000D97D11 /* iTerm2GeneratedScriptingBridge.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = iTerm2GeneratedScriptingBridge.h; sourceTree = ""; }; A2F8D0DD17AAB32500580B84 /* PBGitStash.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PBGitStash.h; sourceTree = ""; }; A2F8D0DE17AAB32500580B84 /* PBGitStash.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PBGitStash.m; sourceTree = ""; }; A2F8D0E917AAB95E00580B84 /* PBSourceViewGitStashItem.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PBSourceViewGitStashItem.h; sourceTree = ""; }; @@ -872,6 +873,7 @@ 4D6E4F791E56851A004C3A6F /* PBMacros.h */, 4DF173E721A1D60A003CD3CE /* NSAppearance+PBDarkMode.h */, 4DF173E821A1D60A003CD3CE /* NSAppearance+PBDarkMode.m */, + A1021294218C5A2000D97D11 /* iTerm2GeneratedScriptingBridge.h */, ); path = Classes; sourceTree = ""; diff --git a/Resources/Info.plist b/Resources/Info.plist index 402cfd673..3ce4498e7 100644 --- a/Resources/Info.plist +++ b/Resources/Info.plist @@ -52,6 +52,8 @@ 0.12 LSApplicationCategoryType public.app-category.developer-tools + NSAppleEventsUsageDescription + GitX needs authorization in order to open your Terminal application. NSAppleScriptEnabled NSMainNibFile