@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.
@@ -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
- let charactersToDrop = lastCommand?.complete ? 0 : lastCommand?.token.length ?? 0;
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
- var SuggestionIcons;
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
- // TODO: enable fig icons once spacing is better
21
- // if (icon && /[^\u0000-\u00ff]/.test(icon)) {
22
- // return icon;
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
- pathy: getPathy(suggestion.type),
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
- pathy: getPathy(s.type),
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
- pathy: getPathy(s.type),
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
- pathy: getPathy(s.type),
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
- pathy: getPathy(s.type),
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
- const getEscapedPath = (value) => {
157
- return value?.replaceAll(" ", "\\ ");
158
- };
159
- function adjustPathSuggestions(suggestions, partialToken) {
160
- if (partialToken == null || partialToken.isQuoted)
161
- return suggestions;
162
- return suggestions.map((s) => s.pathy ? { ...s, insertValue: getEscapedPath(s.insertValue), name: s.insertValue == null ? getEscapedPath(s.name) : s.name } : s);
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
  };
@@ -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 { getPathSeperator } from "../utils/shell.js";
6
+ import { getPathSeparator, gitBashPath, Shell } from "../utils/shell.js";
7
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: { ...env, ISTERM: "1" } });
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 token = !isQuoted ? rawToken.replaceAll("\\ ", " ") : rawToken;
33
- const sep = getPathSeperator(shell);
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
+ };
@@ -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 = async () => {
129
- const cliConfigPath = path.join(os.homedir(), cachePath);
130
- if (fs.existsSync(cliConfigPath)) {
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
  };
@@ -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 };
@@ -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 = (await find("pid", process.ppid)).at(0);
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 getPathSeperator = (shell) => (shell == Shell.Bash || shell == Shell.Xonsh || shell == Shell.Nushell ? "/" : path.sep);
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
  }