1
1
// Copyright (c) Microsoft Corporation. All rights reserved.
2
2
// Licensed under the MIT License.
3
3
4
- import { inject , injectable } from 'inversify' ;
4
+ 'use strict' ;
5
+
6
+ import { inject , injectable , multiInject } from 'inversify' ;
5
7
import { Terminal } from 'vscode' ;
6
8
import { sendTelemetryEvent } from '../../telemetry' ;
7
9
import { EventName } from '../../telemetry/constants' ;
8
- import { IWorkspaceService } from '../application/types' ;
9
10
import '../extensions' ;
10
11
import { traceVerbose } from '../logger' ;
11
12
import { IPlatformService } from '../platform/types' ;
12
- import { ICurrentProcess } from '../types' ;
13
13
import { OSType } from '../utils/platform' ;
14
- import { TerminalShellType } from './types' ;
15
-
16
- // Types of shells can be found here:
17
- // 1. https://wiki.ubuntu.com/ChangingShells
18
- const IS_GITBASH = / ( g i t b a s h .e x e $ ) / i;
19
- const IS_BASH = / ( b a s h .e x e $ | b a s h $ ) / i;
20
- const IS_WSL = / ( w s l .e x e $ ) / i;
21
- const IS_ZSH = / ( z s h $ ) / i;
22
- const IS_KSH = / ( k s h $ ) / i;
23
- const IS_COMMAND = / ( c m d .e x e $ | c m d $ ) / i;
24
- const IS_POWERSHELL = / ( p o w e r s h e l l .e x e $ | p o w e r s h e l l $ ) / i;
25
- const IS_POWERSHELL_CORE = / ( p w s h .e x e $ | p w s h $ ) / i;
26
- const IS_FISH = / ( f i s h $ ) / i;
27
- const IS_CSHELL = / ( c s h $ ) / i;
28
- const IS_TCSHELL = / ( t c s h $ ) / i;
29
- const IS_XONSH = / ( x o n s h $ ) / i;
14
+ import { IShellDetector , ShellIdentificationTelemetry , TerminalShellType } from './types' ;
30
15
31
16
const defaultOSShells = {
32
17
[ OSType . Linux ] : TerminalShellType . bash ,
33
18
[ OSType . OSX ] : TerminalShellType . bash ,
34
19
[ OSType . Windows ] : TerminalShellType . commandPrompt ,
35
- [ OSType . Unknown ] : undefined
20
+ [ OSType . Unknown ] : TerminalShellType . other
36
21
} ;
37
22
38
- type ShellIdentificationTelemetry = {
39
- failed : boolean ;
40
- terminalProvided : boolean ;
41
- shellIdentificationSource : 'terminalName' | 'settings' | 'environment' | 'default' ;
42
- hasCustomShell : undefined | boolean ;
43
- hasShellInEnv : undefined | boolean ;
44
- } ;
45
- const detectableShells = new Map < TerminalShellType , RegExp > ( ) ;
46
- detectableShells . set ( TerminalShellType . powershell , IS_POWERSHELL ) ;
47
- detectableShells . set ( TerminalShellType . gitbash , IS_GITBASH ) ;
48
- detectableShells . set ( TerminalShellType . bash , IS_BASH ) ;
49
- detectableShells . set ( TerminalShellType . wsl , IS_WSL ) ;
50
- detectableShells . set ( TerminalShellType . zsh , IS_ZSH ) ;
51
- detectableShells . set ( TerminalShellType . ksh , IS_KSH ) ;
52
- detectableShells . set ( TerminalShellType . commandPrompt , IS_COMMAND ) ;
53
- detectableShells . set ( TerminalShellType . fish , IS_FISH ) ;
54
- detectableShells . set ( TerminalShellType . tcshell , IS_TCSHELL ) ;
55
- detectableShells . set ( TerminalShellType . cshell , IS_CSHELL ) ;
56
- detectableShells . set ( TerminalShellType . powershellCore , IS_POWERSHELL_CORE ) ;
57
- detectableShells . set ( TerminalShellType . xonsh , IS_XONSH ) ;
58
-
59
23
@injectable ( )
60
24
export class ShellDetector {
61
25
constructor ( @inject ( IPlatformService ) private readonly platform : IPlatformService ,
62
- @inject ( ICurrentProcess ) private readonly currentProcess : ICurrentProcess ,
63
- @inject ( IWorkspaceService ) private readonly workspace : IWorkspaceService
26
+ @multiInject ( IShellDetector ) private readonly shellDetectors : IShellDetector [ ]
64
27
) { }
65
28
/**
66
29
* Logic is as follows:
@@ -75,7 +38,7 @@ export class ShellDetector {
75
38
* @memberof TerminalHelper
76
39
*/
77
40
public identifyTerminalShell ( terminal ?: Terminal ) : TerminalShellType {
78
- let shell = TerminalShellType . other ;
41
+ let shell : TerminalShellType | undefined ;
79
42
const telemetryProperties : ShellIdentificationTelemetry = {
80
43
failed : true ,
81
44
shellIdentificationSource : 'default' ,
@@ -84,19 +47,15 @@ export class ShellDetector {
84
47
hasShellInEnv : undefined
85
48
} ;
86
49
87
- // Step 1. Determine shell based on the name of the terminal.
88
- if ( terminal ) {
89
- shell = this . identifyShellByTerminalName ( terminal . name , telemetryProperties ) ;
90
- }
91
-
92
- // Step 2. Detemrine shell based on user settings.
93
- if ( shell === TerminalShellType . other ) {
94
- shell = this . identifyShellFromSettings ( telemetryProperties ) ;
95
- }
50
+ // Sort in order of priority and then identify the shell in terminal.
51
+ const shellDetectors = this . shellDetectors . slice ( ) ;
52
+ shellDetectors . sort ( ( a , b ) => a . priority < b . priority ? 1 : 0 ) ;
96
53
97
- // Step 3. Determine shell based on user environment.
98
- if ( shell === TerminalShellType . other ) {
99
- shell = this . identifyShellFromUserEnv ( telemetryProperties ) ;
54
+ for ( const detector of shellDetectors ) {
55
+ shell = detector . identify ( telemetryProperties , terminal ) ;
56
+ if ( shell ) {
57
+ break ;
58
+ }
100
59
}
101
60
102
61
// This information is useful in determining how well we identify shells on users machines.
@@ -106,104 +65,9 @@ export class ShellDetector {
106
65
traceVerbose ( `Shell identified as '${ shell } '` ) ;
107
66
108
67
// If we could not identify the shell, use the defaults.
109
- return shell === TerminalShellType . other ? ( defaultOSShells [ this . platform . osType ] || TerminalShellType . other ) : shell ;
110
- }
111
- public getTerminalShellPath ( ) : string | undefined {
112
- const shellConfig = this . workspace . getConfiguration ( 'terminal.integrated.shell' ) ;
113
- let osSection = '' ;
114
- switch ( this . platform . osType ) {
115
- case OSType . Windows : {
116
- osSection = 'windows' ;
117
- break ;
118
- }
119
- case OSType . OSX : {
120
- osSection = 'osx' ;
121
- break ;
122
- }
123
- case OSType . Linux : {
124
- osSection = 'linux' ;
125
- break ;
126
- }
127
- default : {
128
- return '' ;
129
- }
130
- }
131
- return shellConfig . get < string > ( osSection ) ! ;
132
- }
133
- public getDefaultPlatformShell ( ) : string {
134
- return getDefaultShell ( this . platform , this . currentProcess ) ;
135
- }
136
- public identifyShellByTerminalName ( name : string , telemetryProperties : ShellIdentificationTelemetry ) : TerminalShellType {
137
- const shell = Array . from ( detectableShells . keys ( ) )
138
- . reduce ( ( matchedShell , shellToDetect ) => {
139
- if ( matchedShell === TerminalShellType . other && detectableShells . get ( shellToDetect ) ! . test ( name ) ) {
140
- return shellToDetect ;
141
- }
142
- return matchedShell ;
143
- } , TerminalShellType . other ) ;
144
- traceVerbose ( `Terminal name '${ name } ' identified as shell '${ shell } '` ) ;
145
- telemetryProperties . shellIdentificationSource = shell === TerminalShellType . other ? telemetryProperties . shellIdentificationSource : 'terminalName' ;
146
- return shell ;
147
- }
148
- public identifyShellFromSettings ( telemetryProperties : ShellIdentificationTelemetry ) : TerminalShellType {
149
- const shellPath = this . getTerminalShellPath ( ) ;
150
- telemetryProperties . hasCustomShell = ! ! shellPath ;
151
- const shell = shellPath ? this . identifyShellFromShellPath ( shellPath ) : TerminalShellType . other ;
152
-
153
- if ( shell !== TerminalShellType . other ) {
154
- telemetryProperties . shellIdentificationSource = 'environment' ;
155
- }
156
- telemetryProperties . shellIdentificationSource = 'settings' ;
157
- traceVerbose ( `Shell path from user settings '${ shellPath } '` ) ;
158
- return shell ;
159
- }
160
-
161
- public identifyShellFromUserEnv ( telemetryProperties : ShellIdentificationTelemetry ) : TerminalShellType {
162
- const shellPath = this . getDefaultPlatformShell ( ) ;
163
- telemetryProperties . hasShellInEnv = ! ! shellPath ;
164
- const shell = this . identifyShellFromShellPath ( shellPath ) ;
165
-
166
- if ( shell !== TerminalShellType . other ) {
167
- telemetryProperties . shellIdentificationSource = 'environment' ;
68
+ if ( shell === undefined || shell === TerminalShellType . other ) {
69
+ shell = defaultOSShells [ this . platform . osType ] ;
168
70
}
169
- traceVerbose ( `Shell path from user env '${ shellPath } '` ) ;
170
71
return shell ;
171
72
}
172
- public identifyShellFromShellPath ( shellPath : string ) : TerminalShellType {
173
- const shell = Array . from ( detectableShells . keys ( ) )
174
- . reduce ( ( matchedShell , shellToDetect ) => {
175
- if ( matchedShell === TerminalShellType . other && detectableShells . get ( shellToDetect ) ! . test ( shellPath ) ) {
176
- return shellToDetect ;
177
- }
178
- return matchedShell ;
179
- } , TerminalShellType . other ) ;
180
-
181
- traceVerbose ( `Shell path '${ shellPath } '` ) ;
182
- traceVerbose ( `Shell path identified as shell '${ shell } '` ) ;
183
- return shell ;
184
- }
185
- }
186
-
187
- /*
188
- The following code is based on VS Code from https://github.com/microsoft/vscode/blob/5c65d9bfa4c56538150d7f3066318e0db2c6151f/src/vs/workbench/contrib/terminal/node/terminal.ts#L12-L55
189
- This is only a fall back to identify the default shell used by VSC.
190
- On Windows, determine the default shell.
191
- On others, default to bash.
192
- */
193
- function getDefaultShell ( platform : IPlatformService , currentProcess : ICurrentProcess ) : string {
194
- if ( platform . osType === OSType . Windows ) {
195
- return getTerminalDefaultShellWindows ( platform , currentProcess ) ;
196
- }
197
-
198
- return currentProcess . env . SHELL && currentProcess . env . SHELL !== '/bin/false' ? currentProcess . env . SHELL : '/bin/bash' ;
199
- }
200
- function getTerminalDefaultShellWindows ( platform : IPlatformService , currentProcess : ICurrentProcess ) : string {
201
- const isAtLeastWindows10 = parseFloat ( platform . osRelease ) >= 10 ;
202
- const is32ProcessOn64Windows = currentProcess . env . hasOwnProperty ( 'PROCESSOR_ARCHITEW6432' ) ;
203
- const powerShellPath = `${ currentProcess . env . windir } \\${ is32ProcessOn64Windows ? 'Sysnative' : 'System32' } \\WindowsPowerShell\\v1.0\\powershell.exe` ;
204
- return isAtLeastWindows10 ? powerShellPath : getWindowsShell ( currentProcess ) ;
205
- }
206
-
207
- function getWindowsShell ( currentProcess : ICurrentProcess ) : string {
208
- return currentProcess . env . comspec || 'cmd.exe' ;
209
73
}
0 commit comments