shell.ts 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227
  1. import * as vscode from "vscode"
  2. import { userInfo } from "os"
  3. const SHELL_PATHS = {
  4. // Windows paths
  5. POWERSHELL_7: "C:\\Program Files\\PowerShell\\7\\pwsh.exe",
  6. POWERSHELL_LEGACY: "C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe",
  7. CMD: "C:\\Windows\\System32\\cmd.exe",
  8. WSL_BASH: "/bin/bash",
  9. // Unix paths
  10. MAC_DEFAULT: "/bin/zsh",
  11. LINUX_DEFAULT: "/bin/bash",
  12. CSH: "/bin/csh",
  13. BASH: "/bin/bash",
  14. KSH: "/bin/ksh",
  15. SH: "/bin/sh",
  16. ZSH: "/bin/zsh",
  17. DASH: "/bin/dash",
  18. TCSH: "/bin/tcsh",
  19. FALLBACK: "/bin/sh",
  20. } as const
  21. interface MacTerminalProfile {
  22. path?: string
  23. }
  24. type MacTerminalProfiles = Record<string, MacTerminalProfile>
  25. interface WindowsTerminalProfile {
  26. path?: string
  27. source?: "PowerShell" | "WSL"
  28. }
  29. type WindowsTerminalProfiles = Record<string, WindowsTerminalProfile>
  30. interface LinuxTerminalProfile {
  31. path?: string
  32. }
  33. type LinuxTerminalProfiles = Record<string, LinuxTerminalProfile>
  34. // -----------------------------------------------------
  35. // 1) VS Code Terminal Configuration Helpers
  36. // -----------------------------------------------------
  37. function getWindowsTerminalConfig() {
  38. try {
  39. const config = vscode.workspace.getConfiguration("terminal.integrated")
  40. const defaultProfileName = config.get<string>("defaultProfile.windows")
  41. const profiles = config.get<WindowsTerminalProfiles>("profiles.windows") || {}
  42. return { defaultProfileName, profiles }
  43. } catch {
  44. return { defaultProfileName: null, profiles: {} as WindowsTerminalProfiles }
  45. }
  46. }
  47. function getMacTerminalConfig() {
  48. try {
  49. const config = vscode.workspace.getConfiguration("terminal.integrated")
  50. const defaultProfileName = config.get<string>("defaultProfile.osx")
  51. const profiles = config.get<MacTerminalProfiles>("profiles.osx") || {}
  52. return { defaultProfileName, profiles }
  53. } catch {
  54. return { defaultProfileName: null, profiles: {} as MacTerminalProfiles }
  55. }
  56. }
  57. function getLinuxTerminalConfig() {
  58. try {
  59. const config = vscode.workspace.getConfiguration("terminal.integrated")
  60. const defaultProfileName = config.get<string>("defaultProfile.linux")
  61. const profiles = config.get<LinuxTerminalProfiles>("profiles.linux") || {}
  62. return { defaultProfileName, profiles }
  63. } catch {
  64. return { defaultProfileName: null, profiles: {} as LinuxTerminalProfiles }
  65. }
  66. }
  67. // -----------------------------------------------------
  68. // 2) Platform-Specific VS Code Shell Retrieval
  69. // -----------------------------------------------------
  70. /** Attempts to retrieve a shell path from VS Code config on Windows. */
  71. function getWindowsShellFromVSCode(): string | null {
  72. const { defaultProfileName, profiles } = getWindowsTerminalConfig()
  73. if (!defaultProfileName) {
  74. return null
  75. }
  76. const profile = profiles[defaultProfileName]
  77. // If the profile name indicates PowerShell, do version-based detection.
  78. // In testing it was found these typically do not have a path, and this
  79. // implementation manages to deductively get the corect version of PowerShell
  80. if (defaultProfileName.toLowerCase().includes("powershell")) {
  81. if (profile?.path) {
  82. // If there's an explicit PowerShell path, return that
  83. return profile.path
  84. } else if (profile?.source === "PowerShell") {
  85. // If the profile is sourced from PowerShell, assume the newest
  86. return SHELL_PATHS.POWERSHELL_7
  87. }
  88. // Otherwise, assume legacy Windows PowerShell
  89. return SHELL_PATHS.POWERSHELL_LEGACY
  90. }
  91. // If there's a specific path, return that immediately
  92. if (profile?.path) {
  93. return profile.path
  94. }
  95. // If the profile indicates WSL
  96. if (profile?.source === "WSL" || defaultProfileName.toLowerCase().includes("wsl")) {
  97. return SHELL_PATHS.WSL_BASH
  98. }
  99. // If nothing special detected, we assume cmd
  100. return SHELL_PATHS.CMD
  101. }
  102. /** Attempts to retrieve a shell path from VS Code config on macOS. */
  103. function getMacShellFromVSCode(): string | null {
  104. const { defaultProfileName, profiles } = getMacTerminalConfig()
  105. if (!defaultProfileName) {
  106. return null
  107. }
  108. const profile = profiles[defaultProfileName]
  109. return profile?.path || null
  110. }
  111. /** Attempts to retrieve a shell path from VS Code config on Linux. */
  112. function getLinuxShellFromVSCode(): string | null {
  113. const { defaultProfileName, profiles } = getLinuxTerminalConfig()
  114. if (!defaultProfileName) {
  115. return null
  116. }
  117. const profile = profiles[defaultProfileName]
  118. return profile?.path || null
  119. }
  120. // -----------------------------------------------------
  121. // 3) General Fallback Helpers
  122. // -----------------------------------------------------
  123. /**
  124. * Tries to get a user’s shell from os.userInfo() (works on Unix if the
  125. * underlying system call is supported). Returns null on error or if not found.
  126. */
  127. function getShellFromUserInfo(): string | null {
  128. try {
  129. const { shell } = userInfo()
  130. return shell || null
  131. } catch {
  132. return null
  133. }
  134. }
  135. /** Returns the environment-based shell variable, or null if not set. */
  136. function getShellFromEnv(): string | null {
  137. const { env } = process
  138. if (process.platform === "win32") {
  139. // On Windows, COMSPEC typically holds cmd.exe
  140. return env.COMSPEC || "C:\\Windows\\System32\\cmd.exe"
  141. }
  142. if (process.platform === "darwin") {
  143. // On macOS/Linux, SHELL is commonly the environment variable
  144. return env.SHELL || "/bin/zsh"
  145. }
  146. if (process.platform === "linux") {
  147. // On Linux, SHELL is commonly the environment variable
  148. return env.SHELL || "/bin/bash"
  149. }
  150. return null
  151. }
  152. // -----------------------------------------------------
  153. // 4) Publicly Exposed Shell Getter
  154. // -----------------------------------------------------
  155. export function getShell(): string {
  156. // 1. Check VS Code config first.
  157. if (process.platform === "win32") {
  158. // Special logic for Windows
  159. const windowsShell = getWindowsShellFromVSCode()
  160. if (windowsShell) {
  161. return windowsShell
  162. }
  163. } else if (process.platform === "darwin") {
  164. // macOS from VS Code
  165. const macShell = getMacShellFromVSCode()
  166. if (macShell) {
  167. return macShell
  168. }
  169. } else if (process.platform === "linux") {
  170. // Linux from VS Code
  171. const linuxShell = getLinuxShellFromVSCode()
  172. if (linuxShell) {
  173. return linuxShell
  174. }
  175. }
  176. // 2. If no shell from VS Code, try userInfo()
  177. const userInfoShell = getShellFromUserInfo()
  178. if (userInfoShell) {
  179. return userInfoShell
  180. }
  181. // 3. If still nothing, try environment variable
  182. const envShell = getShellFromEnv()
  183. if (envShell) {
  184. return envShell
  185. }
  186. // 4. Finally, fall back to a default
  187. if (process.platform === "win32") {
  188. // On Windows, if we got here, we have no config, no COMSPEC, and one very messed up operating system.
  189. // Use CMD as a last resort
  190. return SHELL_PATHS.CMD
  191. }
  192. // On macOS/Linux, fallback to a POSIX shell - This is the behavior of our old shell detection method.
  193. return SHELL_PATHS.FALLBACK
  194. }