mirror of
https://github.com/desktop/desktop
synced 2024-06-30 22:54:41 +00:00
Merge pull request #18096 from lhvy/alacritty
Add support for multiple macOS shell bundle IDs
This commit is contained in:
commit
329b6c2e93
|
@ -1,8 +1,8 @@
|
|||
import { spawn, ChildProcess } from 'child_process'
|
||||
import { assertNever } from '../fatal-error'
|
||||
import { IFoundShell } from './found-shell'
|
||||
import appPath from 'app-path'
|
||||
import { parseEnumValue } from '../enum'
|
||||
import { FoundShell } from './shared'
|
||||
|
||||
export enum Shell {
|
||||
Terminal = 'Terminal',
|
||||
|
@ -22,113 +22,142 @@ export function parse(label: string): Shell {
|
|||
return parseEnumValue(Shell, label) ?? Default
|
||||
}
|
||||
|
||||
function getBundleID(shell: Shell): string {
|
||||
function getBundleIDs(shell: Shell): ReadonlyArray<string> {
|
||||
switch (shell) {
|
||||
case Shell.Terminal:
|
||||
return 'com.apple.Terminal'
|
||||
return ['com.apple.Terminal']
|
||||
case Shell.iTerm2:
|
||||
return 'com.googlecode.iterm2'
|
||||
return ['com.googlecode.iterm2']
|
||||
case Shell.Hyper:
|
||||
return 'co.zeit.hyper'
|
||||
return ['co.zeit.hyper']
|
||||
case Shell.PowerShellCore:
|
||||
return 'com.microsoft.powershell'
|
||||
return ['com.microsoft.powershell']
|
||||
case Shell.Kitty:
|
||||
return 'net.kovidgoyal.kitty'
|
||||
return ['net.kovidgoyal.kitty']
|
||||
case Shell.Alacritty:
|
||||
return 'org.alacritty'
|
||||
return ['org.alacritty', 'io.alacritty']
|
||||
case Shell.Tabby:
|
||||
return 'org.tabby'
|
||||
return ['org.tabby']
|
||||
case Shell.WezTerm:
|
||||
return 'com.github.wez.wezterm'
|
||||
return ['com.github.wez.wezterm']
|
||||
case Shell.Warp:
|
||||
return 'dev.warp.Warp-Stable'
|
||||
return ['dev.warp.Warp-Stable']
|
||||
default:
|
||||
return assertNever(shell, `Unknown shell: ${shell}`)
|
||||
}
|
||||
}
|
||||
|
||||
async function getShellPath(shell: Shell): Promise<string | null> {
|
||||
const bundleId = getBundleID(shell)
|
||||
try {
|
||||
return await appPath(bundleId)
|
||||
} catch (e) {
|
||||
// `appPath` will raise an error if it cannot find the program.
|
||||
return null
|
||||
async function getShellInfo(
|
||||
shell: Shell
|
||||
): Promise<{ path: string; bundleID: string } | null> {
|
||||
const bundleIds = getBundleIDs(shell)
|
||||
for (const id of bundleIds) {
|
||||
try {
|
||||
const path = await appPath(id)
|
||||
return { path, bundleID: id }
|
||||
} catch (error) {
|
||||
log.debug(
|
||||
`Unable to locate ${shell} installation with bundle id ${id}`,
|
||||
error
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
export async function getAvailableShells(): Promise<
|
||||
ReadonlyArray<IFoundShell<Shell>>
|
||||
ReadonlyArray<FoundShell<Shell>>
|
||||
> {
|
||||
const [
|
||||
terminalPath,
|
||||
hyperPath,
|
||||
iTermPath,
|
||||
powerShellCorePath,
|
||||
kittyPath,
|
||||
alacrittyPath,
|
||||
tabbyPath,
|
||||
wezTermPath,
|
||||
warpPath,
|
||||
terminalInfo,
|
||||
hyperInfo,
|
||||
iTermInfo,
|
||||
powerShellCoreInfo,
|
||||
kittyInfo,
|
||||
alacrittyInfo,
|
||||
tabbyInfo,
|
||||
wezTermInfo,
|
||||
warpInfo,
|
||||
] = await Promise.all([
|
||||
getShellPath(Shell.Terminal),
|
||||
getShellPath(Shell.Hyper),
|
||||
getShellPath(Shell.iTerm2),
|
||||
getShellPath(Shell.PowerShellCore),
|
||||
getShellPath(Shell.Kitty),
|
||||
getShellPath(Shell.Alacritty),
|
||||
getShellPath(Shell.Tabby),
|
||||
getShellPath(Shell.WezTerm),
|
||||
getShellPath(Shell.Warp),
|
||||
getShellInfo(Shell.Terminal),
|
||||
getShellInfo(Shell.Hyper),
|
||||
getShellInfo(Shell.iTerm2),
|
||||
getShellInfo(Shell.PowerShellCore),
|
||||
getShellInfo(Shell.Kitty),
|
||||
getShellInfo(Shell.Alacritty),
|
||||
getShellInfo(Shell.Tabby),
|
||||
getShellInfo(Shell.WezTerm),
|
||||
getShellInfo(Shell.Warp),
|
||||
])
|
||||
|
||||
const shells: Array<IFoundShell<Shell>> = []
|
||||
if (terminalPath) {
|
||||
shells.push({ shell: Shell.Terminal, path: terminalPath })
|
||||
const shells: Array<FoundShell<Shell>> = []
|
||||
if (terminalInfo) {
|
||||
shells.push({ shell: Shell.Terminal, ...terminalInfo })
|
||||
}
|
||||
|
||||
if (hyperPath) {
|
||||
shells.push({ shell: Shell.Hyper, path: hyperPath })
|
||||
if (hyperInfo) {
|
||||
shells.push({ shell: Shell.Hyper, ...hyperInfo })
|
||||
}
|
||||
|
||||
if (iTermPath) {
|
||||
shells.push({ shell: Shell.iTerm2, path: iTermPath })
|
||||
if (iTermInfo) {
|
||||
shells.push({ shell: Shell.iTerm2, ...iTermInfo })
|
||||
}
|
||||
|
||||
if (powerShellCorePath) {
|
||||
shells.push({ shell: Shell.PowerShellCore, path: powerShellCorePath })
|
||||
if (powerShellCoreInfo) {
|
||||
shells.push({ shell: Shell.PowerShellCore, ...powerShellCoreInfo })
|
||||
}
|
||||
|
||||
if (kittyPath) {
|
||||
const kittyExecutable = `${kittyPath}/Contents/MacOS/kitty`
|
||||
shells.push({ shell: Shell.Kitty, path: kittyExecutable })
|
||||
if (kittyInfo) {
|
||||
const kittyExecutable = `${kittyInfo.path}/Contents/MacOS/kitty`
|
||||
shells.push({
|
||||
shell: Shell.Kitty,
|
||||
path: kittyExecutable,
|
||||
bundleID: kittyInfo.bundleID,
|
||||
})
|
||||
}
|
||||
|
||||
if (alacrittyPath) {
|
||||
const alacrittyExecutable = `${alacrittyPath}/Contents/MacOS/alacritty`
|
||||
shells.push({ shell: Shell.Alacritty, path: alacrittyExecutable })
|
||||
if (alacrittyInfo) {
|
||||
const alacrittyExecutable = `${alacrittyInfo.path}/Contents/MacOS/alacritty`
|
||||
shells.push({
|
||||
shell: Shell.Alacritty,
|
||||
path: alacrittyExecutable,
|
||||
bundleID: alacrittyInfo.bundleID,
|
||||
})
|
||||
}
|
||||
|
||||
if (tabbyPath) {
|
||||
const tabbyExecutable = `${tabbyPath}/Contents/MacOS/Tabby`
|
||||
shells.push({ shell: Shell.Tabby, path: tabbyExecutable })
|
||||
if (tabbyInfo) {
|
||||
const tabbyExecutable = `${tabbyInfo.path}/Contents/MacOS/Tabby`
|
||||
shells.push({
|
||||
shell: Shell.Tabby,
|
||||
path: tabbyExecutable,
|
||||
bundleID: tabbyInfo.bundleID,
|
||||
})
|
||||
}
|
||||
|
||||
if (wezTermPath) {
|
||||
const wezTermExecutable = `${wezTermPath}/Contents/MacOS/wezterm`
|
||||
shells.push({ shell: Shell.WezTerm, path: wezTermExecutable })
|
||||
if (wezTermInfo) {
|
||||
const wezTermExecutable = `${wezTermInfo.path}/Contents/MacOS/wezterm`
|
||||
shells.push({
|
||||
shell: Shell.WezTerm,
|
||||
path: wezTermExecutable,
|
||||
bundleID: wezTermInfo.bundleID,
|
||||
})
|
||||
}
|
||||
|
||||
if (warpPath) {
|
||||
const warpExecutable = `${warpPath}/Contents/MacOS/stable`
|
||||
shells.push({ shell: Shell.Warp, path: warpExecutable })
|
||||
if (warpInfo) {
|
||||
const warpExecutable = `${warpInfo.path}/Contents/MacOS/stable`
|
||||
shells.push({
|
||||
shell: Shell.Warp,
|
||||
path: warpExecutable,
|
||||
bundleID: warpInfo.bundleID,
|
||||
})
|
||||
}
|
||||
|
||||
return shells
|
||||
}
|
||||
|
||||
export function launch(
|
||||
foundShell: IFoundShell<Shell>,
|
||||
foundShell: FoundShell<Shell>,
|
||||
path: string
|
||||
): ChildProcess {
|
||||
if (foundShell.shell === Shell.Kitty) {
|
||||
|
@ -158,7 +187,6 @@ export function launch(
|
|||
// the working directory, followed by the path.
|
||||
return spawn(foundShell.path, ['start', '--cwd', path])
|
||||
} else {
|
||||
const bundleID = getBundleID(foundShell.shell)
|
||||
return spawn('open', ['-b', bundleID, path])
|
||||
return spawn('open', ['-b', foundShell.bundleID, path])
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +0,0 @@
|
|||
export interface IFoundShell<T> {
|
||||
readonly shell: T
|
||||
readonly path: string
|
||||
readonly extraArgs?: string[]
|
||||
}
|
|
@ -1,8 +1,8 @@
|
|||
import { spawn, ChildProcess } from 'child_process'
|
||||
import { assertNever } from '../fatal-error'
|
||||
import { IFoundShell } from './found-shell'
|
||||
import { parseEnumValue } from '../enum'
|
||||
import { pathExists } from '../../ui/lib/path-exists'
|
||||
import { FoundShell } from './shared'
|
||||
|
||||
export enum Shell {
|
||||
Gnome = 'GNOME Terminal',
|
||||
|
@ -64,7 +64,7 @@ function getShellPath(shell: Shell): Promise<string | null> {
|
|||
}
|
||||
|
||||
export async function getAvailableShells(): Promise<
|
||||
ReadonlyArray<IFoundShell<Shell>>
|
||||
ReadonlyArray<FoundShell<Shell>>
|
||||
> {
|
||||
const [
|
||||
gnomeTerminalPath,
|
||||
|
@ -96,7 +96,7 @@ export async function getAvailableShells(): Promise<
|
|||
getShellPath(Shell.Kitty),
|
||||
])
|
||||
|
||||
const shells: Array<IFoundShell<Shell>> = []
|
||||
const shells: Array<FoundShell<Shell>> = []
|
||||
if (gnomeTerminalPath) {
|
||||
shells.push({ shell: Shell.Gnome, path: gnomeTerminalPath })
|
||||
}
|
||||
|
@ -153,7 +153,7 @@ export async function getAvailableShells(): Promise<
|
|||
}
|
||||
|
||||
export function launch(
|
||||
foundShell: IFoundShell<Shell>,
|
||||
foundShell: FoundShell<Shell>,
|
||||
path: string
|
||||
): ChildProcess {
|
||||
const shell = foundShell.shell
|
||||
|
|
|
@ -3,13 +3,22 @@ import { ChildProcess } from 'child_process'
|
|||
import * as Darwin from './darwin'
|
||||
import * as Win32 from './win32'
|
||||
import * as Linux from './linux'
|
||||
import { IFoundShell } from './found-shell'
|
||||
import { ShellError } from './error'
|
||||
import { pathExists } from '../../ui/lib/path-exists'
|
||||
|
||||
export type Shell = Darwin.Shell | Win32.Shell | Linux.Shell
|
||||
|
||||
export type FoundShell = IFoundShell<Shell>
|
||||
export type FoundShell<T extends Shell> = {
|
||||
readonly shell: T
|
||||
readonly path: string
|
||||
readonly extraArgs?: string[]
|
||||
} & (T extends Darwin.Shell
|
||||
? {
|
||||
readonly bundleID: string
|
||||
}
|
||||
: {})
|
||||
|
||||
type AnyFoundShell = FoundShell<Shell>
|
||||
|
||||
/** The default shell. */
|
||||
export const Default = (function () {
|
||||
|
@ -22,7 +31,7 @@ export const Default = (function () {
|
|||
}
|
||||
})()
|
||||
|
||||
let shellCache: ReadonlyArray<FoundShell> | null = null
|
||||
let shellCache: ReadonlyArray<AnyFoundShell> | null = null
|
||||
|
||||
/** Parse the label into the specified shell type. */
|
||||
export function parse(label: string): Shell {
|
||||
|
@ -40,7 +49,9 @@ export function parse(label: string): Shell {
|
|||
}
|
||||
|
||||
/** Get the shells available for the user. */
|
||||
export async function getAvailableShells(): Promise<ReadonlyArray<FoundShell>> {
|
||||
export async function getAvailableShells(): Promise<
|
||||
ReadonlyArray<AnyFoundShell>
|
||||
> {
|
||||
if (shellCache) {
|
||||
return shellCache
|
||||
}
|
||||
|
@ -62,7 +73,7 @@ export async function getAvailableShells(): Promise<ReadonlyArray<FoundShell>> {
|
|||
}
|
||||
|
||||
/** Find the given shell or the default if the given shell can't be found. */
|
||||
export async function findShellOrDefault(shell: Shell): Promise<FoundShell> {
|
||||
export async function findShellOrDefault(shell: Shell): Promise<AnyFoundShell> {
|
||||
const available = await getAvailableShells()
|
||||
const found = available.find(s => s.shell === shell)
|
||||
if (found) {
|
||||
|
@ -74,7 +85,7 @@ export async function findShellOrDefault(shell: Shell): Promise<FoundShell> {
|
|||
|
||||
/** Launch the given shell at the path. */
|
||||
export async function launchShell(
|
||||
shell: FoundShell,
|
||||
shell: AnyFoundShell,
|
||||
path: string,
|
||||
onError: (error: Error) => void
|
||||
): Promise<void> {
|
||||
|
@ -92,11 +103,11 @@ export async function launchShell(
|
|||
let cp: ChildProcess | null = null
|
||||
|
||||
if (__DARWIN__) {
|
||||
cp = Darwin.launch(shell as IFoundShell<Darwin.Shell>, path)
|
||||
cp = Darwin.launch(shell as FoundShell<Darwin.Shell>, path)
|
||||
} else if (__WIN32__) {
|
||||
cp = Win32.launch(shell as IFoundShell<Win32.Shell>, path)
|
||||
cp = Win32.launch(shell as FoundShell<Win32.Shell>, path)
|
||||
} else if (__LINUX__) {
|
||||
cp = Linux.launch(shell as IFoundShell<Linux.Shell>, path)
|
||||
cp = Linux.launch(shell as FoundShell<Linux.Shell>, path)
|
||||
}
|
||||
|
||||
if (cp != null) {
|
||||
|
|
|
@ -2,11 +2,11 @@ import { spawn, ChildProcess } from 'child_process'
|
|||
import * as Path from 'path'
|
||||
import { enumerateValues, HKEY, RegistryValueType } from 'registry-js'
|
||||
import { assertNever } from '../fatal-error'
|
||||
import { IFoundShell } from './found-shell'
|
||||
import { enableWSLDetection } from '../feature-flag'
|
||||
import { findGitOnPath } from '../is-git-on-path'
|
||||
import { parseEnumValue } from '../enum'
|
||||
import { pathExists } from '../../ui/lib/path-exists'
|
||||
import { FoundShell } from './shared'
|
||||
|
||||
export enum Shell {
|
||||
Cmd = 'Command Prompt',
|
||||
|
@ -28,12 +28,12 @@ export function parse(label: string): Shell {
|
|||
}
|
||||
|
||||
export async function getAvailableShells(): Promise<
|
||||
ReadonlyArray<IFoundShell<Shell>>
|
||||
ReadonlyArray<FoundShell<Shell>>
|
||||
> {
|
||||
const gitPath = await findGitOnPath()
|
||||
const rootDir = process.env.WINDIR || 'C:\\Windows'
|
||||
const dosKeyExePath = `"${rootDir}\\system32\\doskey.exe git=^"${gitPath}^" $*"`
|
||||
const shells: IFoundShell<Shell>[] = [
|
||||
const shells: FoundShell<Shell>[] = [
|
||||
{
|
||||
shell: Shell.Cmd,
|
||||
path: process.env.comspec || 'C:\\Windows\\System32\\cmd.exe',
|
||||
|
@ -392,7 +392,7 @@ async function findFluentTerminal(): Promise<string | null> {
|
|||
}
|
||||
|
||||
export function launch(
|
||||
foundShell: IFoundShell<Shell>,
|
||||
foundShell: FoundShell<Shell>,
|
||||
path: string
|
||||
): ChildProcess {
|
||||
const shell = foundShell.shell
|
||||
|
|
|
@ -172,13 +172,13 @@ use Hyper as a reference to explain the rest of the process.
|
|||
|
||||
### Step 1: Find the shell application
|
||||
|
||||
The `getBundleID()` function is used to map a shell enum to it's bundle ID
|
||||
that is defined in it's manifest. You should add a new entry here for your
|
||||
shell.
|
||||
The `getBundleIDs()` function is used to map a shell enum to the possible bundle IDs
|
||||
that are defined in its manifest. You should add a new entry here for your
|
||||
shell. An array is returned to handle the case where a shell updates its bundle ID.
|
||||
|
||||
```ts
|
||||
case Shell.Hyper:
|
||||
return 'co.zeit.hyper'
|
||||
return ['co.zeit.hyper']
|
||||
```
|
||||
|
||||
After that, follow the existing patterns in `getAvailableShells()` and add a
|
||||
|
@ -186,44 +186,33 @@ new entry to lookup the install path for your shell.
|
|||
|
||||
```ts
|
||||
export async function getAvailableShells(): Promise<
|
||||
ReadonlyArray<IFoundShell<Shell>>
|
||||
ReadonlyArray<FoundShell<Shell>>
|
||||
> {
|
||||
const [
|
||||
terminalPath,
|
||||
hyperPath,
|
||||
iTermPath,
|
||||
powerShellCorePath,
|
||||
kittyPath,
|
||||
terminalInfo,
|
||||
hyperInfo,
|
||||
iTermInfo,
|
||||
powerShellCoreInfo,
|
||||
kittyInfo,
|
||||
] = await Promise.all([
|
||||
getShellPath(Shell.Terminal),
|
||||
getShellPath(Shell.Hyper),
|
||||
getShellPath(Shell.iTerm2),
|
||||
getShellPath(Shell.PowerShellCore),
|
||||
getShellPath(Shell.Kitty),
|
||||
getShellInfo(Shell.Terminal),
|
||||
getShellInfo(Shell.Hyper),
|
||||
getShellInfo(Shell.iTerm2),
|
||||
getShellInfo(Shell.PowerShellCore),
|
||||
getShellInfo(Shell.Kitty),
|
||||
])
|
||||
|
||||
// other code
|
||||
|
||||
if (hyperPath) {
|
||||
shells.push({ shell: Shell.Hyper, path: hyperPath })
|
||||
if (hyperInfo) {
|
||||
shells.push({ shell: Shell.Hyper, ...hyperInfo })
|
||||
}
|
||||
|
||||
// other code
|
||||
}
|
||||
```
|
||||
|
||||
### Step 2: Parse the shell
|
||||
|
||||
The `parse()` function is used to parse shell names. You should add a new entry here for your
|
||||
shell.
|
||||
|
||||
```ts
|
||||
if (label === Shell.Hyper) {
|
||||
return Shell.Hyper
|
||||
}
|
||||
```
|
||||
|
||||
### Step 3: Launch the shell
|
||||
### Step 2: Launch the shell
|
||||
|
||||
The launch step will use the `open` command in macOS to launch a given bundle
|
||||
at the path requested by the user. You may not need to make changes here,
|
||||
|
@ -231,12 +220,10 @@ unless your shell behaviour differs significantly from this.
|
|||
|
||||
```ts
|
||||
export function launch(
|
||||
foundShell: IFoundShell<Shell>,
|
||||
foundShell: FoundShell<Shell>,
|
||||
path: string
|
||||
): ChildProcess {
|
||||
const bundleID = getBundleID(foundShell.shell)
|
||||
const commandArgs = ['-b', bundleID, path]
|
||||
return spawn('open', commandArgs)
|
||||
return spawn('open', ['-b', foundShell.bundleID, path])
|
||||
}
|
||||
```
|
||||
|
||||
|
@ -290,7 +277,7 @@ new entry to lookup the install path for your shell.
|
|||
|
||||
```ts
|
||||
export async function getAvailableShells(): Promise<
|
||||
ReadonlyArray<IFoundShell<Shell>>
|
||||
ReadonlyArray<FoundShell<Shell>>
|
||||
> {
|
||||
const [
|
||||
gnomeTerminalPath,
|
||||
|
@ -328,7 +315,7 @@ the correct arguments are passed to the command line interface:
|
|||
|
||||
```ts
|
||||
export function launch(
|
||||
foundShell: IFoundShell<Shell>,
|
||||
foundShell: FoundShell<Shell>,
|
||||
path: string
|
||||
): ChildProcess {
|
||||
const shell = foundShell.shell
|
||||
|
|
Loading…
Reference in New Issue
Block a user