@microsoft/inshellisense 0.0.1-rc.15 → 0.0.1-rc.18
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/LICENSE +21 -21
- package/README.md +7 -4
- package/SECURITY.md +41 -41
- package/build/commands/complete-action.js +10 -0
- package/build/commands/doctor.js +11 -0
- package/build/commands/init.js +13 -7
- package/build/commands/root-action.js +38 -0
- package/build/commands/root.js +1 -0
- package/build/commands/shellenv.js +18 -0
- package/build/index.js +2 -0
- package/build/isterm/commandManager.js +3 -6
- package/build/isterm/pty.js +7 -1
- package/build/runtime/alias.js +3 -3
- package/build/runtime/generator.js +1 -1
- package/build/runtime/parser.js +68 -9
- package/build/runtime/runtime.js +23 -26
- package/build/runtime/suggestion.js +25 -21
- package/build/runtime/utils.js +74 -6
- package/build/ui/ui-doctor.js +41 -0
- package/build/utils/config.js +3 -4
- package/build/utils/log.js +10 -1
- package/build/utils/shell.js +123 -8
- package/package.json +9 -6
- package/scripts/postinstall.js +9 -0
- package/shell/shellIntegration-env.zsh +5 -2
- package/shell/shellIntegration-login.zsh +6 -1
- package/shell/shellIntegration-profile.zsh +6 -1
- package/shell/shellIntegration-rc.zsh +8 -0
- package/shell/shellIntegration.bash +2 -0
- package/shell/shellIntegration.fish +2 -1
- package/shell/shellIntegration.nu +1 -1
- package/shell/shellIntegration.ps1 +1 -1
- package/shell/shellIntegration.xsh +1 -1
package/build/runtime/runtime.js
CHANGED
|
@@ -76,7 +76,7 @@ export const loadLocalSpecsSet = async () => {
|
|
|
76
76
|
}
|
|
77
77
|
};
|
|
78
78
|
export const getSuggestions = async (cmd, cwd, shell) => {
|
|
79
|
-
let activeCmd = parseCommand(cmd);
|
|
79
|
+
let activeCmd = parseCommand(cmd, shell);
|
|
80
80
|
const rootToken = activeCmd.at(0);
|
|
81
81
|
if (activeCmd.length === 0 || !rootToken?.complete) {
|
|
82
82
|
return;
|
|
@@ -94,13 +94,10 @@ export const getSuggestions = async (cmd, cwd, shell) => {
|
|
|
94
94
|
lastCommand.isPath = true;
|
|
95
95
|
lastCommand.isPathComplete = pathyComplete;
|
|
96
96
|
}
|
|
97
|
-
const result = await runSubcommand(activeCmd.slice(1), subcommand, resolvedCwd);
|
|
97
|
+
const result = await runSubcommand(activeCmd.slice(1), subcommand, resolvedCwd, shell);
|
|
98
98
|
if (result == null)
|
|
99
99
|
return;
|
|
100
|
-
|
|
101
|
-
if (pathy) {
|
|
102
|
-
charactersToDrop = pathyComplete ? 0 : path.basename(lastCommand?.token ?? "").length;
|
|
103
|
-
}
|
|
100
|
+
const charactersToDrop = lastCommand?.complete ? 0 : lastCommand?.tokenLength;
|
|
104
101
|
return { ...result, charactersToDrop };
|
|
105
102
|
};
|
|
106
103
|
export const getSpecNames = () => {
|
|
@@ -123,7 +120,7 @@ const getSubcommand = (spec) => {
|
|
|
123
120
|
}
|
|
124
121
|
return spec;
|
|
125
122
|
};
|
|
126
|
-
const executeShellCommand = buildExecuteShellCommand(5000);
|
|
123
|
+
const executeShellCommand = await buildExecuteShellCommand(5000);
|
|
127
124
|
const genSubcommand = async (command, parentCommand) => {
|
|
128
125
|
if (!parentCommand.subcommands || parentCommand.subcommands.length === 0)
|
|
129
126
|
return;
|
|
@@ -195,7 +192,7 @@ const getPersistentTokens = (tokens) => {
|
|
|
195
192
|
const getArgs = (args) => {
|
|
196
193
|
return args instanceof Array ? args : args != null ? [args] : [];
|
|
197
194
|
};
|
|
198
|
-
const runOption = async (tokens, option, subcommand, cwd, persistentOptions, acceptedTokens) => {
|
|
195
|
+
const runOption = async (tokens, option, subcommand, cwd, shell, persistentOptions, acceptedTokens) => {
|
|
199
196
|
if (tokens.length === 0) {
|
|
200
197
|
throw new Error("invalid state reached, option expected but no tokens found");
|
|
201
198
|
}
|
|
@@ -203,40 +200,40 @@ const runOption = async (tokens, option, subcommand, cwd, persistentOptions, acc
|
|
|
203
200
|
const isPersistent = persistentOptions.some((o) => (typeof o.name === "string" ? o.name === activeToken.token : o.name.includes(activeToken.token)));
|
|
204
201
|
if ((option.args instanceof Array && option.args.length > 0) || option.args != null) {
|
|
205
202
|
const args = option.args instanceof Array ? option.args : [option.args];
|
|
206
|
-
return runArg(tokens.slice(1), args, subcommand, cwd, persistentOptions, acceptedTokens.concat(activeToken), true, false);
|
|
203
|
+
return runArg(tokens.slice(1), args, subcommand, cwd, shell, persistentOptions, acceptedTokens.concat(activeToken), true, false);
|
|
207
204
|
}
|
|
208
|
-
return runSubcommand(tokens.slice(1), subcommand, cwd, persistentOptions, acceptedTokens.concat({
|
|
205
|
+
return runSubcommand(tokens.slice(1), subcommand, cwd, shell, persistentOptions, acceptedTokens.concat({
|
|
209
206
|
...activeToken,
|
|
210
207
|
isPersistent,
|
|
211
208
|
}));
|
|
212
209
|
};
|
|
213
|
-
const runArg = async (tokens, args, subcommand, cwd, persistentOptions, acceptedTokens, fromOption, fromVariadic) => {
|
|
210
|
+
const runArg = async (tokens, args, subcommand, cwd, shell, persistentOptions, acceptedTokens, fromOption, fromVariadic) => {
|
|
214
211
|
if (args.length === 0) {
|
|
215
|
-
return runSubcommand(tokens, subcommand, cwd, persistentOptions, acceptedTokens, true, !fromOption);
|
|
212
|
+
return runSubcommand(tokens, subcommand, cwd, shell, persistentOptions, acceptedTokens, true, !fromOption);
|
|
216
213
|
}
|
|
217
214
|
else if (tokens.length === 0) {
|
|
218
|
-
return await getArgDrivenRecommendation(args, subcommand, persistentOptions, undefined, acceptedTokens, fromVariadic, cwd);
|
|
215
|
+
return await getArgDrivenRecommendation(args, subcommand, persistentOptions, undefined, acceptedTokens, fromVariadic, cwd, shell);
|
|
219
216
|
}
|
|
220
217
|
else if (!tokens.at(0)?.complete) {
|
|
221
|
-
return await getArgDrivenRecommendation(args, subcommand, persistentOptions, tokens[0], acceptedTokens, fromVariadic, cwd);
|
|
218
|
+
return await getArgDrivenRecommendation(args, subcommand, persistentOptions, tokens[0], acceptedTokens, fromVariadic, cwd, shell);
|
|
222
219
|
}
|
|
223
220
|
const activeToken = tokens[0];
|
|
224
221
|
if (args.every((a) => a.isOptional)) {
|
|
225
222
|
if (activeToken.isOption) {
|
|
226
223
|
const option = getOption(activeToken, persistentOptions.concat(subcommand.options ?? []));
|
|
227
224
|
if (option != null) {
|
|
228
|
-
return runOption(tokens, option, subcommand, cwd, persistentOptions, acceptedTokens);
|
|
225
|
+
return runOption(tokens, option, subcommand, cwd, shell, persistentOptions, acceptedTokens);
|
|
229
226
|
}
|
|
230
227
|
return;
|
|
231
228
|
}
|
|
232
229
|
const nextSubcommand = await genSubcommand(activeToken.token, subcommand);
|
|
233
230
|
if (nextSubcommand != null) {
|
|
234
|
-
return runSubcommand(tokens.slice(1), nextSubcommand, cwd, persistentOptions, getPersistentTokens(acceptedTokens.concat(activeToken)));
|
|
231
|
+
return runSubcommand(tokens.slice(1), nextSubcommand, cwd, shell, persistentOptions, getPersistentTokens(acceptedTokens.concat(activeToken)));
|
|
235
232
|
}
|
|
236
233
|
}
|
|
237
234
|
const activeArg = args[0];
|
|
238
235
|
if (activeArg.isVariadic) {
|
|
239
|
-
return runArg(tokens.slice(1), args, subcommand, cwd, persistentOptions, acceptedTokens.concat(activeToken), fromOption, true);
|
|
236
|
+
return runArg(tokens.slice(1), args, subcommand, cwd, shell, persistentOptions, acceptedTokens.concat(activeToken), fromOption, true);
|
|
240
237
|
}
|
|
241
238
|
else if (activeArg.isCommand) {
|
|
242
239
|
if (tokens.length <= 0) {
|
|
@@ -248,16 +245,16 @@ const runArg = async (tokens, args, subcommand, cwd, persistentOptions, accepted
|
|
|
248
245
|
const subcommand = getSubcommand(spec);
|
|
249
246
|
if (subcommand == null)
|
|
250
247
|
return;
|
|
251
|
-
return runSubcommand(tokens.slice(1), subcommand, cwd);
|
|
248
|
+
return runSubcommand(tokens.slice(1), subcommand, cwd, shell);
|
|
252
249
|
}
|
|
253
|
-
return runArg(tokens.slice(1), args.slice(1), subcommand, cwd, persistentOptions, acceptedTokens.concat(activeToken), fromOption, false);
|
|
250
|
+
return runArg(tokens.slice(1), args.slice(1), subcommand, cwd, shell, persistentOptions, acceptedTokens.concat(activeToken), fromOption, false);
|
|
254
251
|
};
|
|
255
|
-
const runSubcommand = async (tokens, subcommand, cwd, persistentOptions = [], acceptedTokens = [], argsDepleted = false, argsUsed = false) => {
|
|
252
|
+
const runSubcommand = async (tokens, subcommand, cwd, shell, persistentOptions = [], acceptedTokens = [], argsDepleted = false, argsUsed = false) => {
|
|
256
253
|
if (tokens.length === 0) {
|
|
257
|
-
return getSubcommandDrivenRecommendation(subcommand, persistentOptions, undefined, argsDepleted, argsUsed, acceptedTokens, cwd);
|
|
254
|
+
return getSubcommandDrivenRecommendation(subcommand, persistentOptions, undefined, argsDepleted, argsUsed, acceptedTokens, cwd, shell);
|
|
258
255
|
}
|
|
259
256
|
else if (!tokens.at(0)?.complete) {
|
|
260
|
-
return getSubcommandDrivenRecommendation(subcommand, persistentOptions, tokens[0], argsDepleted, argsUsed, acceptedTokens, cwd);
|
|
257
|
+
return getSubcommandDrivenRecommendation(subcommand, persistentOptions, tokens[0], argsDepleted, argsUsed, acceptedTokens, cwd, shell);
|
|
261
258
|
}
|
|
262
259
|
const activeToken = tokens[0];
|
|
263
260
|
const activeArgsLength = subcommand.args instanceof Array ? subcommand.args.length : 1;
|
|
@@ -265,21 +262,21 @@ const runSubcommand = async (tokens, subcommand, cwd, persistentOptions = [], ac
|
|
|
265
262
|
if (activeToken.isOption) {
|
|
266
263
|
const option = getOption(activeToken, allOptions);
|
|
267
264
|
if (option != null) {
|
|
268
|
-
return runOption(tokens, option, subcommand, cwd, persistentOptions, acceptedTokens);
|
|
265
|
+
return runOption(tokens, option, subcommand, cwd, shell, persistentOptions, acceptedTokens);
|
|
269
266
|
}
|
|
270
267
|
return;
|
|
271
268
|
}
|
|
272
269
|
const nextSubcommand = await genSubcommand(activeToken.token, subcommand);
|
|
273
270
|
if (nextSubcommand != null) {
|
|
274
|
-
return runSubcommand(tokens.slice(1), nextSubcommand, cwd, getPersistentOptions(persistentOptions, subcommand.options), getPersistentTokens(acceptedTokens.concat(activeToken)));
|
|
271
|
+
return runSubcommand(tokens.slice(1), nextSubcommand, cwd, shell, getPersistentOptions(persistentOptions, subcommand.options), getPersistentTokens(acceptedTokens.concat(activeToken)));
|
|
275
272
|
}
|
|
276
273
|
if (activeArgsLength <= 0) {
|
|
277
274
|
return; // not subcommand or option & no args exist
|
|
278
275
|
}
|
|
279
276
|
const args = getArgs(subcommand.args);
|
|
280
277
|
if (args.length != 0) {
|
|
281
|
-
return runArg(tokens, args, subcommand, cwd, allOptions, acceptedTokens, false, false);
|
|
278
|
+
return runArg(tokens, args, subcommand, cwd, shell, allOptions, acceptedTokens, false, false);
|
|
282
279
|
}
|
|
283
280
|
// if the subcommand has no args specified, fallback to the subcommand and ignore this item
|
|
284
|
-
return runSubcommand(tokens.slice(1), subcommand, cwd, persistentOptions, acceptedTokens.concat(activeToken));
|
|
281
|
+
return runSubcommand(tokens.slice(1), subcommand, cwd, shell, persistentOptions, acceptedTokens.concat(activeToken));
|
|
285
282
|
};
|
|
@@ -4,7 +4,9 @@ import path from "node:path";
|
|
|
4
4
|
import { runGenerator } from "./generator.js";
|
|
5
5
|
import { runTemplates } from "./template.js";
|
|
6
6
|
import log from "../utils/log.js";
|
|
7
|
-
|
|
7
|
+
import { escapePath } from "./utils.js";
|
|
8
|
+
import { addPathSeparator, getPathDirname, removePathSeparator } from "../utils/shell.js";
|
|
9
|
+
export var SuggestionIcons;
|
|
8
10
|
(function (SuggestionIcons) {
|
|
9
11
|
SuggestionIcons["File"] = "\uD83D\uDCC4";
|
|
10
12
|
SuggestionIcons["Folder"] = "\uD83D\uDCC1";
|
|
@@ -17,10 +19,10 @@ var SuggestionIcons;
|
|
|
17
19
|
SuggestionIcons["Default"] = "\uD83D\uDCC0";
|
|
18
20
|
})(SuggestionIcons || (SuggestionIcons = {}));
|
|
19
21
|
const getIcon = (icon, suggestionType) => {
|
|
20
|
-
//
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
22
|
+
// eslint-disable-next-line no-control-regex
|
|
23
|
+
if (icon && /[^\u0000-\u00ff]/.test(icon)) {
|
|
24
|
+
return icon;
|
|
25
|
+
}
|
|
24
26
|
switch (suggestionType) {
|
|
25
27
|
case "arg":
|
|
26
28
|
return SuggestionIcons.Argument;
|
|
@@ -57,7 +59,7 @@ const toSuggestion = (suggestion, name, type) => {
|
|
|
57
59
|
allNames: suggestion.name instanceof Array ? suggestion.name : [suggestion.name],
|
|
58
60
|
priority: suggestion.priority ?? 50,
|
|
59
61
|
insertValue: suggestion.insertValue,
|
|
60
|
-
|
|
62
|
+
type: suggestion.type,
|
|
61
63
|
};
|
|
62
64
|
};
|
|
63
65
|
function filter(suggestions, filterStrategy, partialCmd, suggestionType) {
|
|
@@ -79,7 +81,7 @@ function filter(suggestions, filterStrategy, partialCmd, suggestionType) {
|
|
|
79
81
|
allNames: s.name,
|
|
80
82
|
priority: s.priority ?? 50,
|
|
81
83
|
insertValue: s.insertValue,
|
|
82
|
-
|
|
84
|
+
type: s.type,
|
|
83
85
|
}
|
|
84
86
|
: undefined;
|
|
85
87
|
}
|
|
@@ -91,7 +93,7 @@ function filter(suggestions, filterStrategy, partialCmd, suggestionType) {
|
|
|
91
93
|
allNames: [s.name],
|
|
92
94
|
priority: s.priority ?? 50,
|
|
93
95
|
insertValue: s.insertValue,
|
|
94
|
-
|
|
96
|
+
type: s.type,
|
|
95
97
|
}
|
|
96
98
|
: undefined;
|
|
97
99
|
})
|
|
@@ -111,7 +113,7 @@ function filter(suggestions, filterStrategy, partialCmd, suggestionType) {
|
|
|
111
113
|
allNames: s.name,
|
|
112
114
|
insertValue: s.insertValue,
|
|
113
115
|
priority: s.priority ?? 50,
|
|
114
|
-
|
|
116
|
+
type: s.type,
|
|
115
117
|
}
|
|
116
118
|
: undefined;
|
|
117
119
|
}
|
|
@@ -123,7 +125,7 @@ function filter(suggestions, filterStrategy, partialCmd, suggestionType) {
|
|
|
123
125
|
allNames: [s.name],
|
|
124
126
|
insertValue: s.insertValue,
|
|
125
127
|
priority: s.priority ?? 50,
|
|
126
|
-
|
|
128
|
+
type: s.type,
|
|
127
129
|
}
|
|
128
130
|
: undefined;
|
|
129
131
|
})
|
|
@@ -153,13 +155,15 @@ const optionSuggestions = (options, acceptedTokens, filterStrategy, partialCmd)
|
|
|
153
155
|
const validOptions = options?.filter((o) => o.exclusiveOn?.every((exclusiveOption) => !usedOptions.has(exclusiveOption)) ?? true);
|
|
154
156
|
return filter(validOptions ?? [], filterStrategy, partialCmd, "option");
|
|
155
157
|
};
|
|
156
|
-
|
|
157
|
-
return
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
158
|
+
function adjustPathSuggestions(suggestions, partialToken, shell) {
|
|
159
|
+
return suggestions.map((s) => {
|
|
160
|
+
const pathy = getPathy(s.type);
|
|
161
|
+
const rawInsertValue = removePathSeparator(s.insertValue ?? s.name ?? "");
|
|
162
|
+
const insertValue = s.type == "folder" ? addPathSeparator(rawInsertValue, shell) : rawInsertValue;
|
|
163
|
+
const partialDir = getPathDirname(partialToken?.token ?? "", shell);
|
|
164
|
+
const fullPath = partialToken?.isPath ? `${partialDir}${insertValue}` : insertValue;
|
|
165
|
+
return pathy ? { ...s, insertValue: escapePath(fullPath, shell), name: removePathSeparator(s.name) } : s;
|
|
166
|
+
});
|
|
163
167
|
}
|
|
164
168
|
const removeAcceptedSuggestions = (suggestions, acceptedTokens) => {
|
|
165
169
|
const seen = new Set(acceptedTokens.map((t) => t.token));
|
|
@@ -179,7 +183,7 @@ const removeDuplicateSuggestion = (suggestions) => {
|
|
|
179
183
|
const removeEmptySuggestion = (suggestions) => {
|
|
180
184
|
return suggestions.filter((s) => s.name.length > 0);
|
|
181
185
|
};
|
|
182
|
-
export const getSubcommandDrivenRecommendation = async (subcommand, persistentOptions, partialToken, argsDepleted, argsFromSubcommand, acceptedTokens, cwd) => {
|
|
186
|
+
export const getSubcommandDrivenRecommendation = async (subcommand, persistentOptions, partialToken, argsDepleted, argsFromSubcommand, acceptedTokens, cwd, shell) => {
|
|
183
187
|
log.debug({ msg: "suggestion point", subcommand, persistentOptions, partialToken, argsDepleted, argsFromSubcommand, acceptedTokens, cwd });
|
|
184
188
|
if (argsDepleted && argsFromSubcommand) {
|
|
185
189
|
return;
|
|
@@ -202,10 +206,10 @@ export const getSubcommandDrivenRecommendation = async (subcommand, persistentOp
|
|
|
202
206
|
suggestions.push(...(await templateSuggestions(activeArg?.template, activeArg?.filterStrategy, partialCmd, cwd)));
|
|
203
207
|
}
|
|
204
208
|
return {
|
|
205
|
-
suggestions: removeDuplicateSuggestion(removeEmptySuggestion(removeAcceptedSuggestions(adjustPathSuggestions(suggestions.sort((a, b) => b.priority - a.priority), partialToken), acceptedTokens))),
|
|
209
|
+
suggestions: removeDuplicateSuggestion(removeEmptySuggestion(removeAcceptedSuggestions(adjustPathSuggestions(suggestions.sort((a, b) => b.priority - a.priority), partialToken, shell), acceptedTokens))),
|
|
206
210
|
};
|
|
207
211
|
};
|
|
208
|
-
export const getArgDrivenRecommendation = async (args, subcommand, persistentOptions, partialToken, acceptedTokens, variadicArgBound, cwd) => {
|
|
212
|
+
export const getArgDrivenRecommendation = async (args, subcommand, persistentOptions, partialToken, acceptedTokens, variadicArgBound, cwd, shell) => {
|
|
209
213
|
let partialCmd = partialToken?.token;
|
|
210
214
|
if (partialToken?.isPath) {
|
|
211
215
|
partialCmd = partialToken.isPathComplete ? "" : path.basename(partialCmd ?? "");
|
|
@@ -222,7 +226,7 @@ export const getArgDrivenRecommendation = async (args, subcommand, persistentOpt
|
|
|
222
226
|
suggestions.push(...optionSuggestions(allOptions, acceptedTokens, activeArg?.filterStrategy, partialCmd));
|
|
223
227
|
}
|
|
224
228
|
return {
|
|
225
|
-
suggestions: removeDuplicateSuggestion(removeEmptySuggestion(removeAcceptedSuggestions(adjustPathSuggestions(suggestions.sort((a, b) => b.priority - a.priority), partialToken), acceptedTokens))),
|
|
229
|
+
suggestions: removeDuplicateSuggestion(removeEmptySuggestion(removeAcceptedSuggestions(adjustPathSuggestions(suggestions.sort((a, b) => b.priority - a.priority), partialToken, shell), acceptedTokens))),
|
|
226
230
|
argumentDescription: activeArg.description ?? activeArg.name,
|
|
227
231
|
};
|
|
228
232
|
};
|
package/build/runtime/utils.js
CHANGED
|
@@ -3,17 +3,84 @@
|
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { spawn } from "node:child_process";
|
|
5
5
|
import fsAsync from "node:fs/promises";
|
|
6
|
-
import {
|
|
6
|
+
import { getPathSeparator, gitBashPath, Shell } from "../utils/shell.js";
|
|
7
7
|
import log from "../utils/log.js";
|
|
8
|
-
|
|
9
|
-
|
|
8
|
+
const getExecutionShell = async () => {
|
|
9
|
+
if (process.platform !== "win32")
|
|
10
|
+
return;
|
|
11
|
+
try {
|
|
12
|
+
return await gitBashPath();
|
|
13
|
+
}
|
|
14
|
+
catch (e) {
|
|
15
|
+
log.debug({ msg: "failed to load posix shell for windows child_process.spawn, some generators might fail", error: e });
|
|
16
|
+
}
|
|
17
|
+
};
|
|
18
|
+
const bashSpecialCharacters = /[&|<>\s]/g;
|
|
19
|
+
// escape whitespace & special characters in an argument when not quoted
|
|
20
|
+
const shouldEscapeArg = (arg) => {
|
|
21
|
+
const hasSpecialCharacter = bashSpecialCharacters.test(arg);
|
|
22
|
+
const isSingleCharacter = arg.length === 1;
|
|
23
|
+
return hasSpecialCharacter && !isSingleCharacter && !isQuoted(arg, `"`);
|
|
24
|
+
};
|
|
25
|
+
/* based on libuv process.c used by nodejs, only quotes are escaped for shells. if using git bash need to escape whitespace & special characters in an argument */
|
|
26
|
+
const escapeArgs = (shell, args) => {
|
|
27
|
+
// only escape args for git bash
|
|
28
|
+
if (process.platform !== "win32" || shell == undefined)
|
|
29
|
+
return args;
|
|
30
|
+
return args.map((arg) => (shouldEscapeArg(arg) ? `"${arg.replaceAll('"', '\\"')}"` : arg));
|
|
31
|
+
};
|
|
32
|
+
const isQuoted = (value, quoteChar) => (value?.startsWith(quoteChar) && value?.endsWith(quoteChar)) ?? false;
|
|
33
|
+
const quoteString = (value, quoteChar) => {
|
|
34
|
+
if (isQuoted(value, quoteChar))
|
|
35
|
+
return value;
|
|
36
|
+
const escapedValue = value.replaceAll(`\\${quoteChar}`, quoteChar).replaceAll(quoteChar, `\\${quoteChar}`);
|
|
37
|
+
return `${quoteChar}${escapedValue}${quoteChar}`;
|
|
38
|
+
};
|
|
39
|
+
const needsQuoted = (value, quoteChar) => isQuoted(value, quoteChar) || value.includes(" ");
|
|
40
|
+
const getShellQuoteChar = (shell) => {
|
|
41
|
+
switch (shell) {
|
|
42
|
+
case Shell.Zsh:
|
|
43
|
+
case Shell.Bash:
|
|
44
|
+
case Shell.Fish:
|
|
45
|
+
return `"`;
|
|
46
|
+
case Shell.Xonsh:
|
|
47
|
+
return `'`;
|
|
48
|
+
case Shell.Nushell:
|
|
49
|
+
return "`";
|
|
50
|
+
case Shell.Pwsh:
|
|
51
|
+
case Shell.Powershell:
|
|
52
|
+
return `'`;
|
|
53
|
+
case Shell.Cmd:
|
|
54
|
+
return `"`;
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
export const getShellWhitespaceEscapeChar = (shell) => {
|
|
58
|
+
switch (shell) {
|
|
59
|
+
case Shell.Zsh:
|
|
60
|
+
case Shell.Bash:
|
|
61
|
+
case Shell.Fish:
|
|
62
|
+
case Shell.Xonsh:
|
|
63
|
+
case Shell.Nushell:
|
|
64
|
+
return "\\";
|
|
65
|
+
case Shell.Pwsh:
|
|
66
|
+
case Shell.Powershell:
|
|
67
|
+
return "`";
|
|
68
|
+
case Shell.Cmd:
|
|
69
|
+
return "^";
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
export const escapePath = (value, shell) => value != null && needsQuoted(value, getShellQuoteChar(shell)) ? quoteString(value, getShellQuoteChar(shell)) : value;
|
|
73
|
+
export const buildExecuteShellCommand = async (timeout) => async ({ command, env, args, cwd }) => {
|
|
74
|
+
const executionShell = await getExecutionShell();
|
|
75
|
+
const escapedArgs = escapeArgs(executionShell, args);
|
|
76
|
+
const child = spawn(command, escapedArgs, { cwd, env: { ...process.env, ...env, ISTERM: "1" }, shell: executionShell });
|
|
10
77
|
setTimeout(() => child.kill("SIGKILL"), timeout);
|
|
11
78
|
let stdout = "";
|
|
12
79
|
let stderr = "";
|
|
13
80
|
child.stdout.on("data", (data) => (stdout += data));
|
|
14
81
|
child.stderr.on("data", (data) => (stderr += data));
|
|
15
82
|
child.on("error", (err) => {
|
|
16
|
-
log.debug({ msg: "shell command failed", e: err.message });
|
|
83
|
+
log.debug({ msg: "shell command failed", command, args, e: err.message });
|
|
17
84
|
});
|
|
18
85
|
return new Promise((resolve) => {
|
|
19
86
|
child.on("close", (code) => {
|
|
@@ -29,8 +96,9 @@ export const resolveCwd = async (cmdToken, cwd, shell) => {
|
|
|
29
96
|
if (cmdToken == null)
|
|
30
97
|
return { cwd, pathy: false, complete: false };
|
|
31
98
|
const { token: rawToken, isQuoted } = cmdToken;
|
|
32
|
-
const
|
|
33
|
-
const
|
|
99
|
+
const escapedToken = !isQuoted ? rawToken.replaceAll(" ", "\\ ") : rawToken;
|
|
100
|
+
const token = escapedToken;
|
|
101
|
+
const sep = getPathSeparator(shell);
|
|
34
102
|
if (!token.includes(sep))
|
|
35
103
|
return { cwd, pathy: false, complete: false };
|
|
36
104
|
const resolvedCwd = path.isAbsolute(token) ? token : path.join(cwd, token);
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
// Copyright (c) Microsoft Corporation.
|
|
2
|
+
// Licensed under the MIT License.
|
|
3
|
+
import chalk from "chalk";
|
|
4
|
+
import { checkLegacyConfigs, checkShellConfigs } from "../utils/shell.js";
|
|
5
|
+
export const render = async () => {
|
|
6
|
+
let errors = 0;
|
|
7
|
+
errors += await renderLegacyConfigIssues();
|
|
8
|
+
errors += renderShellConfigIssues();
|
|
9
|
+
process.exit(errors);
|
|
10
|
+
};
|
|
11
|
+
const renderLegacyConfigIssues = async () => {
|
|
12
|
+
const shellsWithLegacyConfigs = await checkLegacyConfigs();
|
|
13
|
+
if (shellsWithLegacyConfigs.length > 0) {
|
|
14
|
+
process.stderr.write(chalk.red("•") + chalk.bold(" detected legacy configurations\n"));
|
|
15
|
+
process.stderr.write(" the following shells have legacy configurations:\n");
|
|
16
|
+
shellsWithLegacyConfigs.forEach((shell) => {
|
|
17
|
+
process.stderr.write(chalk.red(" - ") + shell + "\n");
|
|
18
|
+
});
|
|
19
|
+
process.stderr.write(chalk.yellow(" remove any inshellisense configurations from your shell profile and re-add them following the instructions in the README\n"));
|
|
20
|
+
return 1;
|
|
21
|
+
}
|
|
22
|
+
else {
|
|
23
|
+
process.stdout.write(chalk.green("✓") + " no legacy configurations found\n");
|
|
24
|
+
}
|
|
25
|
+
return 0;
|
|
26
|
+
};
|
|
27
|
+
const renderShellConfigIssues = () => {
|
|
28
|
+
const shellsWithoutConfigs = checkShellConfigs();
|
|
29
|
+
if (shellsWithoutConfigs.length > 0) {
|
|
30
|
+
process.stderr.write(chalk.red("•") + " the following shells do not have configurations:\n");
|
|
31
|
+
shellsWithoutConfigs.forEach((shell) => {
|
|
32
|
+
process.stderr.write(chalk.red(" - ") + shell + "\n");
|
|
33
|
+
});
|
|
34
|
+
process.stderr.write(chalk.yellow(" run " + chalk.underline(chalk.cyan("is init --generate-full-configs")) + " to generate new configurations\n"));
|
|
35
|
+
return 1;
|
|
36
|
+
}
|
|
37
|
+
else {
|
|
38
|
+
process.stdout.write(chalk.green("✓") + " all shells have configurations\n");
|
|
39
|
+
}
|
|
40
|
+
return 0;
|
|
41
|
+
};
|
package/build/utils/config.js
CHANGED
|
@@ -125,9 +125,8 @@ export const loadConfig = async (program) => {
|
|
|
125
125
|
});
|
|
126
126
|
globalConfig.specs = { path: [`${os.homedir()}/.fig/autocomplete/build`, ...(globalConfig.specs?.path ?? [])] };
|
|
127
127
|
};
|
|
128
|
-
export const deleteCacheFolder =
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
fs.rmSync(cliConfigPath, { recursive: true });
|
|
128
|
+
export const deleteCacheFolder = () => {
|
|
129
|
+
if (fs.existsSync(cachePath)) {
|
|
130
|
+
fs.rmSync(cachePath, { recursive: true });
|
|
132
131
|
}
|
|
133
132
|
};
|
package/build/utils/log.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
// Copyright (c) Microsoft Corporation.
|
|
2
2
|
// Licensed under the MIT License.
|
|
3
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
3
4
|
import os from "node:os";
|
|
4
5
|
import path from "node:path";
|
|
5
6
|
import fs from "node:fs";
|
|
@@ -23,8 +24,16 @@ const debug = (content) => {
|
|
|
23
24
|
}
|
|
24
25
|
});
|
|
25
26
|
};
|
|
27
|
+
const getLogFunction = (level) => (...data) => debug({ msg: `console.${level}`, data: data.toString() });
|
|
28
|
+
const logConsole = {
|
|
29
|
+
...console,
|
|
30
|
+
log: getLogFunction("log"),
|
|
31
|
+
error: getLogFunction("error"),
|
|
32
|
+
};
|
|
33
|
+
// eslint-disable-next-line no-global-assign
|
|
34
|
+
const overrideConsole = () => (console = logConsole);
|
|
26
35
|
export const enable = async () => {
|
|
27
36
|
await reset();
|
|
28
37
|
logEnabled = true;
|
|
29
38
|
};
|
|
30
|
-
export default { reset, debug, enable };
|
|
39
|
+
export default { reset, debug, enable, overrideConsole };
|
package/build/utils/shell.js
CHANGED
|
@@ -8,6 +8,10 @@ import fs from "node:fs";
|
|
|
8
8
|
import url from "node:url";
|
|
9
9
|
import os from "node:os";
|
|
10
10
|
import fsAsync from "node:fs/promises";
|
|
11
|
+
import util from "node:util";
|
|
12
|
+
import childProcess from "node:child_process";
|
|
13
|
+
import log from "./log.js";
|
|
14
|
+
const exec = util.promisify(childProcess.exec);
|
|
11
15
|
export var Shell;
|
|
12
16
|
(function (Shell) {
|
|
13
17
|
Shell["Bash"] = "bash";
|
|
@@ -34,6 +38,79 @@ export const aliasSupportedShells = [Shell.Bash, Shell.Zsh];
|
|
|
34
38
|
export const userZdotdir = process.env?.ZDOTDIR ?? os.homedir() ?? `~`;
|
|
35
39
|
export const zdotdir = path.join(os.tmpdir(), `is-zsh`);
|
|
36
40
|
const configFolder = ".inshellisense";
|
|
41
|
+
export const checkShellConfigs = () => {
|
|
42
|
+
const shellsWithoutConfigs = [];
|
|
43
|
+
const configFolderPath = path.join(os.homedir(), configFolder);
|
|
44
|
+
for (const shell of supportedShells) {
|
|
45
|
+
const shellConfigName = getShellConfigName(shell);
|
|
46
|
+
if (shellConfigName == null)
|
|
47
|
+
continue;
|
|
48
|
+
if (!fs.existsSync(path.join(configFolderPath, shell, shellConfigName))) {
|
|
49
|
+
shellsWithoutConfigs.push(shell);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return shellsWithoutConfigs;
|
|
53
|
+
};
|
|
54
|
+
export const checkLegacyConfigs = async () => {
|
|
55
|
+
const shellsWithLegacyConfig = [];
|
|
56
|
+
for (const shell of supportedShells) {
|
|
57
|
+
const profilePath = await getProfilePath(shell);
|
|
58
|
+
if (profilePath != null && fs.existsSync(profilePath)) {
|
|
59
|
+
const profile = await fsAsync.readFile(profilePath, "utf8");
|
|
60
|
+
if (profile.includes("inshellisense shell plugin")) {
|
|
61
|
+
shellsWithLegacyConfig.push(shell);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return shellsWithLegacyConfig;
|
|
66
|
+
};
|
|
67
|
+
const getProfilePath = async (shell) => {
|
|
68
|
+
switch (shell) {
|
|
69
|
+
case Shell.Bash:
|
|
70
|
+
return path.join(os.homedir(), ".bashrc");
|
|
71
|
+
case Shell.Powershell:
|
|
72
|
+
return (await exec(`echo $profile`, { shell })).stdout.trim();
|
|
73
|
+
case Shell.Pwsh:
|
|
74
|
+
return (await exec(`echo $profile`, { shell })).stdout.trim();
|
|
75
|
+
case Shell.Zsh:
|
|
76
|
+
return path.join(os.homedir(), ".zshrc");
|
|
77
|
+
case Shell.Fish:
|
|
78
|
+
return path.join(os.homedir(), ".config", "fish", "config.fish");
|
|
79
|
+
case Shell.Xonsh:
|
|
80
|
+
return path.join(os.homedir(), ".xonshrc");
|
|
81
|
+
case Shell.Nushell:
|
|
82
|
+
return (await exec(`echo $nu.env-path`, { shell })).stdout.trim();
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
export const createShellConfigs = async () => {
|
|
86
|
+
const configFolderPath = path.join(os.homedir(), configFolder);
|
|
87
|
+
for (const shell of supportedShells) {
|
|
88
|
+
const shellConfigName = getShellConfigName(shell);
|
|
89
|
+
if (shellConfigName == null)
|
|
90
|
+
continue;
|
|
91
|
+
await fsAsync.mkdir(path.join(configFolderPath, shell), { recursive: true });
|
|
92
|
+
await fsAsync.writeFile(path.join(configFolderPath, shell, shellConfigName), getShellConfig(shell));
|
|
93
|
+
}
|
|
94
|
+
};
|
|
95
|
+
const getShellConfigName = (shell) => {
|
|
96
|
+
switch (shell) {
|
|
97
|
+
case Shell.Bash:
|
|
98
|
+
return "init.sh";
|
|
99
|
+
case Shell.Powershell:
|
|
100
|
+
case Shell.Pwsh:
|
|
101
|
+
return "init.ps1";
|
|
102
|
+
case Shell.Zsh:
|
|
103
|
+
return "init.zsh";
|
|
104
|
+
case Shell.Fish:
|
|
105
|
+
return "init.fish";
|
|
106
|
+
case Shell.Xonsh:
|
|
107
|
+
return "init.xsh";
|
|
108
|
+
case Shell.Nushell:
|
|
109
|
+
return "init.nu";
|
|
110
|
+
default:
|
|
111
|
+
return undefined;
|
|
112
|
+
}
|
|
113
|
+
};
|
|
37
114
|
export const setupBashPreExec = async () => {
|
|
38
115
|
const shellFolderPath = path.join(path.dirname(url.fileURLToPath(import.meta.url)), "..", "..", "shell");
|
|
39
116
|
const globalConfigPath = path.join(os.homedir(), configFolder);
|
|
@@ -49,6 +126,14 @@ export const setupZshDotfiles = async () => {
|
|
|
49
126
|
await fsAsync.cp(path.join(shellFolderPath, "shellIntegration-env.zsh"), path.join(zdotdir, ".zshenv"));
|
|
50
127
|
await fsAsync.cp(path.join(shellFolderPath, "shellIntegration-login.zsh"), path.join(zdotdir, ".zlogin"));
|
|
51
128
|
};
|
|
129
|
+
const findParentProcess = async () => {
|
|
130
|
+
try {
|
|
131
|
+
return (await find("pid", process.ppid)).at(0);
|
|
132
|
+
}
|
|
133
|
+
catch (e) {
|
|
134
|
+
log.debug({ msg: `error finding parent process: ${e}` });
|
|
135
|
+
}
|
|
136
|
+
};
|
|
52
137
|
export const inferShell = async () => {
|
|
53
138
|
// try getting shell from shell specific env variables
|
|
54
139
|
if (process.env.NU_VERSION != null) {
|
|
@@ -77,7 +162,7 @@ export const inferShell = async () => {
|
|
|
77
162
|
/* empty */
|
|
78
163
|
}
|
|
79
164
|
// try getting shell from parent process
|
|
80
|
-
const processResult =
|
|
165
|
+
const processResult = await findParentProcess();
|
|
81
166
|
const name = processResult?.name;
|
|
82
167
|
return name != null ? supportedShells.find((shell) => name.includes(shell)) : undefined;
|
|
83
168
|
};
|
|
@@ -116,13 +201,43 @@ const getGitBashPaths = async () => {
|
|
|
116
201
|
return gitBashPaths;
|
|
117
202
|
};
|
|
118
203
|
export const getBackspaceSequence = (press, shell) => shell === Shell.Pwsh || shell === Shell.Powershell || shell === Shell.Cmd || shell === Shell.Nushell ? "\u007F" : press[1].sequence;
|
|
119
|
-
export const
|
|
204
|
+
export const getPathSeparator = (shell) => (shell == Shell.Bash || shell == Shell.Xonsh || shell == Shell.Nushell ? "/" : path.sep);
|
|
205
|
+
export const removePathSeparator = (dir) => {
|
|
206
|
+
return dir.endsWith("/") || dir.endsWith("\\") ? dir.slice(0, -1) : dir;
|
|
207
|
+
};
|
|
208
|
+
export const addPathSeparator = (dir, shell) => {
|
|
209
|
+
const pathSep = getPathSeparator(shell);
|
|
210
|
+
return dir.endsWith(pathSep) ? dir : dir + pathSep;
|
|
211
|
+
};
|
|
212
|
+
export const getPathDirname = (dir, shell) => {
|
|
213
|
+
const pathSep = getPathSeparator(shell);
|
|
214
|
+
return dir.endsWith(pathSep) || path.dirname(dir) == "." ? dir : addPathSeparator(path.dirname(dir), shell);
|
|
215
|
+
};
|
|
120
216
|
// nu fully re-writes the prompt every keystroke resulting in duplicate start/end sequences on the same line
|
|
121
217
|
export const getShellPromptRewrites = (shell) => shell == Shell.Nushell;
|
|
218
|
+
export const getShellSourceCommand = (shell) => {
|
|
219
|
+
switch (shell) {
|
|
220
|
+
case Shell.Bash:
|
|
221
|
+
return `[ -f ~/.inshellisense/bash/init.sh ] && source ~/.inshellisense/bash/init.sh`;
|
|
222
|
+
case Shell.Powershell:
|
|
223
|
+
return `if ( Test-Path '~/.inshellisense/powershell/init.ps1' -PathType Leaf ) { . ~/.inshellisense/powershell/init.ps1 } `;
|
|
224
|
+
case Shell.Pwsh:
|
|
225
|
+
return `if ( Test-Path '~/.inshellisense/pwsh/init.ps1' -PathType Leaf ) { . ~/.inshellisense/pwsh/init.ps1 } `;
|
|
226
|
+
case Shell.Zsh:
|
|
227
|
+
return `[[ -f ~/.inshellisense/zsh/init.zsh ]] && source ~/.inshellisense/zsh/init.zsh`;
|
|
228
|
+
case Shell.Fish:
|
|
229
|
+
return `test -f ~/.inshellisense/fish/init.fish && source ~/.inshellisense/fish/init.fish`;
|
|
230
|
+
case Shell.Xonsh:
|
|
231
|
+
return `p"~/.inshellisense/xonsh/init.xsh".exists() && source "~/.inshellisense/xonsh/init.xsh"`;
|
|
232
|
+
case Shell.Nushell:
|
|
233
|
+
return `if ( '~/.inshellisense/nu/init.nu' | path exists ) { source ~/.inshellisense/nu/init.nu } `;
|
|
234
|
+
}
|
|
235
|
+
return "";
|
|
236
|
+
};
|
|
122
237
|
export const getShellConfig = (shell) => {
|
|
123
238
|
switch (shell) {
|
|
124
239
|
case Shell.Zsh:
|
|
125
|
-
return `if [[ -z "\${ISTERM}" && $- = *i* && $- != *c* ]]; then
|
|
240
|
+
return `if [[ -z "\${ISTERM}" && $- = *i* && $- != *c* && -z "\${VSCODE_RESOLVING_ENVIRONMENT}" ]]; then
|
|
126
241
|
if [[ -o login ]]; then
|
|
127
242
|
is -s zsh --login ; exit
|
|
128
243
|
else
|
|
@@ -130,7 +245,7 @@ export const getShellConfig = (shell) => {
|
|
|
130
245
|
fi
|
|
131
246
|
fi`;
|
|
132
247
|
case Shell.Bash:
|
|
133
|
-
return `if [[ -z "\${ISTERM}" && $- = *i* && $- != *c* ]]; then
|
|
248
|
+
return `if [[ -z "\${ISTERM}" && $- = *i* && $- != *c* && -z "\${VSCODE_RESOLVING_ENVIRONMENT}" ]]; then
|
|
134
249
|
shopt -q login_shell
|
|
135
250
|
login_shell=$?
|
|
136
251
|
if [ $login_shell -eq 0 ]; then
|
|
@@ -144,12 +259,12 @@ fi`;
|
|
|
144
259
|
return `$__IsCommandFlag = ([Environment]::GetCommandLineArgs() | ForEach-Object { $_.contains("-Command") }) -contains $true
|
|
145
260
|
$__IsNoExitFlag = ([Environment]::GetCommandLineArgs() | ForEach-Object { $_.contains("-NoExit") }) -contains $true
|
|
146
261
|
$__IsInteractive = -not $__IsCommandFlag -or ($__IsCommandFlag -and $__IsNoExitFlag)
|
|
147
|
-
if ([string]::IsNullOrEmpty($env:ISTERM) -and [Environment]::UserInteractive -and $__IsInteractive) {
|
|
262
|
+
if ([string]::IsNullOrEmpty($env:ISTERM) -and [Environment]::UserInteractive -and $__IsInteractive -and [string]::IsNullOrEmpty($env:VSCODE_RESOLVING_ENVIRONMENT)) {
|
|
148
263
|
is -s ${shell}
|
|
149
264
|
Stop-Process -Id $pid
|
|
150
265
|
}`;
|
|
151
266
|
case Shell.Fish:
|
|
152
|
-
return `if test -z "$ISTERM" && status --is-interactive
|
|
267
|
+
return `if test -z "$ISTERM" && status --is-interactive && test -z "$VSCODE_RESOLVING_ENVIRONMENT"
|
|
153
268
|
if status --is-login
|
|
154
269
|
is -s fish --login ; kill %self
|
|
155
270
|
else
|
|
@@ -157,13 +272,13 @@ if ([string]::IsNullOrEmpty($env:ISTERM) -and [Environment]::UserInteractive -an
|
|
|
157
272
|
end
|
|
158
273
|
end`;
|
|
159
274
|
case Shell.Xonsh:
|
|
160
|
-
return `if 'ISTERM' not in \${...} and $XONSH_INTERACTIVE:
|
|
275
|
+
return `if 'ISTERM' not in \${...} and $XONSH_INTERACTIVE and 'VSCODE_RESOLVING_ENVIRONMENT' not in \${...}:
|
|
161
276
|
if $XONSH_LOGIN:
|
|
162
277
|
is -s xonsh --login ; exit
|
|
163
278
|
else:
|
|
164
279
|
is -s xonsh ; exit`;
|
|
165
280
|
case Shell.Nushell:
|
|
166
|
-
return `if "ISTERM" not-in $env and $nu.is-interactive {
|
|
281
|
+
return `if "ISTERM" not-in $env and $nu.is-interactive and "VSCODE_RESOLVING_ENVIRONMENT" not-in $env {
|
|
167
282
|
if $nu.is-login { is -s nu --login ; exit } else { is -s nu ; exit }
|
|
168
283
|
}`;
|
|
169
284
|
}
|