1
1
using System . Diagnostics ;
2
2
using System . Management . Automation ;
3
- using System . Text ;
4
3
5
4
namespace AIShell . Integration . Commands ;
6
5
@@ -12,6 +11,10 @@ public class StartAIShellCommand : PSCmdlet
12
11
[ ValidateNotNullOrEmpty ]
13
12
public string Path { get ; set ; }
14
13
14
+ private string _venvPipPath ;
15
+ private string _venvPythonPath ;
16
+ private static bool s_iterm2Installed = false ;
17
+
15
18
protected override void BeginProcessing ( )
16
19
{
17
20
if ( Path is null )
@@ -80,25 +83,21 @@ protected override void BeginProcessing()
80
83
targetObject : null ) ) ;
81
84
}
82
85
83
- var python = SessionState . InvokeCommand . GetCommand ( "python3" , CommandTypes . Application ) ;
84
- if ( python is null )
86
+ try
85
87
{
86
- ThrowTerminatingError ( new (
87
- new NotSupportedException ( "The executable 'python3' (Windows Terminal) cannot be found. It's required to split a pane in iTerm2 programmatically." ) ,
88
- "Python3Missing" ,
89
- ErrorCategory . NotInstalled ,
90
- targetObject : null ) ) ;
88
+ InitAndCleanup . CreateVirtualEnvTask . GetAwaiter ( ) . GetResult ( ) ;
91
89
}
92
-
93
- var pip3 = SessionState . InvokeCommand . GetCommand ( "pip3" , CommandTypes . Application ) ;
94
- if ( pip3 is null )
90
+ catch ( Exception exception )
95
91
{
96
92
ThrowTerminatingError ( new (
97
- new NotSupportedException ( "The executable 'pip3' cannot be found. It's required to split a pane in iTerm2 programmatically." ) ,
98
- "Pip3Missing " ,
99
- ErrorCategory . NotInstalled ,
93
+ exception ,
94
+ "FailedToCreateVirtualEnvironment " ,
95
+ ErrorCategory . InvalidOperation ,
100
96
targetObject : null ) ) ;
101
97
}
98
+
99
+ _venvPipPath = System . IO . Path . Join ( InitAndCleanup . VirtualEnvPath , "bin" , "pip3" ) ;
100
+ _venvPythonPath = System . IO . Path . Join ( InitAndCleanup . VirtualEnvPath , "bin" , "python3" ) ;
102
101
}
103
102
else
104
103
{
@@ -112,12 +111,11 @@ protected override void BeginProcessing()
112
111
113
112
protected override void EndProcessing ( )
114
113
{
115
- string pipeName = Channel . Singleton . StartChannelSetup ( ) ;
116
-
117
114
if ( OperatingSystem . IsWindows ( ) )
118
115
{
119
116
ProcessStartInfo startInfo ;
120
117
string wtProfileGuid = Environment . GetEnvironmentVariable ( "WT_PROFILE_ID" ) ;
118
+ string pipeName = Channel . Singleton . StartChannelSetup ( ) ;
121
119
122
120
if ( wtProfileGuid is null )
123
121
{
@@ -169,94 +167,62 @@ protected override void EndProcessing()
169
167
}
170
168
else if ( OperatingSystem . IsMacOS ( ) )
171
169
{
172
- // Install the Python package 'iterm2'.
173
- ProcessStartInfo startInfo = new ( "pip3" )
174
- {
175
- ArgumentList = { "install" , "-q" , "iterm2" } ,
176
- RedirectStandardError = true ,
177
- RedirectStandardOutput = true
178
- } ;
179
-
180
- Process proc = new ( ) { StartInfo = startInfo } ;
181
- proc . Start ( ) ;
182
- proc . WaitForExit ( ) ;
170
+ Process proc ;
171
+ ProcessStartInfo startInfo ;
183
172
184
- if ( proc . ExitCode is 1 )
173
+ // Install the Python package 'iterm2' to the venv.
174
+ if ( ! s_iterm2Installed )
185
175
{
186
- ThrowTerminatingError ( new (
187
- new NotSupportedException ( "The Python package 'iterm2' cannot be installed. It's required to split a pane in iTerm2 programmatically." ) ,
188
- "iterm2Missing" ,
189
- ErrorCategory . NotInstalled ,
190
- targetObject : null ) ) ;
191
- }
176
+ startInfo = new ( _venvPipPath )
177
+ {
178
+ // Make 'pypi.org' and 'files.pythonhosted.org' as trusted hosts, because a security software
179
+ // may cause issue to SSL validation for access to/from those two endpoints.
180
+ // See https://stackoverflow.com/a/71993364 for details.
181
+ ArgumentList = {
182
+ "install" ,
183
+ "-q" ,
184
+ "--disable-pip-version-check" ,
185
+ "--trusted-host" ,
186
+ "pypi.org" ,
187
+ "--trusted-host" ,
188
+ "files.pythonhosted.org" ,
189
+ "iterm2"
190
+ } ,
191
+ RedirectStandardError = true ,
192
+ RedirectStandardOutput = true
193
+ } ;
192
194
193
- proc . Dispose ( ) ;
195
+ proc = Process . Start ( startInfo ) ;
196
+ proc . WaitForExit ( ) ;
194
197
195
- // Write the Python script to a temp file, if not yet.
196
- string pythonScript = System . IO . Path . Combine ( System . IO . Path . GetTempPath ( ) , "__aish_split_pane.py" ) ;
197
- if ( ! File . Exists ( pythonScript ) )
198
- {
199
- File . WriteAllText ( pythonScript , SplitPanePythonCode , Encoding . UTF8 ) ;
198
+ if ( proc . ExitCode is 0 )
199
+ {
200
+ s_iterm2Installed = true ;
201
+ }
202
+ else
203
+ {
204
+ string error = "The Python package 'iterm2' cannot be installed. It's required to split a pane in iTerm2 programmatically." ;
205
+ string stderr = proc . StandardError . ReadToEnd ( ) ;
206
+ if ( ! string . IsNullOrEmpty ( stderr ) )
207
+ {
208
+ error = $ "{ error } \n Error details:\n { stderr } ";
209
+ }
210
+
211
+ ThrowTerminatingError ( new (
212
+ new NotSupportedException ( error ) ,
213
+ "iterm2Missing" ,
214
+ ErrorCategory . NotInstalled ,
215
+ targetObject : null ) ) ;
216
+ }
217
+
218
+ proc . Dispose ( ) ;
200
219
}
201
220
202
221
// Run the Python script to split the pane and start AIShell.
203
- startInfo = new ( "python3" ) { ArgumentList = { pythonScript , Path , pipeName } } ;
204
- proc = new ( ) { StartInfo = startInfo } ;
205
- proc . Start ( ) ;
222
+ string pipeName = Channel . Singleton . StartChannelSetup ( ) ;
223
+ startInfo = new ( _venvPythonPath ) { ArgumentList = { InitAndCleanup . PythonScript , Path , pipeName } } ;
224
+ proc = Process . Start ( startInfo ) ;
206
225
proc . WaitForExit ( ) ;
207
226
}
208
227
}
209
-
210
- private const string SplitPanePythonCode = """
211
- import iterm2
212
- import sys
213
-
214
- # iTerm needs to be running for this to work
215
- async def main(connection):
216
- app = await iterm2.async_get_app(connection)
217
-
218
- # Foreground the app
219
- await app.async_activate()
220
-
221
- window = app.current_terminal_window
222
- if window is not None:
223
- # Get the current pane so that we can split it.
224
- current_tab = window.current_tab
225
- current_pane = current_tab.current_session
226
-
227
- # Get the total width before splitting.
228
- width = current_pane.grid_size.width
229
-
230
- # Split pane vertically
231
- split_pane = await current_pane.async_split_pane(vertical=True)
232
-
233
- # Get the height of the pane after splitting. This value will be
234
- # slightly smaller than its height before splitting.
235
- height = current_pane.grid_size.height
236
-
237
- # Calculate the new width for both panes using the ratio 0.4 for the new pane.
238
- # Then set the preferred size for both pane sessions.
239
- new_current_width = round(width * 0.6);
240
- new_split_width = width - new_current_width;
241
- current_pane.preferred_size = iterm2.Size(new_current_width, height)
242
- split_pane.preferred_size = iterm2.Size(new_split_width, height);
243
-
244
- # Update the layout, which will change the panes to preferred size.
245
- await current_tab.async_update_layout()
246
-
247
- await split_pane.async_send_text(f'{app_path} --channel {channel}\n')
248
- else:
249
- # You can view this message in the script console.
250
- print("No current iTerm2 window. Make sure you are running in iTerm2.")
251
-
252
- if len(sys.argv) > 1:
253
- app_path = sys.argv[1]
254
- channel = sys.argv[2]
255
-
256
- # Do not specify True for retry. It's possible that the user hasn't enable the Python API for iTerm2,
257
- # and in that case, we want it to fail immediately instead of stucking in retries.
258
- iterm2.run_until_complete(main)
259
- else:
260
- print("Please provide the application path as a command line argument.")
261
- """ ;
262
228
}
0 commit comments