@microsoft/inshellisense 0.0.1-rc.4 → 0.0.1-rc.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +23 -7
- package/build/commands/complete.js +15 -0
- package/build/commands/root.js +20 -29
- package/build/commands/uninstall.js +1 -1
- package/build/index.js +5 -6
- package/build/isterm/commandManager.js +217 -0
- package/build/isterm/index.js +4 -0
- package/build/isterm/pty.js +248 -0
- package/build/runtime/generator.js +21 -10
- package/build/runtime/parser.js +2 -2
- package/build/runtime/runtime.js +37 -23
- package/build/runtime/suggestion.js +34 -14
- package/build/runtime/template.js +24 -18
- package/build/runtime/utils.js +42 -12
- package/build/ui/input.js +7 -53
- package/build/ui/suggestionManager.js +139 -0
- package/build/ui/ui-root.js +120 -64
- package/build/ui/ui-uninstall.js +1 -3
- package/build/ui/utils.js +41 -0
- package/build/utils/ansi.js +56 -0
- package/build/utils/config.js +68 -0
- package/build/utils/log.js +30 -0
- package/build/utils/shell.js +84 -1
- package/package.json +12 -5
- package/shell/bash-preexec.sh +380 -0
- package/shell/shellIntegration-env.zsh +9 -0
- package/shell/shellIntegration-login.zsh +4 -0
- package/shell/shellIntegration-profile.zsh +4 -0
- package/shell/shellIntegration-rc.zsh +52 -0
- package/shell/shellIntegration.bash +65 -0
- package/shell/shellIntegration.fish +14 -0
- package/shell/shellIntegration.ps1 +17 -0
- package/build/commands/bind.js +0 -12
- package/build/ui/suggestions.js +0 -84
- package/build/ui/ui-bind.js +0 -69
- package/build/ui/ui-init.js +0 -44
- package/build/utils/bindings.js +0 -236
- package/build/utils/cache.js +0 -21
- package/shell/key-bindings-powershell.ps1 +0 -27
- package/shell/key-bindings-pwsh.ps1 +0 -27
- package/shell/key-bindings.bash +0 -7
- package/shell/key-bindings.fish +0 -8
- package/shell/key-bindings.zsh +0 -10
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
// Copyright (c) Microsoft Corporation.
|
|
2
2
|
// Licensed under the MIT License.
|
|
3
|
+
import log from "../utils/log.js";
|
|
3
4
|
import { runTemplates } from "./template.js";
|
|
4
5
|
import { buildExecuteShellCommand } from "./utils.js";
|
|
5
|
-
const getGeneratorContext = () => {
|
|
6
|
+
const getGeneratorContext = (cwd) => {
|
|
6
7
|
return {
|
|
7
8
|
environmentVariables: Object.fromEntries(Object.entries(process.env).filter((entry) => entry[1] != null)),
|
|
8
|
-
currentWorkingDirectory:
|
|
9
|
+
currentWorkingDirectory: cwd,
|
|
9
10
|
currentProcess: "",
|
|
10
11
|
sshPrefix: "",
|
|
11
12
|
isDangerous: false,
|
|
@@ -13,30 +14,40 @@ const getGeneratorContext = () => {
|
|
|
13
14
|
};
|
|
14
15
|
};
|
|
15
16
|
// TODO: add support for caching, trigger, & getQueryTerm
|
|
16
|
-
export const runGenerator = async (generator, tokens) => {
|
|
17
|
-
|
|
17
|
+
export const runGenerator = async (generator, tokens, cwd) => {
|
|
18
|
+
// TODO: support trigger
|
|
19
|
+
const { script, postProcess, scriptTimeout, splitOn, custom, template, filterTemplateSuggestions } = generator;
|
|
18
20
|
const executeShellCommand = buildExecuteShellCommand(scriptTimeout ?? 5000);
|
|
19
21
|
const suggestions = [];
|
|
20
22
|
try {
|
|
21
23
|
if (script) {
|
|
22
|
-
const
|
|
24
|
+
const shellInput = typeof script === "function" ? script(tokens) : script;
|
|
25
|
+
const scriptOutput = Array.isArray(shellInput)
|
|
26
|
+
? await executeShellCommand({ command: shellInput.at(0) ?? "", args: shellInput.slice(1) })
|
|
27
|
+
: await executeShellCommand(shellInput);
|
|
23
28
|
if (postProcess) {
|
|
24
|
-
suggestions.push(...postProcess(scriptOutput, tokens));
|
|
29
|
+
suggestions.push(...postProcess(scriptOutput.stdout, tokens));
|
|
25
30
|
}
|
|
26
31
|
else if (splitOn) {
|
|
27
|
-
suggestions.push(...scriptOutput.split(splitOn).map((s) => ({ name: s })));
|
|
32
|
+
suggestions.push(...scriptOutput.stdout.split(splitOn).map((s) => ({ name: s })));
|
|
28
33
|
}
|
|
29
34
|
}
|
|
30
35
|
if (custom) {
|
|
31
|
-
suggestions.push(...(await custom(tokens, executeShellCommand, getGeneratorContext())));
|
|
36
|
+
suggestions.push(...(await custom(tokens, executeShellCommand, getGeneratorContext(cwd))));
|
|
32
37
|
}
|
|
33
38
|
if (template != null) {
|
|
34
|
-
|
|
39
|
+
const templateSuggestions = await runTemplates(template, cwd);
|
|
40
|
+
if (filterTemplateSuggestions) {
|
|
41
|
+
suggestions.push(...filterTemplateSuggestions(templateSuggestions));
|
|
42
|
+
}
|
|
43
|
+
else {
|
|
44
|
+
suggestions.push(...templateSuggestions);
|
|
45
|
+
}
|
|
35
46
|
}
|
|
36
47
|
return suggestions;
|
|
37
48
|
}
|
|
38
49
|
catch (e) {
|
|
39
|
-
|
|
50
|
+
log.debug({ msg: "generator failed", e, script, splitOn, template });
|
|
40
51
|
}
|
|
41
52
|
return suggestions;
|
|
42
53
|
};
|
package/build/runtime/parser.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// Copyright (c) Microsoft Corporation.
|
|
2
2
|
// Licensed under the MIT License.
|
|
3
|
-
const cmdDelim = /(\|\|)|(&&)|(;)/;
|
|
3
|
+
const cmdDelim = /(\|\|)|(&&)|(;)|(\|)/;
|
|
4
4
|
const spaceRegex = /\s/;
|
|
5
5
|
export const parseCommand = (command) => {
|
|
6
6
|
const lastCommand = command.split(cmdDelim).at(-1)?.trimStart();
|
|
@@ -56,7 +56,7 @@ const lex = (command) => {
|
|
|
56
56
|
tokens.push({
|
|
57
57
|
token: command.slice(readingIdx),
|
|
58
58
|
complete: false,
|
|
59
|
-
isOption:
|
|
59
|
+
isOption: readingFlag,
|
|
60
60
|
});
|
|
61
61
|
}
|
|
62
62
|
return tokens;
|
package/build/runtime/runtime.js
CHANGED
|
@@ -4,9 +4,10 @@ import speclist, { diffVersionedCompletions as versionedSpeclist,
|
|
|
4
4
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
5
5
|
// @ts-ignore
|
|
6
6
|
} from "@withfig/autocomplete/build/index.js";
|
|
7
|
+
import path from "node:path";
|
|
7
8
|
import { parseCommand } from "./parser.js";
|
|
8
9
|
import { getArgDrivenRecommendation, getSubcommandDrivenRecommendation } from "./suggestion.js";
|
|
9
|
-
import { buildExecuteShellCommand } from "./utils.js";
|
|
10
|
+
import { buildExecuteShellCommand, resolveCwd } from "./utils.js";
|
|
10
11
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- recursive type, setting as any
|
|
11
12
|
const specSet = {};
|
|
12
13
|
speclist.forEach((s) => {
|
|
@@ -46,7 +47,7 @@ const lazyLoadSpec = async (key) => {
|
|
|
46
47
|
const lazyLoadSpecLocation = async (location) => {
|
|
47
48
|
return; //TODO: implement spec location loading
|
|
48
49
|
};
|
|
49
|
-
export const getSuggestions = async (cmd) => {
|
|
50
|
+
export const getSuggestions = async (cmd, cwd, shell) => {
|
|
50
51
|
const activeCmd = parseCommand(cmd);
|
|
51
52
|
const rootToken = activeCmd.at(0);
|
|
52
53
|
if (activeCmd.length === 0 || !rootToken?.complete) {
|
|
@@ -58,11 +59,19 @@ export const getSuggestions = async (cmd) => {
|
|
|
58
59
|
const subcommand = getSubcommand(spec);
|
|
59
60
|
if (subcommand == null)
|
|
60
61
|
return;
|
|
61
|
-
const
|
|
62
|
+
const lastCommand = activeCmd.at(-1);
|
|
63
|
+
const { cwd: resolvedCwd, pathy, complete: pathyComplete } = await resolveCwd(lastCommand, cwd, shell);
|
|
64
|
+
if (pathy && lastCommand) {
|
|
65
|
+
lastCommand.isPath = true;
|
|
66
|
+
lastCommand.isPathComplete = pathyComplete;
|
|
67
|
+
}
|
|
68
|
+
const result = await runSubcommand(activeCmd.slice(1), subcommand, resolvedCwd);
|
|
62
69
|
if (result == null)
|
|
63
70
|
return;
|
|
64
|
-
|
|
65
|
-
|
|
71
|
+
let charactersToDrop = lastCommand?.complete ? 0 : lastCommand?.token.length ?? 0;
|
|
72
|
+
if (pathy) {
|
|
73
|
+
charactersToDrop = pathyComplete ? 0 : path.basename(lastCommand?.token ?? "").length;
|
|
74
|
+
}
|
|
66
75
|
return { ...result, charactersToDrop };
|
|
67
76
|
};
|
|
68
77
|
const getPersistentOptions = (persistentOptions, options) => {
|
|
@@ -142,7 +151,7 @@ const getPersistentTokens = (tokens) => {
|
|
|
142
151
|
const getArgs = (args) => {
|
|
143
152
|
return args instanceof Array ? args : args != null ? [args] : [];
|
|
144
153
|
};
|
|
145
|
-
const runOption = async (tokens, option, subcommand, persistentOptions, acceptedTokens) => {
|
|
154
|
+
const runOption = async (tokens, option, subcommand, cwd, persistentOptions, acceptedTokens) => {
|
|
146
155
|
if (tokens.length === 0) {
|
|
147
156
|
throw new Error("invalid state reached, option expected but no tokens found");
|
|
148
157
|
}
|
|
@@ -150,37 +159,37 @@ const runOption = async (tokens, option, subcommand, persistentOptions, accepted
|
|
|
150
159
|
const isPersistent = persistentOptions.some((o) => (typeof o.name === "string" ? o.name === activeToken.token : o.name.includes(activeToken.token)));
|
|
151
160
|
if ((option.args instanceof Array && option.args.length > 0) || option.args != null) {
|
|
152
161
|
const args = option.args instanceof Array ? option.args : [option.args];
|
|
153
|
-
return runArg(tokens.slice(1), args, subcommand, persistentOptions, acceptedTokens.concat(activeToken), true, false);
|
|
162
|
+
return runArg(tokens.slice(1), args, subcommand, cwd, persistentOptions, acceptedTokens.concat(activeToken), true, false);
|
|
154
163
|
}
|
|
155
|
-
return runSubcommand(tokens.slice(1), subcommand, persistentOptions, acceptedTokens.concat({ ...activeToken, isPersistent }));
|
|
164
|
+
return runSubcommand(tokens.slice(1), subcommand, cwd, persistentOptions, acceptedTokens.concat({ ...activeToken, isPersistent }));
|
|
156
165
|
};
|
|
157
|
-
const runArg = async (tokens, args, subcommand, persistentOptions, acceptedTokens, fromOption, fromVariadic) => {
|
|
166
|
+
const runArg = async (tokens, args, subcommand, cwd, persistentOptions, acceptedTokens, fromOption, fromVariadic) => {
|
|
158
167
|
if (args.length === 0) {
|
|
159
|
-
return runSubcommand(tokens, subcommand, persistentOptions, acceptedTokens, true, !fromOption);
|
|
168
|
+
return runSubcommand(tokens, subcommand, cwd, persistentOptions, acceptedTokens, true, !fromOption);
|
|
160
169
|
}
|
|
161
170
|
else if (tokens.length === 0) {
|
|
162
|
-
return await getArgDrivenRecommendation(args, subcommand, persistentOptions, undefined, acceptedTokens, fromVariadic);
|
|
171
|
+
return await getArgDrivenRecommendation(args, subcommand, persistentOptions, undefined, acceptedTokens, fromVariadic, cwd);
|
|
163
172
|
}
|
|
164
173
|
else if (!tokens.at(0)?.complete) {
|
|
165
|
-
return await getArgDrivenRecommendation(args, subcommand, persistentOptions, tokens[0]
|
|
174
|
+
return await getArgDrivenRecommendation(args, subcommand, persistentOptions, tokens[0], acceptedTokens, fromVariadic, cwd);
|
|
166
175
|
}
|
|
167
176
|
const activeToken = tokens[0];
|
|
168
177
|
if (args.every((a) => a.isOptional)) {
|
|
169
178
|
if (activeToken.isOption) {
|
|
170
179
|
const option = getOption(activeToken, persistentOptions.concat(subcommand.options ?? []));
|
|
171
180
|
if (option != null) {
|
|
172
|
-
return runOption(tokens, option, subcommand, persistentOptions, acceptedTokens);
|
|
181
|
+
return runOption(tokens, option, subcommand, cwd, persistentOptions, acceptedTokens);
|
|
173
182
|
}
|
|
174
183
|
return;
|
|
175
184
|
}
|
|
176
185
|
const nextSubcommand = await genSubcommand(activeToken.token, subcommand);
|
|
177
186
|
if (nextSubcommand != null) {
|
|
178
|
-
return runSubcommand(tokens.slice(1), nextSubcommand, persistentOptions, getPersistentTokens(acceptedTokens.concat(activeToken)));
|
|
187
|
+
return runSubcommand(tokens.slice(1), nextSubcommand, cwd, persistentOptions, getPersistentTokens(acceptedTokens.concat(activeToken)));
|
|
179
188
|
}
|
|
180
189
|
}
|
|
181
190
|
const activeArg = args[0];
|
|
182
191
|
if (activeArg.isVariadic) {
|
|
183
|
-
return runArg(tokens.slice(1), args, subcommand, persistentOptions, acceptedTokens.concat(activeToken), fromOption, true);
|
|
192
|
+
return runArg(tokens.slice(1), args, subcommand, cwd, persistentOptions, acceptedTokens.concat(activeToken), fromOption, true);
|
|
184
193
|
}
|
|
185
194
|
else if (activeArg.isCommand) {
|
|
186
195
|
if (tokens.length <= 0) {
|
|
@@ -192,16 +201,16 @@ const runArg = async (tokens, args, subcommand, persistentOptions, acceptedToken
|
|
|
192
201
|
const subcommand = getSubcommand(spec);
|
|
193
202
|
if (subcommand == null)
|
|
194
203
|
return;
|
|
195
|
-
return runSubcommand(tokens.slice(1), subcommand);
|
|
204
|
+
return runSubcommand(tokens.slice(1), subcommand, cwd);
|
|
196
205
|
}
|
|
197
|
-
return runArg(tokens.slice(1), args.slice(1), subcommand, persistentOptions, acceptedTokens.concat(activeToken), fromOption, false);
|
|
206
|
+
return runArg(tokens.slice(1), args.slice(1), subcommand, cwd, persistentOptions, acceptedTokens.concat(activeToken), fromOption, false);
|
|
198
207
|
};
|
|
199
|
-
const runSubcommand = async (tokens, subcommand, persistentOptions = [], acceptedTokens = [], argsDepleted = false, argsUsed = false) => {
|
|
208
|
+
const runSubcommand = async (tokens, subcommand, cwd, persistentOptions = [], acceptedTokens = [], argsDepleted = false, argsUsed = false) => {
|
|
200
209
|
if (tokens.length === 0) {
|
|
201
|
-
return getSubcommandDrivenRecommendation(subcommand, persistentOptions, undefined, argsDepleted, argsUsed, acceptedTokens);
|
|
210
|
+
return getSubcommandDrivenRecommendation(subcommand, persistentOptions, undefined, argsDepleted, argsUsed, acceptedTokens, cwd);
|
|
202
211
|
}
|
|
203
212
|
else if (!tokens.at(0)?.complete) {
|
|
204
|
-
return getSubcommandDrivenRecommendation(subcommand, persistentOptions, tokens[0]
|
|
213
|
+
return getSubcommandDrivenRecommendation(subcommand, persistentOptions, tokens[0], argsDepleted, argsUsed, acceptedTokens, cwd);
|
|
205
214
|
}
|
|
206
215
|
const activeToken = tokens[0];
|
|
207
216
|
const activeArgsLength = subcommand.args instanceof Array ? subcommand.args.length : 1;
|
|
@@ -209,16 +218,21 @@ const runSubcommand = async (tokens, subcommand, persistentOptions = [], accepte
|
|
|
209
218
|
if (activeToken.isOption) {
|
|
210
219
|
const option = getOption(activeToken, allOptions);
|
|
211
220
|
if (option != null) {
|
|
212
|
-
return runOption(tokens, option, subcommand, persistentOptions, acceptedTokens);
|
|
221
|
+
return runOption(tokens, option, subcommand, cwd, persistentOptions, acceptedTokens);
|
|
213
222
|
}
|
|
214
223
|
return;
|
|
215
224
|
}
|
|
216
225
|
const nextSubcommand = await genSubcommand(activeToken.token, subcommand);
|
|
217
226
|
if (nextSubcommand != null) {
|
|
218
|
-
return runSubcommand(tokens.slice(1), nextSubcommand, getPersistentOptions(persistentOptions, subcommand.options), getPersistentTokens(acceptedTokens.concat(activeToken)));
|
|
227
|
+
return runSubcommand(tokens.slice(1), nextSubcommand, cwd, getPersistentOptions(persistentOptions, subcommand.options), getPersistentTokens(acceptedTokens.concat(activeToken)));
|
|
219
228
|
}
|
|
220
229
|
if (activeArgsLength <= 0) {
|
|
221
230
|
return; // not subcommand or option & no args exist
|
|
222
231
|
}
|
|
223
|
-
|
|
232
|
+
const args = getArgs(subcommand.args);
|
|
233
|
+
if (args.length != 0) {
|
|
234
|
+
return runArg(tokens, args, subcommand, cwd, allOptions, acceptedTokens, false, false);
|
|
235
|
+
}
|
|
236
|
+
// if the subcommand has no args specified, fallback to the subcommand and ignore this item
|
|
237
|
+
return runSubcommand(tokens.slice(1), subcommand, cwd, persistentOptions, acceptedTokens.concat(activeToken));
|
|
224
238
|
};
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
// Copyright (c) Microsoft Corporation.
|
|
2
2
|
// Licensed under the MIT License.
|
|
3
|
+
import path from "node:path";
|
|
3
4
|
import { runGenerator } from "./generator.js";
|
|
4
5
|
import { runTemplates } from "./template.js";
|
|
5
6
|
var SuggestionIcons;
|
|
@@ -116,14 +117,14 @@ function filter(suggestions, filterStrategy, partialCmd, suggestionType) {
|
|
|
116
117
|
.filter((s) => s != null);
|
|
117
118
|
}
|
|
118
119
|
}
|
|
119
|
-
const generatorSuggestions = async (generator, acceptedTokens, filterStrategy, partialCmd) => {
|
|
120
|
+
const generatorSuggestions = async (generator, acceptedTokens, filterStrategy, partialCmd, cwd) => {
|
|
120
121
|
const generators = generator instanceof Array ? generator : generator ? [generator] : [];
|
|
121
122
|
const tokens = acceptedTokens.map((t) => t.token);
|
|
122
|
-
const suggestions = (await Promise.all(generators.map((gen) => runGenerator(gen, tokens)))).flat();
|
|
123
|
+
const suggestions = (await Promise.all(generators.map((gen) => runGenerator(gen, tokens, cwd)))).flat();
|
|
123
124
|
return filter(suggestions, filterStrategy, partialCmd, undefined);
|
|
124
125
|
};
|
|
125
|
-
const templateSuggestions = async (templates, filterStrategy, partialCmd) => {
|
|
126
|
-
return filter(await runTemplates(templates ?? []), filterStrategy, partialCmd, undefined);
|
|
126
|
+
const templateSuggestions = async (templates, filterStrategy, partialCmd, cwd) => {
|
|
127
|
+
return filter(await runTemplates(templates ?? [], cwd), filterStrategy, partialCmd, undefined);
|
|
127
128
|
};
|
|
128
129
|
const suggestionSuggestions = (suggestions, filterStrategy, partialCmd) => {
|
|
129
130
|
const cleanedSuggestions = suggestions?.map((s) => (typeof s === "string" ? { name: s } : s)) ?? [];
|
|
@@ -137,17 +138,32 @@ const optionSuggestions = (options, acceptedTokens, filterStrategy, partialCmd)
|
|
|
137
138
|
const validOptions = options?.filter((o) => o.exclusiveOn?.every((exclusiveOption) => !usedOptions.has(exclusiveOption)) ?? true);
|
|
138
139
|
return filter(validOptions ?? [], filterStrategy, partialCmd, "option");
|
|
139
140
|
};
|
|
140
|
-
const
|
|
141
|
+
const removeAcceptedSuggestions = (suggestions, acceptedTokens) => {
|
|
141
142
|
const seen = new Set(acceptedTokens.map((t) => t.token));
|
|
142
143
|
return suggestions.filter((s) => s.allNames.every((n) => !seen.has(n)));
|
|
143
144
|
};
|
|
145
|
+
const removeDuplicateSuggestion = (suggestions) => {
|
|
146
|
+
const seen = new Set();
|
|
147
|
+
return suggestions
|
|
148
|
+
.map((s) => {
|
|
149
|
+
if (seen.has(s.name))
|
|
150
|
+
return null;
|
|
151
|
+
seen.add(s.name);
|
|
152
|
+
return s;
|
|
153
|
+
})
|
|
154
|
+
.filter((s) => s != null);
|
|
155
|
+
};
|
|
144
156
|
const removeEmptySuggestion = (suggestions) => {
|
|
145
157
|
return suggestions.filter((s) => s.name.length > 0);
|
|
146
158
|
};
|
|
147
|
-
export const getSubcommandDrivenRecommendation = async (subcommand, persistentOptions,
|
|
159
|
+
export const getSubcommandDrivenRecommendation = async (subcommand, persistentOptions, partialToken, argsDepleted, argsFromSubcommand, acceptedTokens, cwd) => {
|
|
148
160
|
if (argsDepleted && argsFromSubcommand) {
|
|
149
161
|
return;
|
|
150
162
|
}
|
|
163
|
+
let partialCmd = partialToken?.token;
|
|
164
|
+
if (partialToken?.isPath) {
|
|
165
|
+
partialCmd = partialToken.isPathComplete ? "" : path.basename(partialCmd ?? "");
|
|
166
|
+
}
|
|
151
167
|
const suggestions = [];
|
|
152
168
|
const argLength = subcommand.args instanceof Array ? subcommand.args.length : subcommand.args ? 1 : 0;
|
|
153
169
|
const allOptions = persistentOptions.concat(subcommand.options ?? []);
|
|
@@ -157,28 +173,32 @@ export const getSubcommandDrivenRecommendation = async (subcommand, persistentOp
|
|
|
157
173
|
}
|
|
158
174
|
if (argLength != 0) {
|
|
159
175
|
const activeArg = subcommand.args instanceof Array ? subcommand.args[0] : subcommand.args;
|
|
160
|
-
suggestions.push(...(await generatorSuggestions(activeArg?.generators, acceptedTokens, activeArg?.filterStrategy, partialCmd)));
|
|
176
|
+
suggestions.push(...(await generatorSuggestions(activeArg?.generators, acceptedTokens, activeArg?.filterStrategy, partialCmd, cwd)));
|
|
161
177
|
suggestions.push(...suggestionSuggestions(activeArg?.suggestions, activeArg?.filterStrategy, partialCmd));
|
|
162
|
-
suggestions.push(...(await templateSuggestions(activeArg?.template, activeArg?.filterStrategy, partialCmd)));
|
|
178
|
+
suggestions.push(...(await templateSuggestions(activeArg?.template, activeArg?.filterStrategy, partialCmd, cwd)));
|
|
163
179
|
}
|
|
164
180
|
return {
|
|
165
|
-
suggestions: removeEmptySuggestion(
|
|
181
|
+
suggestions: removeDuplicateSuggestion(removeEmptySuggestion(removeAcceptedSuggestions(suggestions.sort((a, b) => b.priority - a.priority), acceptedTokens))),
|
|
166
182
|
};
|
|
167
183
|
};
|
|
168
|
-
export const getArgDrivenRecommendation = async (args, subcommand, persistentOptions,
|
|
184
|
+
export const getArgDrivenRecommendation = async (args, subcommand, persistentOptions, partialToken, acceptedTokens, variadicArgBound, cwd) => {
|
|
185
|
+
let partialCmd = partialToken?.token;
|
|
186
|
+
if (partialToken?.isPath) {
|
|
187
|
+
partialCmd = partialToken.isPathComplete ? "" : path.basename(partialCmd ?? "");
|
|
188
|
+
}
|
|
169
189
|
const activeArg = args[0];
|
|
170
190
|
const allOptions = persistentOptions.concat(subcommand.options ?? []);
|
|
171
191
|
const suggestions = [
|
|
172
|
-
...(await generatorSuggestions(args[0].generators, acceptedTokens, activeArg?.filterStrategy, partialCmd)),
|
|
192
|
+
...(await generatorSuggestions(args[0].generators, acceptedTokens, activeArg?.filterStrategy, partialCmd, cwd)),
|
|
173
193
|
...suggestionSuggestions(args[0].suggestions, activeArg?.filterStrategy, partialCmd),
|
|
174
|
-
...(await templateSuggestions(args[0].template, activeArg?.filterStrategy, partialCmd)),
|
|
194
|
+
...(await templateSuggestions(args[0].template, activeArg?.filterStrategy, partialCmd, cwd)),
|
|
175
195
|
];
|
|
176
|
-
if (
|
|
196
|
+
if (activeArg.isOptional || (activeArg.isVariadic && variadicArgBound)) {
|
|
177
197
|
suggestions.push(...subcommandSuggestions(subcommand.subcommands, activeArg?.filterStrategy, partialCmd));
|
|
178
198
|
suggestions.push(...optionSuggestions(allOptions, acceptedTokens, activeArg?.filterStrategy, partialCmd));
|
|
179
199
|
}
|
|
180
200
|
return {
|
|
181
|
-
suggestions: removeEmptySuggestion(
|
|
201
|
+
suggestions: removeDuplicateSuggestion(removeEmptySuggestion(removeAcceptedSuggestions(suggestions.sort((a, b) => b.priority - a.priority), acceptedTokens))),
|
|
182
202
|
argumentDescription: activeArg.description ?? activeArg.name,
|
|
183
203
|
};
|
|
184
204
|
};
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
// Copyright (c) Microsoft Corporation.
|
|
2
2
|
// Licensed under the MIT License.
|
|
3
|
-
import fsAsync from "fs/promises";
|
|
4
|
-
import
|
|
5
|
-
const filepathsTemplate = async () => {
|
|
6
|
-
const files = await fsAsync.readdir(
|
|
7
|
-
return files.filter((f) => f.isFile() || f.isDirectory()).map((f) => ({ name: f.name, priority: 90 }));
|
|
3
|
+
import fsAsync from "node:fs/promises";
|
|
4
|
+
import log from "../utils/log.js";
|
|
5
|
+
const filepathsTemplate = async (cwd) => {
|
|
6
|
+
const files = await fsAsync.readdir(cwd, { withFileTypes: true });
|
|
7
|
+
return files.filter((f) => f.isFile() || f.isDirectory()).map((f) => ({ name: f.name, priority: 90, context: { templateType: "filepaths" } }));
|
|
8
8
|
};
|
|
9
|
-
const foldersTemplate = async () => {
|
|
10
|
-
const files = await fsAsync.readdir(
|
|
11
|
-
return files.filter((f) => f.isDirectory()).map((f) => ({ name: f.name, priority: 90 }));
|
|
9
|
+
const foldersTemplate = async (cwd) => {
|
|
10
|
+
const files = await fsAsync.readdir(cwd, { withFileTypes: true });
|
|
11
|
+
return files.filter((f) => f.isDirectory()).map((f) => ({ name: f.name, priority: 90, context: { templateType: "folders" } }));
|
|
12
12
|
};
|
|
13
13
|
// TODO: implement history template
|
|
14
14
|
const historyTemplate = () => {
|
|
@@ -18,18 +18,24 @@ const historyTemplate = () => {
|
|
|
18
18
|
const helpTemplate = () => {
|
|
19
19
|
return [];
|
|
20
20
|
};
|
|
21
|
-
export const runTemplates = async (template) => {
|
|
21
|
+
export const runTemplates = async (template, cwd) => {
|
|
22
22
|
const templates = template instanceof Array ? template : [template];
|
|
23
23
|
return (await Promise.all(templates.map(async (t) => {
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
24
|
+
try {
|
|
25
|
+
switch (t) {
|
|
26
|
+
case "filepaths":
|
|
27
|
+
return await filepathsTemplate(cwd);
|
|
28
|
+
case "folders":
|
|
29
|
+
return await foldersTemplate(cwd);
|
|
30
|
+
case "history":
|
|
31
|
+
return historyTemplate();
|
|
32
|
+
case "help":
|
|
33
|
+
return helpTemplate();
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
catch (e) {
|
|
37
|
+
log.debug({ msg: "template failed", e, template: t, cwd });
|
|
38
|
+
return [];
|
|
33
39
|
}
|
|
34
40
|
}))).flat();
|
|
35
41
|
};
|
package/build/runtime/utils.js
CHANGED
|
@@ -1,22 +1,52 @@
|
|
|
1
1
|
// Copyright (c) Microsoft Corporation.
|
|
2
2
|
// Licensed under the MIT License.
|
|
3
|
-
import
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { spawn } from "node:child_process";
|
|
5
|
+
import fsAsync from "node:fs/promises";
|
|
6
|
+
import { Shell } from "../utils/shell.js";
|
|
7
|
+
import log from "../utils/log.js";
|
|
8
|
+
export const buildExecuteShellCommand = (timeout) => async ({ command, env, args, cwd }) => {
|
|
9
|
+
const child = spawn(command, args, { cwd, env });
|
|
10
|
+
setTimeout(() => child.kill("SIGKILL"), timeout);
|
|
11
|
+
let stdout = "";
|
|
12
|
+
let stderr = "";
|
|
13
|
+
child.stdout.on("data", (data) => (stdout += data));
|
|
14
|
+
child.stderr.on("data", (data) => (stderr += data));
|
|
15
|
+
child.on("error", (err) => {
|
|
16
|
+
log.debug({ msg: "shell command failed", e: err.message });
|
|
11
17
|
});
|
|
12
|
-
};
|
|
13
|
-
export const executeShellCommandTTY = async (shell, command) => {
|
|
14
|
-
const child = spawn(shell, ["-c", command.trim()], { stdio: "inherit" });
|
|
15
18
|
return new Promise((resolve) => {
|
|
16
19
|
child.on("close", (code) => {
|
|
17
20
|
resolve({
|
|
18
|
-
code,
|
|
21
|
+
status: code ?? 0,
|
|
22
|
+
stderr,
|
|
23
|
+
stdout,
|
|
19
24
|
});
|
|
20
25
|
});
|
|
21
26
|
});
|
|
22
27
|
};
|
|
28
|
+
export const resolveCwd = async (cmdToken, cwd, shell) => {
|
|
29
|
+
if (cmdToken == null)
|
|
30
|
+
return { cwd, pathy: false, complete: false };
|
|
31
|
+
const { token } = cmdToken;
|
|
32
|
+
const sep = shell == Shell.Bash ? "/" : path.sep;
|
|
33
|
+
if (!token.includes(sep))
|
|
34
|
+
return { cwd, pathy: false, complete: false };
|
|
35
|
+
const resolvedCwd = path.isAbsolute(token) ? token : path.join(cwd, token);
|
|
36
|
+
try {
|
|
37
|
+
await fsAsync.access(resolvedCwd, fsAsync.constants.R_OK);
|
|
38
|
+
return { cwd: resolvedCwd, pathy: true, complete: token.endsWith(sep) };
|
|
39
|
+
}
|
|
40
|
+
catch {
|
|
41
|
+
// fallback to the parent folder if possible
|
|
42
|
+
const baselessCwd = resolvedCwd.substring(0, resolvedCwd.length - path.basename(resolvedCwd).length);
|
|
43
|
+
try {
|
|
44
|
+
await fsAsync.access(baselessCwd, fsAsync.constants.R_OK);
|
|
45
|
+
return { cwd: baselessCwd, pathy: true, complete: token.endsWith(sep) };
|
|
46
|
+
}
|
|
47
|
+
catch {
|
|
48
|
+
/*empty*/
|
|
49
|
+
}
|
|
50
|
+
return { cwd, pathy: false, complete: false };
|
|
51
|
+
}
|
|
52
|
+
};
|
package/build/ui/input.js
CHANGED
|
@@ -1,55 +1,9 @@
|
|
|
1
1
|
// Copyright (c) Microsoft Corporation.
|
|
2
2
|
// Licensed under the MIT License.
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
const [cursorBlink, setCursorBlink] = useState(true);
|
|
11
|
-
useEffect(() => {
|
|
12
|
-
setTimeout(() => {
|
|
13
|
-
setCursorBlink(!cursorBlink);
|
|
14
|
-
}, BlinkSpeed);
|
|
15
|
-
}, [cursorBlink]);
|
|
16
|
-
// TODO: arrow key navigation shortcuts (emacs & vim modes)
|
|
17
|
-
useInput((input, key) => {
|
|
18
|
-
// TODO: handle delete better on unix systems: https://github.com/vadimdemedes/ink/issues/634
|
|
19
|
-
const windows = process.platform === "win32";
|
|
20
|
-
const backspaceKey = windows ? key.backspace : key.backspace || key.delete;
|
|
21
|
-
const deleteKey = windows ? key.delete : false;
|
|
22
|
-
if (backspaceKey) {
|
|
23
|
-
setValue([...value].slice(0, Math.max(cursorLocation - 1, 0)).join("") + [...value].slice(cursorLocation).join(""));
|
|
24
|
-
setCursorLocation(Math.max(cursorLocation - 1, 0));
|
|
25
|
-
}
|
|
26
|
-
else if (deleteKey) {
|
|
27
|
-
setValue([...value].slice(0, cursorLocation).join("") + [...value].slice(Math.min(value.length, cursorLocation + 1)).join(""));
|
|
28
|
-
}
|
|
29
|
-
else if (key.leftArrow) {
|
|
30
|
-
setCursorLocation(Math.max(cursorLocation - 1, 0));
|
|
31
|
-
}
|
|
32
|
-
else if (key.rightArrow) {
|
|
33
|
-
setCursorLocation(Math.min(cursorLocation + 1, value.length));
|
|
34
|
-
}
|
|
35
|
-
else if (key.tab) {
|
|
36
|
-
if (activeSuggestion) {
|
|
37
|
-
// TOOD: support insertValue
|
|
38
|
-
const newValue = [...value].slice(0, cursorLocation - tabCompletionDropSize).join("") + activeSuggestion.name + " ";
|
|
39
|
-
setValue(newValue);
|
|
40
|
-
setCursorLocation(newValue.length);
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
else if (input) {
|
|
44
|
-
setValue([...value].slice(0, cursorLocation).join("") + input + [...value].slice(cursorLocation).join(""));
|
|
45
|
-
setCursorLocation(cursorLocation + input.length);
|
|
46
|
-
}
|
|
47
|
-
});
|
|
48
|
-
const cursoredCommand = value + " ";
|
|
49
|
-
const cursoredText = [...cursoredCommand].slice(0, cursorLocation).join("") +
|
|
50
|
-
(cursorBlink ? chalk.bgHex(CursorColor).inverse([...cursoredCommand].at(cursorLocation)) : [...cursoredCommand].at(cursorLocation)) +
|
|
51
|
-
[...cursoredCommand].slice(cursorLocation + 1).join("");
|
|
52
|
-
return (React.createElement(Text, null,
|
|
53
|
-
prompt,
|
|
54
|
-
cursoredText));
|
|
55
|
-
}
|
|
3
|
+
export const inputModifier = (input) => {
|
|
4
|
+
switch (input.toString()) {
|
|
5
|
+
case "\b":
|
|
6
|
+
return "\u007F"; // DEL
|
|
7
|
+
}
|
|
8
|
+
return input.toString();
|
|
9
|
+
};
|