Share exec/spawn code between shells

This commit is contained in:
Daniel Imms
2025-01-31 13:07:11 -08:00
parent 735fde9116
commit fee91cbff5
6 changed files with 58 additions and 88 deletions
+4 -25
View File
@@ -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<string>): Promise<(string | ICompletionResource)[]> {
return [
@@ -15,15 +16,7 @@ export async function getBashGlobals(options: ExecOptionsWithStringEncoding, exi
}
async function getBuiltins(options: ExecOptionsWithStringEncoding, existingCommands?: Set<string>): Promise<string[]> {
const compgenOutput = await new Promise<string>((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<IComp
// 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.
const aliasOutput = await new Promise<string>((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 (?<alias>[a-zA-Z0-9\.:-]+)='(?<resolved>.+)'$/);
@@ -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<string> {
// 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<string>((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<string> {
return new Promise<string>((resolve, reject) => {
exec(commandLine, options, (error, stdout) => {
if (error) {
reject(error);
} else {
resolve(stdout);
}
});
});
}
+4 -24
View File
@@ -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<string>): Promise<(string | ICompletionResource)[]> {
return [
@@ -15,15 +16,7 @@ export async function getFishGlobals(options: ExecOptionsWithStringEncoding, exi
}
async function getBuiltins(options: ExecOptionsWithStringEncoding, existingCommands?: Set<string>): Promise<string[]> {
const compgenOutput = await new Promise<string>((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<IComp
// 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.
const aliasOutput = await new Promise<string>((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')) {
+5 -13
View File
@@ -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<string>): Promise<(string | ICompletionResource)[]> {
return [
@@ -18,18 +19,9 @@ const enum PwshCommandType {
}
async function getCommands(options: ExecOptionsWithStringEncoding, existingCommands?: Set<string>): Promise<ICompletionResource[]> {
const output = await new Promise<string>((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 {
+4 -25
View File
@@ -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<string>): Promise<(string | ICompletionResource)[]> {
return [
@@ -15,15 +16,7 @@ export async function getZshGlobals(options: ExecOptionsWithStringEncoding, exis
}
async function getBuiltins(options: ExecOptionsWithStringEncoding, existingCommands?: Set<string>): Promise<string[]> {
const compgenOutput = await new Promise<string>((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<IComp
// 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.
const aliasOutput = await new Promise<string>((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(/^(?<alias>[a-zA-Z0-9\.:-]+)=(?:'(?<resolved>.+)'|(?<resolved>.+))$/);
@@ -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';