diff --git a/app/src/lib/custom-integration.ts b/app/src/lib/custom-integration.ts index 55a9a57d6b9..116ac44397d 100644 --- a/app/src/lib/custom-integration.ts +++ b/app/src/lib/custom-integration.ts @@ -193,12 +193,5 @@ export function spawnCustomIntegration( args: readonly string[], options?: SpawnOptions ): ChildProcess { - // On Windows, we need to wrap the arguments and the command in quotes, - // otherwise the shell will split them by spaces again after invoking spawn. - if (__WIN32__ && options?.shell) { - command = `"${command}"` - args = args.map(a => `"${a}"`) - } - return options ? spawn(command, args, options) : spawn(command, args) } diff --git a/app/src/lib/editors/found-editor.ts b/app/src/lib/editors/found-editor.ts index c6a54597cc5..f64241749f4 100644 --- a/app/src/lib/editors/found-editor.ts +++ b/app/src/lib/editors/found-editor.ts @@ -1,11 +1,4 @@ export interface IFoundEditor { readonly editor: T readonly path: string - /** - * Indicate to Desktop to launch the editor with the `shell: true` option included. - * - * This is available to all platforms, but is only currently used by some Windows - * editors as their launch programs end in `.cmd` - */ - readonly usesShell?: boolean } diff --git a/app/src/lib/editors/launch.ts b/app/src/lib/editors/launch.ts index ed00ff396ff..68099797156 100644 --- a/app/src/lib/editors/launch.ts +++ b/app/src/lib/editors/launch.ts @@ -36,9 +36,7 @@ export async function launchExternalEditor( } try { - if (editor.usesShell) { - spawn(`"${editorPath}"`, [`"${fullPath}"`], { ...opts, shell: true }) - } else if (__DARWIN__) { + if (__DARWIN__) { // In macOS we can use `open`, which will open the right executable file // for us, we only need the path to the editor .app folder. spawn('open', ['-a', editorPath, fullPath], opts) @@ -94,14 +92,7 @@ export async function launchCustomExternalEditor( const args = expandTargetPathArgument(argv, fullPath) try { - // This logic around `usesShell` is also used in Windows `getAvailableEditors` implementation - const usesShell = editorPath.endsWith('.cmd') - if (usesShell) { - spawnCustomIntegration(editorPath, args, { - ...opts, - shell: true, - }) - } else if (__DARWIN__ && customEditor.bundleID) { + if (__DARWIN__ && customEditor.bundleID) { // In macOS we can use `open` if it's an app (i.e. if we have a bundleID), // which will open the right executable file for us, we only need the path // to the editor .app folder. diff --git a/app/src/lib/editors/shared.ts b/app/src/lib/editors/shared.ts index 3375efdddcc..0440cddbe9f 100644 --- a/app/src/lib/editors/shared.ts +++ b/app/src/lib/editors/shared.ts @@ -10,10 +10,6 @@ export type FoundEditor = { * The executable associated with the editor to launch */ path: string - /** - * the editor requires a shell spawn to launch - */ - usesShell?: boolean } interface IErrorMetadata { diff --git a/app/src/lib/editors/win32.ts b/app/src/lib/editors/win32.ts index a2411b7f293..4df4a5e9ebf 100644 --- a/app/src/lib/editors/win32.ts +++ b/app/src/lib/editors/win32.ts @@ -1,6 +1,7 @@ import * as Path from 'path' import { + enumerateKeys, enumerateValues, HKEY, RegistryValue, @@ -9,6 +10,7 @@ import { import { pathExists } from '../../ui/lib/path-exists' import { IFoundEditor } from './found-editor' +import memoizeOne from 'memoize-one' interface IWindowsAppInformation { displayName: string @@ -60,16 +62,6 @@ type WindowsExternalEditor = { /** Value of the Publisher registry key that belongs to this editor. */ readonly publishers: string[] - - /** - * Default shell script name for JetBrains Product - * To get the script name go to: - * JetBrains Toolbox > Editor settings > Shell script name - * - * Go to `/docs/techical/editor-integration.md` for more information on - * how to use this field. - */ - readonly jetBrainsToolboxScriptName?: string } & WindowsExternalEditorPathInfo const registryKey = (key: HKEY, ...subKeys: string[]): RegistryKey => ({ @@ -166,27 +158,6 @@ const getCleanInstallLocationFromDisplayIcon = ( * entry here to add support for your favorite editor. **/ const editors: WindowsExternalEditor[] = [ - { - name: 'Atom', - registryKeys: [CurrentUserUninstallKey('atom')], - executableShimPaths: [['bin', 'atom.cmd']], - displayNamePrefixes: ['Atom'], - publishers: ['GitHub Inc.'], - }, - { - name: 'Atom Beta', - registryKeys: [CurrentUserUninstallKey('atom-beta')], - executableShimPaths: [['bin', 'atom-beta.cmd']], - displayNamePrefixes: ['Atom Beta'], - publishers: ['GitHub Inc.'], - }, - { - name: 'Atom Nightly', - registryKeys: [CurrentUserUninstallKey('atom-nightly')], - executableShimPaths: [['bin', 'atom-nightly.cmd']], - displayNamePrefixes: ['Atom Nightly'], - publishers: ['GitHub Inc.'], - }, { name: 'Visual Studio Code', registryKeys: [ @@ -205,7 +176,7 @@ const editors: WindowsExternalEditor[] = [ // ARM64 version of VSCode (system) LocalMachineUninstallKey('{A5270FC5-65AD-483E-AC30-2C276B63D0AC}_is1'), ], - executableShimPaths: [['bin', 'code.cmd']], + executableShimPaths: [['code.exe']], displayNamePrefixes: ['Microsoft Visual Studio Code'], publishers: ['Microsoft Corporation'], }, @@ -227,7 +198,7 @@ const editors: WindowsExternalEditor[] = [ // ARM64 version of VSCode (system) LocalMachineUninstallKey('{0AEDB616-9614-463B-97D7-119DD86CCA64}_is1'), ], - executableShimPaths: [['bin', 'code-insiders.cmd']], + executableShimPaths: [['Code - Insiders.exe']], displayNamePrefixes: ['Microsoft Visual Studio Code Insiders'], publishers: ['Microsoft Corporation'], }, @@ -261,7 +232,7 @@ const editors: WindowsExternalEditor[] = [ // ARM64 version of VSCodium (system) - old key LocalMachineUninstallKey('{D1ACE434-89C5-48D1-88D3-E2991DF85475}_is1'), ], - executableShimPaths: [['bin', 'codium.cmd']], + executableShimPaths: [['VSCodium.exe']], displayNamePrefixes: ['VSCodium'], publishers: ['VSCodium', 'Microsoft Corporation'], }, @@ -283,7 +254,7 @@ const editors: WindowsExternalEditor[] = [ // ARM64 version of VSCodium - Insiders (system) LocalMachineUninstallKey('{44721278-64C6-4513-BC45-D48E07830599}_is1'), ], - executableShimPaths: [['bin', 'codium-insiders.cmd']], + executableShimPaths: [['VSCodium - Insiders.exe']], displayNamePrefixes: ['VSCodium Insiders', 'VSCodium (Insiders)'], publishers: ['VSCodium'], }, @@ -377,7 +348,6 @@ const editors: WindowsExternalEditor[] = [ name: 'JetBrains Webstorm', registryKeys: registryKeysForJetBrainsIDE('WebStorm'), executableShimPaths: executableShimPathsForJetBrainsIDE('webstorm'), - jetBrainsToolboxScriptName: 'webstorm', displayNamePrefixes: ['WebStorm'], publishers: ['JetBrains s.r.o.'], }, @@ -385,7 +355,6 @@ const editors: WindowsExternalEditor[] = [ name: 'JetBrains PhpStorm', registryKeys: registryKeysForJetBrainsIDE('PhpStorm'), executableShimPaths: executableShimPathsForJetBrainsIDE('phpstorm'), - jetBrainsToolboxScriptName: 'phpstorm', displayNamePrefixes: ['PhpStorm'], publishers: ['JetBrains s.r.o.'], }, @@ -393,7 +362,6 @@ const editors: WindowsExternalEditor[] = [ name: 'Android Studio', registryKeys: [LocalMachineUninstallKey('Android Studio')], installLocationRegistryKey: 'UninstallString', - jetBrainsToolboxScriptName: 'studio', executableShimPaths: [ ['..', 'bin', `studio64.exe`], ['..', 'bin', `studio.exe`], @@ -417,7 +385,6 @@ const editors: WindowsExternalEditor[] = [ name: 'JetBrains Rider', registryKeys: registryKeysForJetBrainsIDE('JetBrains Rider'), executableShimPaths: executableShimPathsForJetBrainsIDE('rider'), - jetBrainsToolboxScriptName: 'rider', displayNamePrefixes: ['JetBrains Rider'], publishers: ['JetBrains s.r.o.'], }, @@ -432,7 +399,6 @@ const editors: WindowsExternalEditor[] = [ name: 'JetBrains IntelliJ Idea', registryKeys: registryKeysForJetBrainsIDE('IntelliJ IDEA'), executableShimPaths: executableShimPathsForJetBrainsIDE('idea'), - jetBrainsToolboxScriptName: 'idea', displayNamePrefixes: ['IntelliJ IDEA '], publishers: ['JetBrains s.r.o.'], }, @@ -449,7 +415,6 @@ const editors: WindowsExternalEditor[] = [ name: 'JetBrains PyCharm', registryKeys: registryKeysForJetBrainsIDE('PyCharm'), executableShimPaths: executableShimPathsForJetBrainsIDE('pycharm'), - jetBrainsToolboxScriptName: 'pycharm', displayNamePrefixes: ['PyCharm '], publishers: ['JetBrains s.r.o.'], }, @@ -464,7 +429,6 @@ const editors: WindowsExternalEditor[] = [ name: 'JetBrains CLion', registryKeys: registryKeysForJetBrainsIDE('CLion'), executableShimPaths: executableShimPathsForJetBrainsIDE('clion'), - jetBrainsToolboxScriptName: 'clion', displayNamePrefixes: ['CLion '], publishers: ['JetBrains s.r.o.'], }, @@ -472,7 +436,6 @@ const editors: WindowsExternalEditor[] = [ name: 'JetBrains RubyMine', registryKeys: registryKeysForJetBrainsIDE('RubyMine'), executableShimPaths: executableShimPathsForJetBrainsIDE('rubymine'), - jetBrainsToolboxScriptName: 'rubymine', displayNamePrefixes: ['RubyMine '], publishers: ['JetBrains s.r.o.'], }, @@ -480,14 +443,12 @@ const editors: WindowsExternalEditor[] = [ name: 'JetBrains GoLand', registryKeys: registryKeysForJetBrainsIDE('GoLand'), executableShimPaths: executableShimPathsForJetBrainsIDE('goland'), - jetBrainsToolboxScriptName: 'goland', displayNamePrefixes: ['GoLand '], publishers: ['JetBrains s.r.o.'], }, { name: 'JetBrains Fleet', registryKeys: [LocalMachineUninstallKey('Fleet')], - jetBrainsToolboxScriptName: 'fleet', installLocationRegistryKey: 'DisplayIcon', displayNamePrefixes: ['Fleet '], publishers: ['JetBrains s.r.o.'], @@ -496,7 +457,6 @@ const editors: WindowsExternalEditor[] = [ name: 'JetBrains DataSpell', registryKeys: registryKeysForJetBrainsIDE('DataSpell'), executableShimPaths: executableShimPathsForJetBrainsIDE('dataspell'), - jetBrainsToolboxScriptName: 'dataspell', displayNamePrefixes: ['DataSpell '], publishers: ['JetBrains s.r.o.'], }, @@ -504,7 +464,6 @@ const editors: WindowsExternalEditor[] = [ name: 'JetBrains RustRover', registryKeys: registryKeysForJetBrainsIDE('RustRover'), executableShimPaths: executableShimPathsForJetBrainsIDE('rustrover'), - jetBrainsToolboxScriptName: 'rustrover', displayNamePrefixes: ['RustRover '], publishers: ['JetBrains s.r.o.'], }, @@ -582,41 +541,36 @@ async function findApplication(editor: WindowsExternalEditor) { } } - return findJetBrainsToolboxApplication(editor) + return undefined } -/** - * Find JetBrain products installed through JetBrains Toolbox - */ -async function findJetBrainsToolboxApplication(editor: WindowsExternalEditor) { - if (!editor.jetBrainsToolboxScriptName) { - return null - } - - const toolboxRegistryReference = [ - CurrentUserUninstallKey('toolbox'), - Wow64LocalMachineUninstallKey('toolbox'), - ] - - for (const { key, subKey } of toolboxRegistryReference) { - const keys = enumerateValues(key, subKey) - if (keys.length > 0) { - const editorPathInToolbox = Path.join( - getKeyOrEmpty(keys, 'UninstallString'), - '..', - '..', - 'scripts', - `${editor.jetBrainsToolboxScriptName}.cmd` - ) - const exists = await pathExists(editorPathInToolbox) - if (exists) { - return editorPathInToolbox +const getJetBrainsToolboxEditors = memoizeOne(async () => { + const re = /^JetBrains Toolbox \((.*)\)/ + const editors = new Array() + + for (const parent of [uninstallSubKey, wow64UninstallSubKey]) { + for (const key of enumerateKeys(HKEY.HKEY_CURRENT_USER, parent)) { + const m = re.exec(key) + if (m) { + const [name, product] = m + editors.push({ + name, + installLocationRegistryKey: 'DisplayIcon', + registryKeys: [ + { + key: HKEY.HKEY_CURRENT_USER, + subKey: `${parent}\\${key}`, + }, + ], + displayNamePrefixes: [product], + publishers: ['JetBrains s.r.o.'], + }) } } } - return null -} + return editors +}) /** * Lookup known external editors using the Windows registry to find installed @@ -626,16 +580,19 @@ export async function getAvailableEditors(): Promise< ReadonlyArray> > { const results: Array> = [] + const candidates = [ + ...editors, + ...(await getJetBrainsToolboxEditors().catch(e => { + log.error(`Failed resolving JetBrains Toolbox products`, e) + return [] + })), + ] - for (const editor of editors) { + for (const editor of candidates) { const path = await findApplication(editor) if (path) { - results.push({ - editor: editor.name, - path, - usesShell: path.endsWith('.cmd'), - }) + results.push({ editor: editor.name, path }) } }