@@ -45,95 +45,37 @@ pub fn setup(
4545 env : * EnvMap ,
4646 force_shell : ? Shell ,
4747) ! ? ShellIntegration {
48- const exe = if (force_shell ) | shell | switch (shell ) {
49- .bash = > "bash" ,
50- .elvish = > "elvish" ,
51- .fish = > "fish" ,
52- .zsh = > "zsh" ,
53- } else switch (command ) {
54- .direct = > | v | std .fs .path .basename (v [0 ]),
55- .shell = > | v | exe : {
56- // Shell strings can include spaces so we want to only
57- // look up to the space if it exists. No shell that we integrate
58- // has spaces.
59- const idx = std .mem .indexOfScalar (u8 , v , ' ' ) orelse v .len ;
60- break :exe std .fs .path .basename (v [0.. idx ]);
61- },
62- };
63-
64- const result = try setupShell (
65- alloc_arena ,
66- resource_dir ,
67- command ,
68- env ,
69- exe ,
70- );
71-
72- return result ;
73- }
74-
75- fn setupShell (
76- alloc_arena : Allocator ,
77- resource_dir : []const u8 ,
78- command : config.Command ,
79- env : * EnvMap ,
80- exe : []const u8 ,
81- ) ! ? ShellIntegration {
82- if (std .mem .eql (u8 , "bash" , exe )) {
83- // Apple distributes their own patched version of Bash 3.2
84- // on macOS that disables the ENV-based POSIX startup path.
85- // This means we're unable to perform our automatic shell
86- // integration sequence in this specific environment.
87- //
88- // If we're running "/bin/bash" on Darwin, we can assume
89- // we're using Apple's Bash because /bin is non-writable
90- // on modern macOS due to System Integrity Protection.
91- if (comptime builtin .target .os .tag .isDarwin ()) {
92- if (std .mem .eql (u8 , "/bin/bash" , switch (command ) {
93- .direct = > | v | v [0 ],
94- .shell = > | v | v ,
95- })) {
96- return null ;
97- }
98- }
48+ const shell : Shell = force_shell orelse
49+ try detectShell (alloc_arena , command ) orelse
50+ return null ;
9951
100- const new_command = try setupBash (
52+ const new_command : config.Command = switch (shell ) {
53+ .bash = > try setupBash (
10154 alloc_arena ,
10255 command ,
10356 resource_dir ,
10457 env ,
105- ) orelse return null ;
106- return .{
107- .shell = .bash ,
108- .command = new_command ,
109- };
110- }
111-
112- if (std .mem .eql (u8 , "elvish" , exe )) {
113- if (! try setupXdgDataDirs (alloc_arena , resource_dir , env )) return null ;
114- return .{
115- .shell = .elvish ,
116- .command = try command .clone (alloc_arena ),
117- };
118- }
58+ ),
11959
120- if (std .mem .eql (u8 , "fish" , exe )) {
121- if (! try setupXdgDataDirs (alloc_arena , resource_dir , env )) return null ;
122- return .{
123- .shell = .fish ,
124- .command = try command .clone (alloc_arena ),
125- };
126- }
60+ .elvish , .fish = > try setupXdgDataDirs (
61+ alloc_arena ,
62+ command ,
63+ resource_dir ,
64+ env ,
65+ ),
12766
128- if ( std . mem . eql ( u8 , " zsh" , exe )) {
129- if ( ! try setupZsh ( resource_dir , env )) return null ;
130- return .{
131- . shell = .zsh ,
132- . command = try command . clone ( alloc_arena ) ,
133- };
134- }
67+ . zsh = > try setupZsh (
68+ alloc_arena ,
69+ command ,
70+ resource_dir ,
71+ env ,
72+ ),
73+ } orelse return null ;
13574
136- return null ;
75+ return .{
76+ .shell = shell ,
77+ .command = new_command ,
78+ };
13779}
13880
13981test "force shell" {
@@ -185,6 +127,55 @@ test "shell integration failure" {
185127 try testing .expectEqual (0 , env .count ());
186128}
187129
130+ fn detectShell (alloc : Allocator , command : config.Command ) ! ? Shell {
131+ var arg_iter = try command .argIterator (alloc );
132+ defer arg_iter .deinit ();
133+
134+ const arg0 = arg_iter .next () orelse return null ;
135+ const exe = std .fs .path .basename (arg0 );
136+
137+ if (std .mem .eql (u8 , "bash" , exe )) {
138+ // Apple distributes their own patched version of Bash 3.2
139+ // on macOS that disables the ENV-based POSIX startup path.
140+ // This means we're unable to perform our automatic shell
141+ // integration sequence in this specific environment.
142+ //
143+ // If we're running "/bin/bash" on Darwin, we can assume
144+ // we're using Apple's Bash because /bin is non-writable
145+ // on modern macOS due to System Integrity Protection.
146+ if (comptime builtin .target .os .tag .isDarwin ()) {
147+ if (std .mem .eql (u8 , "/bin/bash" , arg0 )) {
148+ return null ;
149+ }
150+ }
151+ return .bash ;
152+ }
153+
154+ if (std .mem .eql (u8 , "elvish" , exe )) return .elvish ;
155+ if (std .mem .eql (u8 , "fish" , exe )) return .fish ;
156+ if (std .mem .eql (u8 , "zsh" , exe )) return .zsh ;
157+
158+ return null ;
159+ }
160+
161+ test detectShell {
162+ const testing = std .testing ;
163+ const alloc = testing .allocator ;
164+
165+ try testing .expect (try detectShell (alloc , .{ .shell = "sh" }) == null );
166+ try testing .expectEqual (.bash , try detectShell (alloc , .{ .shell = "bash" }));
167+ try testing .expectEqual (.elvish , try detectShell (alloc , .{ .shell = "elvish" }));
168+ try testing .expectEqual (.fish , try detectShell (alloc , .{ .shell = "fish" }));
169+ try testing .expectEqual (.zsh , try detectShell (alloc , .{ .shell = "zsh" }));
170+
171+ if (comptime builtin .target .os .tag .isDarwin ()) {
172+ try testing .expect (try detectShell (alloc , .{ .shell = "/bin/bash" }) == null );
173+ }
174+
175+ try testing .expectEqual (.bash , try detectShell (alloc , .{ .shell = "bash -c 'command'" }));
176+ try testing .expectEqual (.bash , try detectShell (alloc , .{ .shell = "\" /a b/bash\" " }));
177+ }
178+
188179/// Set up the shell integration features environment variable.
189180pub fn setupFeatures (
190181 env : * EnvMap ,
@@ -603,10 +594,11 @@ test "bash: missing resources" {
603594/// so that the shell can refer to it and safely remove this directory
604595/// from `XDG_DATA_DIRS` when integration is complete.
605596fn setupXdgDataDirs (
606- alloc_arena : Allocator ,
597+ alloc : Allocator ,
598+ command : config.Command ,
607599 resource_dir : []const u8 ,
608600 env : * EnvMap ,
609- ) ! bool {
601+ ) ! ? config.Command {
610602 var path_buf : [std .fs .max_path_bytes ]u8 = undefined ;
611603
612604 // Get our path to the shell integration directory.
@@ -617,7 +609,7 @@ fn setupXdgDataDirs(
617609 );
618610 var integ_dir = std .fs .openDirAbsolute (integ_path , .{}) catch | err | {
619611 log .warn ("unable to open {s}: {}" , .{ integ_path , err });
620- return false ;
612+ return null ;
621613 };
622614 integ_dir .close ();
623615
@@ -631,7 +623,7 @@ fn setupXdgDataDirs(
631623 // 4K is a reasonable size for this for most cases. However, env
632624 // vars can be significantly larger so if we have to we fall
633625 // back to a heap allocated value.
634- var stack_alloc_state = std .heap .stackFallback (4096 , alloc_arena );
626+ var stack_alloc_state = std .heap .stackFallback (4096 , alloc );
635627 const stack_alloc = stack_alloc_state .get ();
636628
637629 // If no XDG_DATA_DIRS set use the default value as specified.
@@ -648,7 +640,7 @@ fn setupXdgDataDirs(
648640 ),
649641 );
650642
651- return true ;
643+ return try command . clone ( alloc ) ;
652644}
653645
654646test "xdg: empty XDG_DATA_DIRS" {
@@ -664,7 +656,8 @@ test "xdg: empty XDG_DATA_DIRS" {
664656 var env = EnvMap .init (alloc );
665657 defer env .deinit ();
666658
667- try testing .expect (try setupXdgDataDirs (alloc , res .path , & env ));
659+ const command = try setupXdgDataDirs (alloc , .{ .shell = "xdg" }, res .path , & env );
660+ try testing .expectEqualStrings ("xdg" , command .? .shell );
668661
669662 var path_buf : [std .fs .max_path_bytes ]u8 = undefined ;
670663 try testing .expectEqualStrings (
@@ -691,7 +684,9 @@ test "xdg: existing XDG_DATA_DIRS" {
691684 defer env .deinit ();
692685
693686 try env .put ("XDG_DATA_DIRS" , "/opt/share" );
694- try testing .expect (try setupXdgDataDirs (alloc , res .path , & env ));
687+
688+ const command = try setupXdgDataDirs (alloc , .{ .shell = "xdg" }, res .path , & env );
689+ try testing .expectEqualStrings ("xdg" , command .? .shell );
695690
696691 var path_buf : [std .fs .max_path_bytes ]u8 = undefined ;
697692 try testing .expectEqualStrings (
@@ -719,17 +714,19 @@ test "xdg: missing resources" {
719714 var env = EnvMap .init (alloc );
720715 defer env .deinit ();
721716
722- try testing .expect (! try setupXdgDataDirs (alloc , resources_dir , & env ));
717+ try testing .expect (try setupXdgDataDirs (alloc , .{ . shell = "xdg" }, resources_dir , & env ) == null );
723718 try testing .expectEqual (0 , env .count ());
724719}
725720
726721/// Setup the zsh automatic shell integration. This works by setting
727722/// ZDOTDIR to our resources dir so that zsh will load our config. This
728723/// config then loads the true user config.
729724fn setupZsh (
725+ alloc : Allocator ,
726+ command : config.Command ,
730727 resource_dir : []const u8 ,
731728 env : * EnvMap ,
732- ) ! bool {
729+ ) ! ? config.Command {
733730 // Preserve an existing ZDOTDIR value. We're about to overwrite it.
734731 if (env .get ("ZDOTDIR" )) | old | {
735732 try env .put ("GHOSTTY_ZSH_ZDOTDIR" , old );
@@ -744,31 +741,40 @@ fn setupZsh(
744741 );
745742 var integ_dir = std .fs .openDirAbsolute (integ_path , .{}) catch | err | {
746743 log .warn ("unable to open {s}: {}" , .{ integ_path , err });
747- return false ;
744+ return null ;
748745 };
749746 integ_dir .close ();
750747 try env .put ("ZDOTDIR" , integ_path );
751748
752- return true ;
749+ return try command . clone ( alloc ) ;
753750}
754751
755752test "zsh" {
756753 const testing = std .testing ;
757754
755+ var arena = ArenaAllocator .init (testing .allocator );
756+ defer arena .deinit ();
757+ const alloc = arena .allocator ();
758+
758759 var res : TmpResourcesDir = try .init (testing .allocator , .zsh );
759760 defer res .deinit ();
760761
761762 var env = EnvMap .init (testing .allocator );
762763 defer env .deinit ();
763764
764- try testing .expect (try setupZsh (res .path , & env ));
765+ const command = try setupZsh (alloc , .{ .shell = "zsh" }, res .path , & env );
766+ try testing .expectEqualStrings ("zsh" , command .? .shell );
765767 try testing .expectEqualStrings (res .shell_path , env .get ("ZDOTDIR" ).? );
766768 try testing .expect (env .get ("GHOSTTY_ZSH_ZDOTDIR" ) == null );
767769}
768770
769771test "zsh: ZDOTDIR" {
770772 const testing = std .testing ;
771773
774+ var arena = ArenaAllocator .init (testing .allocator );
775+ defer arena .deinit ();
776+ const alloc = arena .allocator ();
777+
772778 var res : TmpResourcesDir = try .init (testing .allocator , .zsh );
773779 defer res .deinit ();
774780
@@ -777,7 +783,8 @@ test "zsh: ZDOTDIR" {
777783
778784 try env .put ("ZDOTDIR" , "$HOME/.config/zsh" );
779785
780- try testing .expect (try setupZsh (res .path , & env ));
786+ const command = try setupZsh (alloc , .{ .shell = "zsh" }, res .path , & env );
787+ try testing .expectEqualStrings ("zsh" , command .? .shell );
781788 try testing .expectEqualStrings (res .shell_path , env .get ("ZDOTDIR" ).? );
782789 try testing .expectEqualStrings ("$HOME/.config/zsh" , env .get ("GHOSTTY_ZSH_ZDOTDIR" ).? );
783790}
@@ -797,7 +804,7 @@ test "zsh: missing resources" {
797804 var env = EnvMap .init (alloc );
798805 defer env .deinit ();
799806
800- try testing .expect (! try setupZsh (resources_dir , & env ));
807+ try testing .expect (try setupZsh (alloc , .{ . shell = "zsh" }, resources_dir , & env ) == null );
801808 try testing .expectEqual (0 , env .count ());
802809}
803810
0 commit comments