From 027860f5110da1e1230c54e9319576181dcd1252 Mon Sep 17 00:00:00 2001 From: seidnerj Date: Sun, 25 Jan 2026 15:26:47 +0200 Subject: [PATCH] Add configurable terminal application support Users can now configure their preferred terminal application via defaults: - defaults write name.tuley.jay.cd-to cdto-terminal-app -string "Terminal" - defaults write name.tuley.jay.cd-to cdto-terminal-app -string "iTerm" Supports both Terminal.app (default) and iTerm2. Accepts app names (case-insensitive) or bundle identifiers. Falls back to Terminal.app for unrecognized values. --- README.md | 14 ++ cd to .../cd to .../iTerm.h | 88 ++++++++++++ cd to .../cd to .../main.m | 156 +++++++++++++++------- cd to .../cd to.xcodeproj/project.pbxproj | 2 + 4 files changed, 211 insertions(+), 49 deletions(-) create mode 100644 cd to .../cd to .../iTerm.h diff --git a/README.md b/README.md index f6be42e..65aab93 100644 --- a/README.md +++ b/README.md @@ -20,6 +20,20 @@ To use, just click on the new button and instantly opens a new terminal window. ### Settings +To configure which terminal application to use (defaults to Terminal.app): + +```bash +# Use Terminal.app (default) +defaults write name.tuley.jay.cd-to cdto-terminal-app -string "Terminal" + +# Use iTerm2 +defaults write name.tuley.jay.cd-to cdto-terminal-app -string "iTerm" + +# Or use bundle identifiers +defaults write name.tuley.jay.cd-to cdto-terminal-app -string "com.apple.Terminal" +defaults write name.tuley.jay.cd-to cdto-terminal-app -string "com.googlecode.iterm2" +``` + To turn on feature that identifies automatically opened Terminal windows, and closes them when using *cd to*. ```bash diff --git a/cd to .../cd to .../iTerm.h b/cd to .../cd to .../iTerm.h new file mode 100644 index 0000000..3328fd0 --- /dev/null +++ b/cd to .../cd to .../iTerm.h @@ -0,0 +1,88 @@ +/* + * iTerm.h + * Scripting Bridge header for iTerm2 + */ + +#import +#import + +@class iTermApplication, iTermWindow, iTermTab, iTermSession; + +enum iTermSaveOptions { + iTermSaveOptionsYes = 'yes ', + iTermSaveOptionsNo = 'no ', + iTermSaveOptionsAsk = 'ask ' +}; +typedef enum iTermSaveOptions iTermSaveOptions; + +@protocol iTermGenericMethods + +- (void) closeSaving:(iTermSaveOptions)saving savingIn:(NSURL *)savingIn; +- (BOOL) exists; + +@end + +// The iTerm application +@interface iTermApplication : SBApplication + +- (SBElementArray *) windows; +- (SBElementArray *) terminalWindows; + +@property (copy, readonly) NSString *name; +@property (readonly) BOOL frontmost; +@property (copy, readonly) NSString *version; + +- (void) activate; +- (iTermWindow *) createWindowWithDefaultProfileCommand:(NSString *)command; + +@end + +// An iTerm window +@interface iTermWindow : SBObject + +- (SBElementArray *) tabs; +- (SBElementArray *) currentTabs; + +@property (copy, readonly) NSString *name; +- (NSString *) id; +@property NSInteger index; +@property NSRect bounds; +@property (readonly) BOOL closeable; +@property (readonly) BOOL miniaturizable; +@property BOOL miniaturized; +@property (readonly) BOOL resizable; +@property BOOL visible; +@property (readonly) BOOL zoomable; +@property BOOL zoomed; +@property BOOL frontmost; +@property NSPoint position; + +- (iTermTab *) currentTab; +- (iTermSession *) currentSession; + +@end + +// An iTerm tab +@interface iTermTab : SBObject + +- (SBElementArray *) sessions; + +@property (copy, readonly) NSString *name; +- (iTermSession *) currentSession; + +@end + +// An iTerm session +@interface iTermSession : SBObject + +@property (copy, readonly) NSString *contents; +@property (copy, readonly) NSString *name; +@property (copy, readonly) NSString *uniqueID; +@property (copy, readonly) NSArray *columns; +@property (copy, readonly) NSArray *rows; +@property (copy) NSString *backgroundColor; +@property (copy, readonly) NSString *tty; + +- (void) writeContentsOfFile:(NSString *)contentsOfFile text:(NSString *)text; + +@end diff --git a/cd to .../cd to .../main.m b/cd to .../cd to .../main.m index 945df9f..916eb33 100644 --- a/cd to .../cd to .../main.m +++ b/cd to .../cd to .../main.m @@ -11,19 +11,102 @@ #import "Finder.h" #import "Terminal.h" +#import "iTerm.h" NSUInteger linesOfHistory(TerminalTab* tab) { NSString* hist = [[tab history] stringByTrimmingCharactersInSet:[NSCharacterSet newlineCharacterSet]]; return [[hist componentsSeparatedByCharactersInSet:[NSCharacterSet newlineCharacterSet]] count]; } +void openInTerminal(NSURL* url) { + TerminalApplication* terminal = [SBApplication applicationWithBundleIdentifier:@"com.apple.Terminal"]; + + TerminalWindow* win = nil; + if ([[terminal windows] count] == 1){ + //get front most and then reference by id + win = [[terminal windows] objectAtLocation:@1]; + win = [[terminal windows] objectWithID: [NSNumber numberWithInteger:win.id]]; + } + [terminal open:@[url]]; + //get front most and then reference by id + TerminalWindow* newWin = [[terminal windows] objectAtLocation:@1]; + newWin = [[terminal windows] objectWithID: [NSNumber numberWithInteger:newWin.id]]; + TerminalTab* newTab = [[newWin tabs] objectAtLocation:@1]; + + NSString* setName = [[NSUserDefaults standardUserDefaults] stringForKey:@"cdto-new-window-setting"]; + if(setName != nil && ![setName isEqualToString:@""]) { //setting set + TerminalSettingsSet* chosenSet = nil; + for (TerminalSettingsSet *set in [terminal settingsSets]) { + if([[set name] isEqualToString:setName]){ + chosenSet = set; + } + } + if(chosenSet != nil){ + newTab.currentSettings = chosenSet; + } + } + + if([[NSUserDefaults standardUserDefaults] boolForKey:@"cdto-close-default-window"]){ //close first launch window + if([[win tabs] count] == 1){ + TerminalTab* tab = [[win tabs]objectAtLocation:@1]; + if(![tab busy]){ + //if history has same number of lines as new window + // assume automatically opened new window, and close it + NSUInteger oldTabLines = linesOfHistory(tab); + while([newTab busy]){ + [NSThread sleepForTimeInterval:0.1f]; + } + NSUInteger newTabLines = linesOfHistory(newTab); + if(oldTabLines == newTabLines){ + [win closeSaving:TerminalSaveOptionsNo savingIn:nil]; + } + } + } + } + + [terminal activate]; +} + +void openIniTerm(NSURL* url) { + NSString* path = [url path]; + + // Escape single quotes in the path for AppleScript + NSString* escapedPath = [path stringByReplacingOccurrencesOfString:@"'" withString:@"'\\''"]; + + // Use AppleScript for more reliable iTerm integration + NSString* script = [NSString stringWithFormat: + @"tell application \"iTerm\"\n" + @" activate\n" + @" try\n" + @" set newWindow to (create window with default profile)\n" + @" tell current session of newWindow\n" + @" write text \"cd '%@'; clear\"\n" + @" end tell\n" + @" on error\n" + @" tell current window\n" + @" tell current session\n" + @" write text \"cd '%@'; clear\"\n" + @" end tell\n" + @" end tell\n" + @" end try\n" + @"end tell", escapedPath, escapedPath]; + + NSAppleScript* appleScript = [[NSAppleScript alloc] initWithSource:script]; + NSDictionary* errorInfo = nil; + [appleScript executeAndReturnError:&errorInfo]; + + if (errorInfo) { + // Fallback to Terminal if iTerm fails + NSLog(@"iTerm failed, falling back to Terminal: %@", errorInfo); + openInTerminal(url); + } +} + int main(int argc, const char * argv[]) { @autoreleasepool { // Setup code that might create autoreleased objects goes here. FinderApplication* finder = [SBApplication applicationWithBundleIdentifier:@"com.apple.Finder"]; - - TerminalApplication* terminal = [SBApplication applicationWithBundleIdentifier:@"com.apple.Terminal"]; - + FinderItem *target = [(NSArray*)[[finder selection] get] firstObject]; FinderFinderWindow* findWin = [[finder FinderWindows] objectAtLocation:@1]; findWin = [[finder FinderWindows] objectWithID:[NSNumber numberWithInteger: findWin.id]]; @@ -32,65 +115,40 @@ int main(int argc, const char * argv[]) { target = [[findWin target] get]; selected = false; } - + NSDictionary* itemProperties = [target properties]; id originalItem = [itemProperties objectForKey:@"originalItem"]; if (originalItem != nil && originalItem != [NSNull null]){ target = originalItem; } - + NSString* fileUrl = [target URL]; if(fileUrl != nil && ![fileUrl hasSuffix:@"/"] && selected){ fileUrl = [fileUrl stringByDeletingLastPathComponent]; } - + NSURL* url = [NSURL URLWithString:fileUrl]; if (url != nil){ - TerminalWindow* win = nil; - if ([[terminal windows] count] == 1){ - //get front most and then reference by id - win = [[terminal windows] objectAtLocation:@1]; - win = [[terminal windows] objectWithID: [NSNumber numberWithInteger:win.id]]; + // Get terminal preference from defaults + NSString* terminalApp = [[NSUserDefaults standardUserDefaults] stringForKey:@"cdto-terminal-app"]; + + // Normalize the terminal app identifier + if (terminalApp == nil || [terminalApp isEqualToString:@""]) { + terminalApp = @"com.apple.Terminal"; // Default to Terminal.app + } else if ([terminalApp caseInsensitiveCompare:@"Terminal"] == NSOrderedSame) { + terminalApp = @"com.apple.Terminal"; + } else if ([terminalApp caseInsensitiveCompare:@"iTerm"] == NSOrderedSame || + [terminalApp caseInsensitiveCompare:@"iTerm2"] == NSOrderedSame) { + terminalApp = @"com.googlecode.iterm2"; } - [terminal open:@[url]]; - //get front most and then reference by id - TerminalWindow* newWin = [[terminal windows] objectAtLocation:@1]; - newWin = [[terminal windows] objectWithID: [NSNumber numberWithInteger:newWin.id]]; - TerminalTab* newTab = [[newWin tabs] objectAtLocation:@1]; - - NSString* setName = [[NSUserDefaults standardUserDefaults] stringForKey:@"cdto-new-window-setting"]; - if(setName != nil && ![setName isEqualToString:@""]) { //setting set - TerminalSettingsSet* chosenSet = nil; - for (TerminalSettingsSet *set in [terminal settingsSets]) { - if([[set name] isEqualToString:setName]){ - chosenSet = set; - } - } - if(chosenSet != nil){ - newTab.currentSettings = chosenSet; - } - } - - if([[NSUserDefaults standardUserDefaults] boolForKey:@"cdto-close-default-window"]){ //close first launch window - if([[win tabs] count] == 1){ - TerminalTab* tab = [[win tabs]objectAtLocation:@1]; - if(![tab busy]){ - //if history has same number of lines as new window - // assume automatically opened new window, and close it - NSUInteger oldTabLines = linesOfHistory(tab); - while([newTab busy]){ - [NSThread sleepForTimeInterval:0.1f]; - } - NSUInteger newTabLines = linesOfHistory(newTab); - if(oldTabLines == newTabLines){ - [win closeSaving:TerminalSaveOptionsNo savingIn:nil]; - } - } - } + + // Open in the appropriate terminal + if ([terminalApp isEqualToString:@"com.googlecode.iterm2"]) { + openIniTerm(url); + } else { + // Default to Terminal.app for any unknown values + openInTerminal(url); } - - - [terminal activate]; } } } diff --git a/cd to .../cd to.xcodeproj/project.pbxproj b/cd to .../cd to.xcodeproj/project.pbxproj index 5f9564d..2e74786 100644 --- a/cd to .../cd to.xcodeproj/project.pbxproj +++ b/cd to .../cd to.xcodeproj/project.pbxproj @@ -21,6 +21,7 @@ 699EB3D2234DBB0700CC2315 /* Finder.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Finder.h; sourceTree = ""; }; 699EB3D3234DBB0700CC2315 /* Terminal.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Terminal.h; sourceTree = ""; }; 699EB3D4234DC4D800CC2315 /* AppIcon.icns */ = {isa = PBXFileReference; lastKnownFileType = image.icns; path = AppIcon.icns; sourceTree = ""; }; + 699EB3D6234DBB0700CC2315 /* iTerm.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = iTerm.h; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -56,6 +57,7 @@ 699EB3D4234DC4D800CC2315 /* AppIcon.icns */, 699EB3D2234DBB0700CC2315 /* Finder.h */, 699EB3D3234DBB0700CC2315 /* Terminal.h */, + 699EB3D6234DBB0700CC2315 /* iTerm.h */, 699EB3BF234DBA0D00CC2315 /* Assets.xcassets */, 699EB3C4234DBA0D00CC2315 /* Info.plist */, 699EB3C5234DBA0D00CC2315 /* main.m */,