Skip to content

Commit ad7397e

Browse files
Merge branch 'main' into devalenc/mjbvz-owner-fix
2 parents e6387ea + c2eb8ab commit ad7397e

File tree

13 files changed

+156
-58
lines changed

13 files changed

+156
-58
lines changed

extensions/terminal-suggest/src/fig/api-binding-wrappers/executeCommandWrappers.ts

Lines changed: 0 additions & 9 deletions
This file was deleted.

extensions/terminal-suggest/src/fig/autocomplete/generators/customSuggestionsGenerator.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@
33
* Licensed under the MIT License. See License.txt in the project root for license information.
44
*--------------------------------------------------------------------------------------------*/
55

6-
import { executeCommand } from '../../api-binding-wrappers/executeCommandWrappers';
6+
7+
import { IFigExecuteExternals } from '../../execute';
78
import {
89
runCachedGenerator,
910
GeneratorContext,
@@ -13,6 +14,7 @@ import {
1314
export async function getCustomSuggestions(
1415
generator: Fig.Generator,
1516
context: GeneratorContext,
17+
executableExternals: IFigExecuteExternals
1618
): Promise<Fig.Suggestion[] | undefined> {
1719
if (!generator.custom) {
1820
return [];
@@ -37,7 +39,7 @@ export async function getCustomSuggestions(
3739
generator,
3840
context,
3941
() =>
40-
generator.custom!(tokenArray, executeCommand, {
42+
generator.custom!(tokenArray, executableExternals.executeCommand, {
4143
currentWorkingDirectory,
4244
currentProcess,
4345
sshPrefix: '',

extensions/terminal-suggest/src/fig/autocomplete/generators/scriptSuggestionsGenerator.ts

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@
33
* Licensed under the MIT License. See License.txt in the project root for license information.
44
*--------------------------------------------------------------------------------------------*/
55

6-
import { executeCommandTimeout } from '../../api-binding-wrappers/executeCommand';
6+
7+
import { IFigExecuteExternals } from '../../execute';
78
import {
89
GeneratorContext,
910
haveContextForGenerator,
@@ -14,6 +15,7 @@ export async function getScriptSuggestions(
1415
generator: Fig.Generator,
1516
context: GeneratorContext,
1617
defaultTimeout: number = 5000,
18+
executeExternals: IFigExecuteExternals
1719
): Promise<Fig.Suggestion[] | undefined> {
1820
const { script, postProcess, splitOn } = generator;
1921
if (!script) {
@@ -62,10 +64,12 @@ export async function getScriptSuggestions(
6264
const { stdout } = await runCachedGenerator(
6365
generator,
6466
context,
65-
() => executeCommandTimeout(executeCommandInput, timeout),
67+
() => {
68+
return executeExternals.executeCommandTimeout(executeCommandInput, timeout);
69+
},
6670
generator.cache?.cacheKey ?? JSON.stringify(executeCommandInput),
6771
);
68-
72+
//
6973
let result: Array<Fig.Suggestion | string | null> | undefined = [];
7074

7175
// If we have a splitOn function

extensions/terminal-suggest/src/fig/autocomplete/state/generators.ts

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import { GeneratorState, GeneratorContext } from '../generators/helpers';
1010
import { sleep } from '../../shared/utils';
1111
import type { ArgumentParserResult } from '../../autocomplete-parser/parseArguments';
1212
import { getCustomSuggestions } from '../generators/customSuggestionsGenerator';
13+
import { IFigExecuteExternals } from '../../execute';
1314

1415
export const shellContextSelector = ({
1516
figState,
@@ -36,7 +37,10 @@ const getGeneratorContext = (state: AutocompleteState): GeneratorContext => {
3637
export const createGeneratorState = (
3738
// setNamed: NamedSetState<AutocompleteState>,
3839
state: AutocompleteState,
39-
): { triggerGenerators: ((result: ArgumentParserResult) => GeneratorState[]) } => {
40+
executeExternals?: IFigExecuteExternals
41+
): {
42+
triggerGenerators: (result: ArgumentParserResult, executeExternals: IFigExecuteExternals) => GeneratorState[];
43+
} => {
4044
// function updateGenerator(
4145
// generatorState: GeneratorState,
4246
// getUpdate: () => Partial<GeneratorState>,
@@ -76,8 +80,7 @@ export const createGeneratorState = (
7680
// }
7781
// return generatorState;
7882
// }
79-
80-
const triggerGenerator = (currentState: GeneratorState) => {
83+
const triggerGenerator = (currentState: GeneratorState, executeExternals: IFigExecuteExternals) => {
8184
console.info('Triggering generator', { currentState });
8285
const { generator, context } = currentState;
8386
let request: Promise<Fig.Suggestion[] | undefined>;
@@ -91,11 +94,12 @@ export const createGeneratorState = (
9194
request = getScriptSuggestions(
9295
generator,
9396
context,
94-
// getSetting<number>(SETTINGS.SCRIPT_TIMEOUT, 5000),
97+
undefined, // getSetting<number>(SETTINGS.SCRIPT_TIMEOUT, 5000),
98+
executeExternals
9599
);
96100
}
97101
else {
98-
request = getCustomSuggestions(generator, context);
102+
request = getCustomSuggestions(generator, context, executeExternals);
99103
// filepaths/folders templates are now a sugar for two custom generators, we need to filter
100104
// the suggestion created by those two custom generators
101105
// if (generator.filterTemplateSuggestions) {
@@ -118,6 +122,7 @@ export const createGeneratorState = (
118122

119123
const triggerGenerators = (
120124
parserResult: ArgumentParserResult,
125+
executeExternals: IFigExecuteExternals,
121126
): GeneratorState[] => {
122127
const {
123128
parserResult: { currentArg: previousArg, searchTerm: previousSearchTerm },
@@ -182,7 +187,7 @@ export const createGeneratorState = (
182187
const result = previousGeneratorState?.result || [];
183188
const generatorState = { generator, context, result, loading: true };
184189

185-
const getTriggeredState = () => triggerGenerator(generatorState);
190+
const getTriggeredState = () => triggerGenerator(generatorState, executeExternals);
186191
if (currentArg?.debounce) {
187192
sleep(
188193
typeof currentArg.debounce === 'number' && currentArg.debounce > 0
Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,9 @@
33
* Licensed under the MIT License. See License.txt in the project root for license information.
44
*--------------------------------------------------------------------------------------------*/
55

6-
import { osIsWindows } from '../../helpers/os';
7-
import { spawnHelper2 } from '../../shell/common';
8-
import { withTimeout } from '../shared/utils';
6+
import { osIsWindows } from '../helpers/os';
7+
import { spawnHelper2 } from '../shell/common';
8+
import { withTimeout } from './shared/utils';
99

1010
export const cleanOutput = (output: string) =>
1111
output
@@ -54,3 +54,15 @@ export const executeCommandTimeout = async (
5454
throw err;
5555
}
5656
};
57+
58+
59+
export const executeCommand: Fig.ExecuteCommandFunction = (args) =>
60+
executeCommandTimeout(args);
61+
62+
export interface IFigExecuteExternals {
63+
executeCommand: Fig.ExecuteCommandFunction;
64+
executeCommandTimeout: (
65+
input: Fig.ExecuteCommandInput,
66+
timeout: number,
67+
) => Promise<Fig.ExecuteCommandOutput>;
68+
}

extensions/terminal-suggest/src/fig/figInterface.ts

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import type { ICompletionResource } from '../types';
1616
import { osIsWindows } from '../helpers/os';
1717
import { removeAnyFileExtension } from '../helpers/file';
1818
import type { EnvironmentVariable } from './api-bindings/types';
19+
import { IFigExecuteExternals } from './execute';
1920

2021
export interface IFigSpecSuggestionsResult {
2122
filesRequested: boolean;
@@ -34,7 +35,8 @@ export async function getFigSuggestions(
3435
env: Record<string, string>,
3536
name: string,
3637
precedingText: string,
37-
token?: vscode.CancellationToken
38+
executeExternals: IFigExecuteExternals,
39+
token?: vscode.CancellationToken,
3840
): Promise<IFigSpecSuggestionsResult> {
3941
const result: IFigSpecSuggestionsResult = {
4042
filesRequested: false,
@@ -84,7 +86,7 @@ export async function getFigSuggestions(
8486
continue;
8587
}
8688

87-
const completionItemResult = await getFigSpecSuggestions(spec, terminalContext, prefix, shellIntegrationCwd, env, name, token);
89+
const completionItemResult = await getFigSpecSuggestions(spec, terminalContext, prefix, shellIntegrationCwd, env, name, executeExternals, token);
8890
result.hasCurrentArg ||= !!completionItemResult?.hasCurrentArg;
8991
if (completionItemResult) {
9092
result.filesRequested ||= completionItemResult.filesRequested;
@@ -105,7 +107,8 @@ async function getFigSpecSuggestions(
105107
shellIntegrationCwd: vscode.Uri | undefined,
106108
env: Record<string, string>,
107109
name: string,
108-
token?: vscode.CancellationToken
110+
executeExternals: IFigExecuteExternals,
111+
token?: vscode.CancellationToken,
109112
): Promise<IFigSpecSuggestionsResult | undefined> {
110113
let filesRequested = false;
111114
let foldersRequested = false;
@@ -125,7 +128,7 @@ async function getFigSpecSuggestions(
125128

126129
const items: vscode.TerminalCompletionItem[] = [];
127130
// TODO: Pass in and respect cancellation token
128-
const completionItemResult = await collectCompletionItemResult(command, parsedArguments, prefix, terminalContext, shellIntegrationCwd, env, items);
131+
const completionItemResult = await collectCompletionItemResult(command, parsedArguments, prefix, terminalContext, shellIntegrationCwd, env, items, executeExternals);
129132
if (token?.isCancellationRequested) {
130133
return undefined;
131134
}
@@ -152,7 +155,8 @@ export async function collectCompletionItemResult(
152155
terminalContext: { commandLine: string; cursorPosition: number },
153156
shellIntegrationCwd: vscode.Uri | undefined,
154157
env: Record<string, string>,
155-
items: vscode.TerminalCompletionItem[]
158+
items: vscode.TerminalCompletionItem[],
159+
executeExternals: IFigExecuteExternals
156160
): Promise<{ filesRequested: boolean; foldersRequested: boolean } | undefined> {
157161
let filesRequested = false;
158162
let foldersRequested = false;
@@ -191,8 +195,8 @@ export async function collectCompletionItemResult(
191195
fuzzySearchEnabled: false,
192196
userFuzzySearchEnabled: false,
193197
};
194-
const s = createGeneratorState(state);
195-
const generatorResults = s.triggerGenerators(parsedArguments);
198+
const s = createGeneratorState(state, executeExternals);
199+
const generatorResults = s.triggerGenerators(parsedArguments, executeExternals);
196200
for (const generatorResult of generatorResults) {
197201
for (const item of (await generatorResult?.request) ?? []) {
198202
if (!item.name) {

extensions/terminal-suggest/src/terminalSuggestMain.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import { getTokenType, TokenType } from './tokens';
2222
import type { ICompletionResource } from './types';
2323
import { createCompletionItem } from './helpers/completionItem';
2424
import { getFigSuggestions } from './fig/figInterface';
25+
import { executeCommand, executeCommandTimeout, IFigExecuteExternals } from './fig/execute';
2526

2627
// TODO: remove once API is finalized
2728
export const enum TerminalShellType {
@@ -206,7 +207,8 @@ export async function getCompletionItemsFromSpecs(
206207
shellIntegrationCwd: vscode.Uri | undefined,
207208
env: Record<string, string>,
208209
name: string,
209-
token?: vscode.CancellationToken
210+
token?: vscode.CancellationToken,
211+
executeExternals?: IFigExecuteExternals,
210212
): Promise<{ items: vscode.TerminalCompletionItem[]; filesRequested: boolean; foldersRequested: boolean; cwd?: vscode.Uri }> {
211213
const items: vscode.TerminalCompletionItem[] = [];
212214
let filesRequested = false;
@@ -223,7 +225,7 @@ export async function getCompletionItemsFromSpecs(
223225
}
224226
}
225227

226-
const result = await getFigSuggestions(specs, terminalContext, availableCommands, prefix, tokenType, shellIntegrationCwd, env, name, precedingText, token);
228+
const result = await getFigSuggestions(specs, terminalContext, availableCommands, prefix, tokenType, shellIntegrationCwd, env, name, precedingText, executeExternals ?? { executeCommand, executeCommandTimeout }, token);
227229
if (result) {
228230
hasCurrentArg ||= result.hasCurrentArg;
229231
filesRequested ||= result.filesRequested;

extensions/terminal-suggest/src/test/fig.test.ts

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,5 +127,62 @@ export const figGenericTestSuites: ISuiteSpec[] = [
127127
{ input: 'foo --bar b|', expectedCompletions: ['a', 'b', 'c'] },
128128
{ input: 'foo --bar c|', expectedCompletions: ['a', 'b', 'c'] },
129129
]
130+
},
131+
{
132+
name: 'Fig script generator',
133+
completionSpecs: [
134+
{
135+
name: 'foo',
136+
description: 'Foo',
137+
args: {
138+
name: 'bar',
139+
generators: [
140+
{
141+
script: () => ['echo abcd'],
142+
postProcess: (out) => out.split('').map(item => {
143+
return { name: item };
144+
}).filter(i => !!i)
145+
}
146+
]
147+
}
148+
}
149+
],
150+
availableCommands: 'foo',
151+
testSpecs: [
152+
{ input: 'foo |', expectedCompletions: ['e', 'c', 'h', 'o', ' ', 'a', 'b', 'c', 'd'] },
153+
{ input: 'foo a|', expectedCompletions: ['e', 'c', 'h', 'o', ' ', 'a', 'b', 'c', 'd'] },
154+
{ input: 'foo b|', expectedCompletions: ['e', 'c', 'h', 'o', ' ', 'a', 'b', 'c', 'd'] },
155+
{ input: 'foo c|', expectedCompletions: ['e', 'c', 'h', 'o', ' ', 'a', 'b', 'c', 'd'] },
156+
]
157+
},
158+
{
159+
name: 'Fig custom generator',
160+
completionSpecs: [
161+
{
162+
name: 'foo',
163+
description: 'Foo',
164+
args: {
165+
name: 'bar',
166+
generators: [
167+
{
168+
custom: async (tokens: string[], executeCommand: Fig.ExecuteCommandFunction, generatorContext: Fig.GeneratorContext) => {
169+
if (tokens.length) {
170+
return tokens.map(token => ({ name: token }));
171+
}
172+
executeCommand({ command: 'echo', args: ['a\tb\nc\td'] });
173+
}
174+
}
175+
]
176+
}
177+
}
178+
],
179+
availableCommands: 'foo',
180+
testSpecs: [
181+
{ input: 'foo |', expectedCompletions: ['foo'] },
182+
{ input: 'foo a|', expectedCompletions: ['a', 'foo'] },
183+
{ input: 'foo b|', expectedCompletions: ['b', 'foo'] },
184+
{ input: 'foo c|', expectedCompletions: ['c', 'foo'] },
185+
]
130186
}
131187
];
188+

extensions/terminal-suggest/src/test/terminalSuggestMain.test.ts

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import { gitTestSuiteSpec } from './completions/upstream/git.test';
2222
import { osIsWindows } from '../helpers/os';
2323
import codeCompletionSpec from '../completions/code';
2424
import { figGenericTestSuites } from './fig.test';
25+
import { IFigExecuteExternals } from '../fig/execute';
2526

2627
const testSpecs2: ISuiteSpec[] = [
2728
{
@@ -101,7 +102,9 @@ suite('Terminal Suggest', () => {
101102
getTokenType(terminalContext, undefined),
102103
testPaths.cwd,
103104
{},
104-
'testName'
105+
'testName',
106+
undefined,
107+
new MockFigExecuteExternals()
105108
);
106109
deepStrictEqual(result.items.map(i => i.label).sort(), (testSpec.expectedCompletions ?? []).sort());
107110
strictEqual(result.filesRequested, filesRequested, 'Files requested different than expected, got: ' + result.filesRequested);
@@ -114,3 +117,24 @@ suite('Terminal Suggest', () => {
114117
});
115118
}
116119
});
120+
121+
122+
class MockFigExecuteExternals implements IFigExecuteExternals {
123+
public async executeCommand(input: Fig.ExecuteCommandInput): Promise<Fig.ExecuteCommandOutput> {
124+
return this.executeCommandTimeout(input);
125+
}
126+
async executeCommandTimeout(input: Fig.ExecuteCommandInput): Promise<Fig.ExecuteCommandOutput> {
127+
const command = [input.command, ...input.args].join(' ');
128+
try {
129+
return {
130+
status: 0,
131+
stdout: input.command,
132+
stderr: '',
133+
};
134+
} catch (err) {
135+
console.error(`Error running shell command '${command}'`, { err });
136+
throw err;
137+
}
138+
}
139+
}
140+

0 commit comments

Comments
 (0)