22// Licensed under the MIT License.
33
44import { inject , injectable , named } from 'inversify' ;
5+ import * as os from 'os' ;
56import { Terminal , Uri } from 'vscode' ;
67import { ICondaService , IInterpreterService , InterpreterType , PythonInterpreter } from '../../interpreter/contracts' ;
78import { sendTelemetryEvent } from '../../telemetry' ;
89import { EventName } from '../../telemetry/constants' ;
9- import { ITerminalManager , IWorkspaceService } from '../application/types' ;
10+ import { ITerminalManager } from '../application/types' ;
1011import '../extensions' ;
1112import { traceDecorators , traceError } from '../logger' ;
1213import { IPlatformService } from '../platform/types' ;
13- import { IConfigurationService , Resource } from '../types' ;
14+ import { IConfigurationService , ICurrentProcess , Resource } from '../types' ;
1415import { OSType } from '../utils/platform' ;
1516import { ITerminalActivationCommandProvider , ITerminalHelper , TerminalActivationProviders , TerminalShellType } from './types' ;
1617
@@ -21,7 +22,7 @@ const IS_BASH = /(bash.exe$|bash$)/i;
2122const IS_WSL = / ( w s l .e x e $ ) / i;
2223const IS_ZSH = / ( z s h $ ) / i;
2324const IS_KSH = / ( k s h $ ) / i;
24- const IS_COMMAND = / c m d .e x e $ / i;
25+ const IS_COMMAND = / ( c m d .e x e $ | c m d $ ) / i;
2526const 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;
2627const IS_POWERSHELL_CORE = / ( p w s h .e x e $ | p w s h $ ) / i;
2728const IS_FISH = / ( f i s h $ ) / i;
@@ -41,15 +42,15 @@ export class TerminalHelper implements ITerminalHelper {
4142 private readonly detectableShells : Map < TerminalShellType , RegExp > ;
4243 constructor ( @inject ( IPlatformService ) private readonly platform : IPlatformService ,
4344 @inject ( ITerminalManager ) private readonly terminalManager : ITerminalManager ,
44- @inject ( IWorkspaceService ) private readonly workspace : IWorkspaceService ,
4545 @inject ( ICondaService ) private readonly condaService : ICondaService ,
4646 @inject ( IInterpreterService ) private readonly interpreterService : IInterpreterService ,
4747 @inject ( IConfigurationService ) private readonly configurationService : IConfigurationService ,
4848 @inject ( ITerminalActivationCommandProvider ) @named ( TerminalActivationProviders . conda ) private readonly conda : ITerminalActivationCommandProvider ,
4949 @inject ( ITerminalActivationCommandProvider ) @named ( TerminalActivationProviders . bashCShellFish ) private readonly bashCShellFish : ITerminalActivationCommandProvider ,
5050 @inject ( ITerminalActivationCommandProvider ) @named ( TerminalActivationProviders . commandPromptAndPowerShell ) private readonly commandPromptAndPowerShell : ITerminalActivationCommandProvider ,
5151 @inject ( ITerminalActivationCommandProvider ) @named ( TerminalActivationProviders . pyenv ) private readonly pyenv : ITerminalActivationCommandProvider ,
52- @inject ( ITerminalActivationCommandProvider ) @named ( TerminalActivationProviders . pipenv ) private readonly pipenv : ITerminalActivationCommandProvider
52+ @inject ( ITerminalActivationCommandProvider ) @named ( TerminalActivationProviders . pipenv ) private readonly pipenv : ITerminalActivationCommandProvider ,
53+ @inject ( IConfigurationService ) private readonly currentProcess : ICurrentProcess
5354 ) {
5455 this . detectableShells = new Map < TerminalShellType , RegExp > ( ) ;
5556 this . detectableShells . set ( TerminalShellType . powershell , IS_POWERSHELL ) ;
@@ -68,37 +69,44 @@ export class TerminalHelper implements ITerminalHelper {
6869 public createTerminal ( title ?: string ) : Terminal {
6970 return this . terminalManager . createTerminal ( { name : title } ) ;
7071 }
71- public identifyTerminalShell ( shellPath : string ) : TerminalShellType {
72+ public identifyTerminalShell ( terminal ?: Terminal ) : TerminalShellType {
73+ let shell = TerminalShellType . other ;
74+ let usingDefaultShell = false ;
75+ const terminalProvided = ! ! terminal ;
76+ // Determine shell based on the name of the terminal.
77+ // See solution here https://github.com/microsoft/vscode/issues/74233#issuecomment-497527337
78+ if ( terminal ) {
79+ shell = this . identifyTerminalShellByName ( terminal . name ) ;
80+ }
81+
82+ // If still unable to identify, then use fall back to determine path to the default shell.
83+ if ( shell === TerminalShellType . other ) {
84+ const shellPath = getDefaultShell ( this . platform . osType , this . currentProcess ) ;
85+ shell = Array . from ( this . detectableShells . keys ( ) )
86+ . reduce ( ( matchedShell , shellToDetect ) => {
87+ if ( matchedShell === TerminalShellType . other && this . detectableShells . get ( shellToDetect ) ! . test ( shellPath ) ) {
88+ return shellToDetect ;
89+ }
90+ return matchedShell ;
91+ } , TerminalShellType . other ) ;
92+
93+ // We have restored to using the default shell.
94+ usingDefaultShell = shell !== TerminalShellType . other ;
95+ }
96+ const properties = { failed : shell === TerminalShellType . other , usingDefaultShell, terminalProvided } ;
97+ sendTelemetryEvent ( EventName . TERMINAL_SHELL_IDENTIFICATION , undefined , properties ) ;
98+ return shell ;
99+ }
100+ public identifyTerminalShellByName ( name : string ) : TerminalShellType {
72101 return Array . from ( this . detectableShells . keys ( ) )
73102 . reduce ( ( matchedShell , shellToDetect ) => {
74- if ( matchedShell === TerminalShellType . other && this . detectableShells . get ( shellToDetect ) ! . test ( shellPath ) ) {
103+ if ( matchedShell === TerminalShellType . other && this . detectableShells . get ( shellToDetect ) ! . test ( name ) ) {
75104 return shellToDetect ;
76105 }
77106 return matchedShell ;
78107 } , TerminalShellType . other ) ;
79108 }
80- public getTerminalShellPath ( ) : string {
81- const shellConfig = this . workspace . getConfiguration ( 'terminal.integrated.shell' ) ;
82- let osSection = '' ;
83- switch ( this . platform . osType ) {
84- case OSType . Windows : {
85- osSection = 'windows' ;
86- break ;
87- }
88- case OSType . OSX : {
89- osSection = 'osx' ;
90- break ;
91- }
92- case OSType . Linux : {
93- osSection = 'linux' ;
94- break ;
95- }
96- default : {
97- return '' ;
98- }
99- }
100- return shellConfig . get < string > ( osSection ) ! ;
101- }
109+
102110 public buildCommandForTerminal ( terminalShellType : TerminalShellType , command : string , args : string [ ] ) {
103111 const isPowershell = terminalShellType === TerminalShellType . powershell || terminalShellType === TerminalShellType . powershellCore ;
104112 const commandPrefix = isPowershell ? '& ' : '' ;
@@ -173,3 +181,30 @@ export class TerminalHelper implements ITerminalHelper {
173181 }
174182 }
175183}
184+
185+ /*
186+ 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
187+ This is only a fall back to identify the default shell used by VSC.
188+ On Windows, determine the default shell.
189+ On others, default to bash.
190+ */
191+ function getDefaultShell ( osType : OSType , currentProcess : ICurrentProcess ) : string {
192+ if ( osType === OSType . Windows ) {
193+ return getTerminalDefaultShellWindows ( osType , currentProcess ) ;
194+ }
195+ return '/bin/bash' ;
196+ }
197+ let _TERMINAL_DEFAULT_SHELL_WINDOWS : string | null = null ;
198+ function getTerminalDefaultShellWindows ( osType : OSType , currentProcess : ICurrentProcess ) : string {
199+ if ( ! _TERMINAL_DEFAULT_SHELL_WINDOWS ) {
200+ const isAtLeastWindows10 = osType === OSType . Windows && parseFloat ( os . release ( ) ) >= 10 ;
201+ const is32ProcessOn64Windows = process . env . hasOwnProperty ( 'PROCESSOR_ARCHITEW6432' ) ;
202+ const powerShellPath = `${ process . env . windir } \\${ is32ProcessOn64Windows ? 'Sysnative' : 'System32' } \\WindowsPowerShell\\v1.0\\powershell.exe` ;
203+ _TERMINAL_DEFAULT_SHELL_WINDOWS = isAtLeastWindows10 ? powerShellPath : getWindowsShell ( currentProcess ) ;
204+ }
205+ return _TERMINAL_DEFAULT_SHELL_WINDOWS ;
206+ }
207+
208+ function getWindowsShell ( currentProcess : ICurrentProcess ) : string {
209+ return currentProcess . env . comspec || 'cmd.exe' ;
210+ }
0 commit comments