From fee91cbff51248d85d76cfc2bea4a2252ac9da82 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 31 Jan 2025 13:07:11 -0800 Subject: [PATCH] Share exec/spawn code between shells --- extensions/terminal-suggest/src/shell/bash.ts | 29 ++------------ .../terminal-suggest/src/shell/common.ts | 40 +++++++++++++++++++ extensions/terminal-suggest/src/shell/fish.ts | 28 ++----------- extensions/terminal-suggest/src/shell/pwsh.ts | 18 +++------ extensions/terminal-suggest/src/shell/zsh.ts | 29 ++------------ .../src/terminalSuggestMain.ts | 2 +- 6 files changed, 58 insertions(+), 88 deletions(-) create mode 100644 extensions/terminal-suggest/src/shell/common.ts diff --git a/extensions/terminal-suggest/src/shell/bash.ts b/extensions/terminal-suggest/src/shell/bash.ts index 70a8110eba3..b440af3c880 100644 --- a/extensions/terminal-suggest/src/shell/bash.ts +++ b/extensions/terminal-suggest/src/shell/bash.ts @@ -5,7 +5,8 @@ import * as vscode from 'vscode'; import type { ICompletionResource } from '../types'; -import { exec, spawn, type ExecOptionsWithStringEncoding } from 'node:child_process'; +import { type ExecOptionsWithStringEncoding } from 'node:child_process'; +import { execHelper, spawnHelper } from './common'; export async function getBashGlobals(options: ExecOptionsWithStringEncoding, existingCommands?: Set): Promise<(string | ICompletionResource)[]> { return [ @@ -15,15 +16,7 @@ export async function getBashGlobals(options: ExecOptionsWithStringEncoding, exi } async function getBuiltins(options: ExecOptionsWithStringEncoding, existingCommands?: Set): Promise { - const compgenOutput = await new Promise((resolve, reject) => { - exec('compgen -b', options, (error, stdout) => { - if (error) { - reject(error); - } else { - resolve(stdout); - } - }); - }); + const compgenOutput = await execHelper('compgen -b', options); const filter = (cmd: string) => cmd && !existingCommands?.has(cmd); return compgenOutput.split('\n').filter(filter); } @@ -33,21 +26,7 @@ async function getAliases(options: ExecOptionsWithStringEncoding): Promise((resolve, reject) => { - const child = spawn('bash', ['-ic', 'alias'], options); - let stdout = ''; - child.stdout.on('data', (data) => { - stdout += data; - }); - child.on('close', (code) => { - if (code !== 0) { - reject(new Error(`bash process exited with code ${code}`)); - } else { - resolve(stdout); - } - }); - }); - + const aliasOutput = await spawnHelper('bash', ['-ic', 'alias'], options); const result: ICompletionResource[] = []; for (const line of aliasOutput.split('\n')) { const match = line.match(/^alias (?[a-zA-Z0-9\.:-]+)='(?.+)'$/); diff --git a/extensions/terminal-suggest/src/shell/common.ts b/extensions/terminal-suggest/src/shell/common.ts new file mode 100644 index 00000000000..5bb5c349f2b --- /dev/null +++ b/extensions/terminal-suggest/src/shell/common.ts @@ -0,0 +1,40 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { exec, spawn, type ExecOptionsWithStringEncoding } from 'node:child_process'; + +export async function spawnHelper(command: string, args: string[], options: ExecOptionsWithStringEncoding): Promise { + // This must be run with interactive, otherwise there's a good chance aliases won't + // be set up. Note that this could differ from the actual aliases as it's a new bash + // session, for the same reason this would not include aliases that are created + // by simply running `alias ...` in the terminal. + return new Promise((resolve, reject) => { + const child = spawn(command, args, options); + let stdout = ''; + child.stdout.on('data', (data) => { + stdout += data; + }); + child.on('close', (code) => { + if (code !== 0) { + reject(new Error(`bash process exited with code ${code}`)); + } else { + resolve(stdout); + } + }); + }); +} + +export async function execHelper(commandLine: string, options: ExecOptionsWithStringEncoding): Promise { + return new Promise((resolve, reject) => { + exec(commandLine, options, (error, stdout) => { + if (error) { + reject(error); + } else { + resolve(stdout); + } + }); + }); +} + diff --git a/extensions/terminal-suggest/src/shell/fish.ts b/extensions/terminal-suggest/src/shell/fish.ts index f63ec5adf4d..eaa472d2b27 100644 --- a/extensions/terminal-suggest/src/shell/fish.ts +++ b/extensions/terminal-suggest/src/shell/fish.ts @@ -5,7 +5,8 @@ import * as vscode from 'vscode'; import type { ICompletionResource } from '../types'; -import { exec, spawn, type ExecOptionsWithStringEncoding } from 'node:child_process'; +import { execHelper, spawnHelper } from './common'; +import { type ExecOptionsWithStringEncoding } from 'node:child_process'; export async function getFishGlobals(options: ExecOptionsWithStringEncoding, existingCommands?: Set): Promise<(string | ICompletionResource)[]> { return [ @@ -15,15 +16,7 @@ export async function getFishGlobals(options: ExecOptionsWithStringEncoding, exi } async function getBuiltins(options: ExecOptionsWithStringEncoding, existingCommands?: Set): Promise { - const compgenOutput = await new Promise((resolve, reject) => { - exec('functions -n', options, (error, stdout) => { - if (error) { - reject(error); - } else { - resolve(stdout); - } - }); - }); + const compgenOutput = await execHelper('functions -n', options); const filter = (cmd: string) => cmd && !existingCommands?.has(cmd); return compgenOutput.split(', ').filter(filter); } @@ -33,20 +26,7 @@ async function getAliases(options: ExecOptionsWithStringEncoding): Promise((resolve, reject) => { - const child = spawn('fish', ['-ic', 'alias'], options); - let stdout = ''; - child.stdout.on('data', (data) => { - stdout += data; - }); - child.on('close', (code) => { - if (code !== 0) { - reject(new Error(`bash process exited with code ${code}`)); - } else { - resolve(stdout); - } - }); - }); + const aliasOutput = await spawnHelper('fish', ['-ic', 'alias'], options); const result: ICompletionResource[] = []; for (const line of aliasOutput.split('\n')) { diff --git a/extensions/terminal-suggest/src/shell/pwsh.ts b/extensions/terminal-suggest/src/shell/pwsh.ts index 4daa0926b86..47b8e7fe7f6 100644 --- a/extensions/terminal-suggest/src/shell/pwsh.ts +++ b/extensions/terminal-suggest/src/shell/pwsh.ts @@ -5,7 +5,8 @@ import * as vscode from 'vscode'; import type { ICompletionResource } from '../types'; -import { exec, type ExecOptionsWithStringEncoding } from 'node:child_process'; +import { type ExecOptionsWithStringEncoding } from 'node:child_process'; +import { execHelper } from './common'; export async function getPwshGlobals(options: ExecOptionsWithStringEncoding, existingCommands?: Set): Promise<(string | ICompletionResource)[]> { return [ @@ -18,18 +19,9 @@ const enum PwshCommandType { } async function getCommands(options: ExecOptionsWithStringEncoding, existingCommands?: Set): Promise { - - const output = await new Promise((resolve, reject) => { - exec('Get-Command -All | Select-Object Name, CommandType, DisplayName, Definition | ConvertTo-Json', { - ...options, - maxBuffer: 1024 * 1024 * 100 // This is a lot of content, increase buffer size - }, (error, stdout) => { - if (error) { - reject(error); - return; - } - resolve(stdout); - }); + const output = await execHelper('Get-Command -All | Select-Object Name, CommandType, DisplayName, Definition | ConvertTo-Json', { + ...options, + maxBuffer: 1024 * 1024 * 100 // This is a lot of content, increase buffer size }); let json: any; try { diff --git a/extensions/terminal-suggest/src/shell/zsh.ts b/extensions/terminal-suggest/src/shell/zsh.ts index d197dba9147..ce00edb900b 100644 --- a/extensions/terminal-suggest/src/shell/zsh.ts +++ b/extensions/terminal-suggest/src/shell/zsh.ts @@ -5,7 +5,8 @@ import * as vscode from 'vscode'; import type { ICompletionResource } from '../types'; -import { exec, spawn, type ExecOptionsWithStringEncoding } from 'node:child_process'; +import { execHelper, spawnHelper } from './common'; +import { type ExecOptionsWithStringEncoding } from 'node:child_process'; export async function getZshGlobals(options: ExecOptionsWithStringEncoding, existingCommands?: Set): Promise<(string | ICompletionResource)[]> { return [ @@ -15,15 +16,7 @@ export async function getZshGlobals(options: ExecOptionsWithStringEncoding, exis } async function getBuiltins(options: ExecOptionsWithStringEncoding, existingCommands?: Set): Promise { - const compgenOutput = await new Promise((resolve, reject) => { - exec('printf "%s\\n" ${(k)builtins}', options, (error, stdout) => { - if (error) { - reject(error); - } else { - resolve(stdout); - } - }); - }); + const compgenOutput = await execHelper('printf "%s\\n" ${(k)builtins}', options); const filter = (cmd: string) => cmd && !existingCommands?.has(cmd); return compgenOutput.split('\n').filter(filter); } @@ -33,21 +26,7 @@ async function getAliases(options: ExecOptionsWithStringEncoding): Promise((resolve, reject) => { - const child = spawn('zsh', ['-ic', 'alias'], options); - let stdout = ''; - child.stdout.on('data', (data) => { - stdout += data; - }); - child.on('close', (code) => { - if (code !== 0) { - reject(new Error(`zsh process exited with code ${code}`)); - } else { - resolve(stdout); - } - }); - }); - + const aliasOutput = await spawnHelper('zsh', ['-ic', 'alias'], options); const result: ICompletionResource[] = []; for (const line of aliasOutput.split('\n')) { const match = line.match(/^(?[a-zA-Z0-9\.:-]+)=(?:'(?.+)'|(?.+))$/); diff --git a/extensions/terminal-suggest/src/terminalSuggestMain.ts b/extensions/terminal-suggest/src/terminalSuggestMain.ts index 6ca278bcfb5..b348e7a73fc 100644 --- a/extensions/terminal-suggest/src/terminalSuggestMain.ts +++ b/extensions/terminal-suggest/src/terminalSuggestMain.ts @@ -5,7 +5,7 @@ import * as vscode from 'vscode'; import * as fs from 'fs/promises'; import * as path from 'path'; -import { exec, ExecOptionsWithStringEncoding } from 'child_process'; +import { ExecOptionsWithStringEncoding } from 'child_process'; import { upstreamSpecs } from './constants'; import codeCompletionSpec from './completions/code'; import cdSpec from './completions/cd';