@microsoft/inshellisense 0.0.1-rc.16 → 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/README.md CHANGED
@@ -18,7 +18,7 @@ npm install -g @microsoft/inshellisense
18
18
 
19
19
  ### Quickstart
20
20
 
21
- After completing the installation, you can run `is` to start the autocomplete session for your desired shell. Additionally, inshellisense is also aliased under `inshellisense` after installation.
21
+ After completing the installation, run `is doctor` to verify your installation was successful. You can run `is` to start the autocomplete session for your desired shell. Additionally, inshellisense is also aliased under `inshellisense` after installation.
22
22
 
23
23
  ### Shell Plugin
24
24
 
@@ -35,19 +35,20 @@ is init zsh >> ~/.zshrc
35
35
  is init fish >> ~/.config/fish/config.fish
36
36
 
37
37
  # pwsh
38
- is init pwsh >> $profile
38
+ is init pwsh | Add-Content $profile
39
39
 
40
40
  # powershell
41
- is init powershell >> $profile
41
+ is init powershell | Add-Content $profile
42
42
 
43
43
  # xonsh
44
44
  is init xonsh >> ~/.xonshrc
45
45
 
46
46
  # nushell
47
- is init nu >> $nu.env-path
47
+ is init nu | save $nu.env-path --append
48
48
  ```
49
49
 
50
- When updating your shell configuration in the future, make sure the inshellisense plugin is the last command in the file.
50
+ > [!NOTE]
51
+ > When updating your shell configuration in the future, make sure the inshellisense plugin is the last command in the file. Including commands after it may break the configuration (ex. initializing your shell plugin manager after the inshellisense plugin)
51
52
 
52
53
  ### Usage
53
54
 
@@ -0,0 +1,10 @@
1
+ // Copyright (c) Microsoft Corporation.
2
+ // Licensed under the MIT License.
3
+ import os from "node:os";
4
+ import { getSuggestions } from "../runtime/runtime.js";
5
+ import { Shell } from "../utils/shell.js";
6
+ export const action = async (input) => {
7
+ const suggestions = await getSuggestions(input, process.cwd(), os.platform() === "win32" ? Shell.Cmd : Shell.Bash);
8
+ process.stdout.write(JSON.stringify(suggestions));
9
+ process.exit(0);
10
+ };
@@ -0,0 +1,11 @@
1
+ // Copyright (c) Microsoft Corporation.
2
+ // Licensed under the MIT License.
3
+ import { Command } from "commander";
4
+ import { render } from "../ui/ui-doctor.js";
5
+ const action = async () => {
6
+ await render();
7
+ };
8
+ const cmd = new Command("doctor");
9
+ cmd.description(`checks the health of this inshellisense installation`);
10
+ cmd.action(action);
11
+ export default cmd;
@@ -1,18 +1,24 @@
1
1
  // Copyright (c) Microsoft Corporation.
2
2
  // Licensed under the MIT License.
3
3
  import { Command } from "commander";
4
- import { initSupportedShells as shells, getShellConfig } from "../utils/shell.js";
4
+ import { createShellConfigs, initSupportedShells as shells, getShellSourceCommand } from "../utils/shell.js";
5
5
  const supportedShells = shells.join(", ");
6
- const action = (program) => async (shell) => {
6
+ const action = (program) => async (shell, options) => {
7
+ if (options.generateFullConfigs) {
8
+ await createShellConfigs();
9
+ return;
10
+ }
11
+ if (shell == null)
12
+ program.error(`Shell is required, supported shells: ${supportedShells}`, { exitCode: 1 });
7
13
  if (!shells.map((s) => s.valueOf()).includes(shell)) {
8
14
  program.error(`Unsupported shell: '${shell}', supported shells: ${supportedShells}`, { exitCode: 1 });
9
15
  }
10
- const config = getShellConfig(shell);
11
- process.stdout.write(`\n\n# ---------------- inshellisense shell plugin ----------------\n${config}`);
12
- process.exit(0);
16
+ const config = getShellSourceCommand(shell);
17
+ process.stdout.write(`\n\n${config}`);
13
18
  };
14
19
  const cmd = new Command("init");
15
- cmd.description(`generates shell configurations for the provided shell`);
16
- cmd.argument("<shell>", `shell to generate configuration for, supported shells: ${supportedShells}`);
20
+ cmd.description(`generates shell configurations and prints the source command for the specified shell`);
21
+ cmd.argument("[shell]", `shell to generate for, supported shells: ${supportedShells}`);
22
+ cmd.option("--generate-full-configs");
17
23
  cmd.action(action(cmd));
18
24
  export default cmd;
@@ -0,0 +1,38 @@
1
+ // Copyright (c) Microsoft Corporation.
2
+ // Licensed under the MIT License.
3
+ import { render, renderConfirmation } from "../ui/ui-root.js";
4
+ import { Shell, supportedShells as shells, setupZshDotfiles, setupBashPreExec } from "../utils/shell.js";
5
+ import { inferShell } from "../utils/shell.js";
6
+ import { loadConfig } from "../utils/config.js";
7
+ import log from "../utils/log.js";
8
+ import { loadAliases } from "../runtime/alias.js";
9
+ import { loadLocalSpecsSet } from "../runtime/runtime.js";
10
+ import { program } from "./root.js";
11
+ export const supportedShells = shells.join(", ");
12
+ export const action = async (options) => {
13
+ const inISTerm = process.env.ISTERM === "1";
14
+ if (options.check || inISTerm) {
15
+ process.stdout.write(renderConfirmation(inISTerm));
16
+ process.exit(0);
17
+ }
18
+ if (options.verbose)
19
+ await log.enable();
20
+ await loadConfig(program);
21
+ await loadLocalSpecsSet();
22
+ const shell = options.shell ?? (await inferShell());
23
+ if (shell == null) {
24
+ program.error(`Unable to identify shell, use the -s/--shell option to provide your shell`, { exitCode: 1 });
25
+ return;
26
+ }
27
+ if (!shells.map((s) => s.valueOf()).includes(shell)) {
28
+ program.error(`Unsupported shell: '${shell}', supported shells: ${supportedShells}`, { exitCode: 1 });
29
+ }
30
+ if (shell == Shell.Zsh) {
31
+ await setupZshDotfiles();
32
+ }
33
+ else if (shell == Shell.Bash) {
34
+ await setupBashPreExec();
35
+ }
36
+ await loadAliases(shell);
37
+ await render(shell, options.test ?? false, options.login ?? false);
38
+ };
@@ -18,6 +18,7 @@ export const action = (program) => async (options) => {
18
18
  await log.enable();
19
19
  await loadConfig(program);
20
20
  await loadLocalSpecsSet();
21
+ log.overrideConsole();
21
22
  const shell = options.shell ?? (await inferShell());
22
23
  if (shell == null) {
23
24
  program.error(`Unable to identify shell, use the -s/--shell option to provide your shell`, { exitCode: 1 });
package/build/index.js CHANGED
@@ -7,6 +7,7 @@ import complete from "./commands/complete.js";
7
7
  import uninstall from "./commands/uninstall.js";
8
8
  import init from "./commands/init.js";
9
9
  import specs from "./commands/specs/root.js";
10
+ import doctor from "./commands/doctor.js";
10
11
  import { action, supportedShells } from "./commands/root.js";
11
12
  import { getVersion } from "./utils/version.js";
12
13
  const program = new Command();
@@ -31,4 +32,5 @@ program.addCommand(complete);
31
32
  program.addCommand(uninstall);
32
33
  program.addCommand(init);
33
34
  program.addCommand(specs);
35
+ program.addCommand(doctor);
34
36
  program.parse();
@@ -7,7 +7,7 @@ import { buildExecuteShellCommand } from "./utils.js";
7
7
  import os from "node:os";
8
8
  const loadedAliases = {};
9
9
  const platform = os.platform();
10
- const executeShellCommand = buildExecuteShellCommand(5000);
10
+ const executeShellCommand = await buildExecuteShellCommand(5000);
11
11
  const loadBashAliases = async () => {
12
12
  const shellTarget = platform == "win32" ? await gitBashPath() : Shell.Bash;
13
13
  const { stdout, stderr, status } = await executeShellCommand({ command: shellTarget, args: ["-i", "-c", "alias"], cwd: process.cwd() });
@@ -20,7 +20,7 @@ const loadBashAliases = async () => {
20
20
  .split("\n")
21
21
  .forEach((line) => {
22
22
  const [alias, ...commandSegments] = line.replace("alias ", "").replaceAll("'\\''", "'").split("=");
23
- loadedAliases[alias] = parseCommand(commandSegments.join("=").slice(1, -1) + " ");
23
+ loadedAliases[alias] = parseCommand(commandSegments.join("=").slice(1, -1) + " ", Shell.Bash);
24
24
  });
25
25
  };
26
26
  const loadZshAliases = async () => {
@@ -34,7 +34,7 @@ const loadZshAliases = async () => {
34
34
  .split("\n")
35
35
  .forEach((line) => {
36
36
  const [alias, ...commandSegments] = line.replaceAll("'\\''", "'").split("=");
37
- loadedAliases[alias] = parseCommand(commandSegments.join("=").slice(1, -1) + " ");
37
+ loadedAliases[alias] = parseCommand(commandSegments.join("=").slice(1, -1) + " ", Shell.Zsh);
38
38
  });
39
39
  };
40
40
  export const loadAliases = async (shell) => {
@@ -17,7 +17,7 @@ const getGeneratorContext = (cwd) => {
17
17
  export const runGenerator = async (generator, tokens, cwd) => {
18
18
  // TODO: support trigger
19
19
  const { script, postProcess, scriptTimeout, splitOn, custom, template, filterTemplateSuggestions } = generator;
20
- const executeShellCommand = buildExecuteShellCommand(scriptTimeout ?? 5000);
20
+ const executeShellCommand = await buildExecuteShellCommand(scriptTimeout ?? 5000);
21
21
  const suggestions = [];
22
22
  try {
23
23
  if (script) {
@@ -1,19 +1,48 @@
1
1
  // Copyright (c) Microsoft Corporation.
2
2
  // Licensed under the MIT License.
3
+ import wcwidth from "wcwidth";
4
+ import { getShellWhitespaceEscapeChar } from "./utils.js";
3
5
  const cmdDelim = /(\|\|)|(&&)|(;)|(\|)/;
4
6
  const spaceRegex = /\s/;
5
- export const parseCommand = (command) => {
7
+ export const parseCommand = (command, shell) => {
6
8
  const lastCommand = command.split(cmdDelim).at(-1)?.trimStart();
7
- return lastCommand ? lex(lastCommand) : [];
9
+ const tokens = lastCommand ? lex(lastCommand, shell) : [];
10
+ return sanitizeTokens(tokens, shell);
8
11
  };
9
- const lex = (command) => {
12
+ const sanitizeTokens = (cmdTokens, shell) => unwrapQuotedTokens(unescapeSpaceTokens(cmdTokens, shell), shell);
13
+ // remove escapes around spaces
14
+ const unescapeSpaceTokens = (cmdTokens, shell) => {
15
+ const escapeChar = getShellWhitespaceEscapeChar(shell);
16
+ return cmdTokens.map((cmdToken) => {
17
+ const { token, isQuoted } = cmdToken;
18
+ if (!isQuoted && token.includes(`${escapeChar} `)) {
19
+ return { ...cmdToken, token: token.replaceAll(`${escapeChar} `, " ") };
20
+ }
21
+ return cmdToken;
22
+ });
23
+ };
24
+ // need to unwrap tokens that are quoted with content after the quotes like `"hello"world`
25
+ const unwrapQuotedTokens = (cmdTokens, shell) => {
26
+ const escapeChar = getShellWhitespaceEscapeChar(shell);
27
+ return cmdTokens.map((cmdToken) => {
28
+ const { token, isQuoteContinued } = cmdToken;
29
+ if (isQuoteContinued) {
30
+ const quoteChar = token[0];
31
+ const unquotedToken = token.replaceAll(`${escapeChar}${quoteChar}`, "\u001B").replaceAll(quoteChar, "").replaceAll("\u001B", quoteChar);
32
+ return { ...cmdToken, token: unquotedToken };
33
+ }
34
+ return cmdToken;
35
+ });
36
+ };
37
+ const lex = (command, shell) => {
10
38
  const tokens = [];
11
- let [readingQuotedString, readingFlag, readingCmd] = [false, false, false];
39
+ const escapeChar = getShellWhitespaceEscapeChar(shell);
40
+ let [readingQuotedString, readingQuoteContinuedString, readingFlag, readingCmd] = [false, false, false, false];
12
41
  let readingIdx = 0;
13
42
  let readingQuoteChar = "";
14
43
  [...command].forEach((char, idx) => {
15
- const reading = readingQuotedString || readingFlag || readingCmd;
16
- if (!reading && (char === `'` || char === `"`)) {
44
+ const reading = readingQuotedString || readingQuoteContinuedString || readingFlag || readingCmd;
45
+ if (!reading && (char === `'` || char === `"` || char == "`")) {
17
46
  [readingQuotedString, readingIdx, readingQuoteChar] = [true, idx, char];
18
47
  return;
19
48
  }
@@ -25,46 +54,76 @@ const lex = (command) => {
25
54
  [readingCmd, readingIdx] = [true, idx];
26
55
  return;
27
56
  }
28
- if (readingQuotedString && char === readingQuoteChar && command.at(idx - 1) !== "\\") {
57
+ if (readingQuotedString && char === readingQuoteChar && command.at(idx - 1) !== escapeChar && !spaceRegex.test(command.at(idx + 1) ?? " ")) {
58
+ readingQuotedString = false;
59
+ readingQuoteContinuedString = true;
60
+ }
61
+ else if (readingQuotedString && char === readingQuoteChar && command.at(idx - 1) !== escapeChar) {
29
62
  readingQuotedString = false;
30
63
  const complete = idx + 1 < command.length && spaceRegex.test(command[idx + 1]);
31
64
  tokens.push({
32
65
  token: command.slice(readingIdx + 1, idx),
66
+ tokenLength: wcwidth(command.slice(readingIdx + 1, idx)) + 2,
33
67
  complete,
34
68
  isOption: false,
35
69
  isQuoted: true,
36
70
  });
37
71
  }
72
+ else if (readingQuoteContinuedString && spaceRegex.test(char) && command.at(idx - 1) !== escapeChar) {
73
+ readingQuoteContinuedString = false;
74
+ tokens.push({
75
+ token: command.slice(readingIdx, idx),
76
+ tokenLength: wcwidth(command.slice(readingIdx, idx)),
77
+ complete: true,
78
+ isOption: false,
79
+ isQuoted: true,
80
+ isQuoteContinued: true,
81
+ });
82
+ }
38
83
  else if ((readingFlag && spaceRegex.test(char)) || char === "=") {
39
84
  readingFlag = false;
40
85
  tokens.push({
41
86
  token: command.slice(readingIdx, idx),
87
+ tokenLength: wcwidth(command.slice(readingIdx, idx)),
42
88
  complete: true,
43
89
  isOption: true,
44
90
  });
45
91
  }
46
- else if (readingCmd && spaceRegex.test(char) && command.at(idx - 1) !== "\\") {
92
+ else if (readingCmd && spaceRegex.test(char) && command.at(idx - 1) !== escapeChar) {
47
93
  readingCmd = false;
48
94
  tokens.push({
49
95
  token: command.slice(readingIdx, idx),
96
+ tokenLength: wcwidth(command.slice(readingIdx, idx)),
50
97
  complete: true,
51
98
  isOption: false,
52
99
  });
53
100
  }
54
101
  });
55
- const reading = readingQuotedString || readingFlag || readingCmd;
102
+ const reading = readingQuotedString || readingQuoteContinuedString || readingFlag || readingCmd;
56
103
  if (reading) {
57
104
  if (readingQuotedString) {
58
105
  tokens.push({
59
106
  token: command.slice(readingIdx + 1),
107
+ tokenLength: wcwidth(command.slice(readingIdx + 1)) + 1,
108
+ complete: false,
109
+ isOption: false,
110
+ isQuoted: true,
111
+ });
112
+ }
113
+ else if (readingQuoteContinuedString) {
114
+ tokens.push({
115
+ token: command.slice(readingIdx),
116
+ tokenLength: wcwidth(command.slice(readingIdx)),
60
117
  complete: false,
61
118
  isOption: false,
62
119
  isQuoted: true,
120
+ isQuoteContinued: true,
63
121
  });
64
122
  }
65
123
  else {
66
124
  tokens.push({
67
125
  token: command.slice(readingIdx),
126
+ tokenLength: wcwidth(command.slice(readingIdx)),
68
127
  complete: false,
69
128
  isOption: readingFlag,
70
129
  });
@@ -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";
@@ -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,10 +3,77 @@
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: { ...process.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 = "";
@@ -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,7 +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";
11
13
  import log from "./log.js";
14
+ const exec = util.promisify(childProcess.exec);
12
15
  export var Shell;
13
16
  (function (Shell) {
14
17
  Shell["Bash"] = "bash";
@@ -35,6 +38,79 @@ export const aliasSupportedShells = [Shell.Bash, Shell.Zsh];
35
38
  export const userZdotdir = process.env?.ZDOTDIR ?? os.homedir() ?? `~`;
36
39
  export const zdotdir = path.join(os.tmpdir(), `is-zsh`);
37
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
+ };
38
114
  export const setupBashPreExec = async () => {
39
115
  const shellFolderPath = path.join(path.dirname(url.fileURLToPath(import.meta.url)), "..", "..", "shell");
40
116
  const globalConfigPath = path.join(os.homedir(), configFolder);
@@ -50,7 +126,7 @@ export const setupZshDotfiles = async () => {
50
126
  await fsAsync.cp(path.join(shellFolderPath, "shellIntegration-env.zsh"), path.join(zdotdir, ".zshenv"));
51
127
  await fsAsync.cp(path.join(shellFolderPath, "shellIntegration-login.zsh"), path.join(zdotdir, ".zlogin"));
52
128
  };
53
- const findPareentProcess = async () => {
129
+ const findParentProcess = async () => {
54
130
  try {
55
131
  return (await find("pid", process.ppid)).at(0);
56
132
  }
@@ -86,7 +162,7 @@ export const inferShell = async () => {
86
162
  /* empty */
87
163
  }
88
164
  // try getting shell from parent process
89
- const processResult = await findPareentProcess();
165
+ const processResult = await findParentProcess();
90
166
  const name = processResult?.name;
91
167
  return name != null ? supportedShells.find((shell) => name.includes(shell)) : undefined;
92
168
  };
@@ -125,13 +201,43 @@ const getGitBashPaths = async () => {
125
201
  return gitBashPaths;
126
202
  };
127
203
  export const getBackspaceSequence = (press, shell) => shell === Shell.Pwsh || shell === Shell.Powershell || shell === Shell.Cmd || shell === Shell.Nushell ? "\u007F" : press[1].sequence;
128
- 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
+ };
129
216
  // nu fully re-writes the prompt every keystroke resulting in duplicate start/end sequences on the same line
130
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
+ };
131
237
  export const getShellConfig = (shell) => {
132
238
  switch (shell) {
133
239
  case Shell.Zsh:
134
- return `if [[ -z "\${ISTERM}" && $- = *i* && $- != *c* ]]; then
240
+ return `if [[ -z "\${ISTERM}" && $- = *i* && $- != *c* && -z "\${VSCODE_RESOLVING_ENVIRONMENT}" ]]; then
135
241
  if [[ -o login ]]; then
136
242
  is -s zsh --login ; exit
137
243
  else
@@ -139,7 +245,7 @@ export const getShellConfig = (shell) => {
139
245
  fi
140
246
  fi`;
141
247
  case Shell.Bash:
142
- return `if [[ -z "\${ISTERM}" && $- = *i* && $- != *c* ]]; then
248
+ return `if [[ -z "\${ISTERM}" && $- = *i* && $- != *c* && -z "\${VSCODE_RESOLVING_ENVIRONMENT}" ]]; then
143
249
  shopt -q login_shell
144
250
  login_shell=$?
145
251
  if [ $login_shell -eq 0 ]; then
@@ -153,12 +259,12 @@ fi`;
153
259
  return `$__IsCommandFlag = ([Environment]::GetCommandLineArgs() | ForEach-Object { $_.contains("-Command") }) -contains $true
154
260
  $__IsNoExitFlag = ([Environment]::GetCommandLineArgs() | ForEach-Object { $_.contains("-NoExit") }) -contains $true
155
261
  $__IsInteractive = -not $__IsCommandFlag -or ($__IsCommandFlag -and $__IsNoExitFlag)
156
- 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)) {
157
263
  is -s ${shell}
158
264
  Stop-Process -Id $pid
159
265
  }`;
160
266
  case Shell.Fish:
161
- return `if test -z "$ISTERM" && status --is-interactive
267
+ return `if test -z "$ISTERM" && status --is-interactive && test -z "$VSCODE_RESOLVING_ENVIRONMENT"
162
268
  if status --is-login
163
269
  is -s fish --login ; kill %self
164
270
  else
@@ -166,13 +272,13 @@ if ([string]::IsNullOrEmpty($env:ISTERM) -and [Environment]::UserInteractive -an
166
272
  end
167
273
  end`;
168
274
  case Shell.Xonsh:
169
- return `if 'ISTERM' not in \${...} and $XONSH_INTERACTIVE:
275
+ return `if 'ISTERM' not in \${...} and $XONSH_INTERACTIVE and 'VSCODE_RESOLVING_ENVIRONMENT' not in \${...}:
170
276
  if $XONSH_LOGIN:
171
277
  is -s xonsh --login ; exit
172
278
  else:
173
279
  is -s xonsh ; exit`;
174
280
  case Shell.Nushell:
175
- 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 {
176
282
  if $nu.is-login { is -s nu --login ; exit } else { is -s nu ; exit }
177
283
  }`;
178
284
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@microsoft/inshellisense",
3
- "version": "0.0.1-rc.16",
3
+ "version": "0.0.1-rc.18",
4
4
  "description": "IDE style command line auto complete",
5
5
  "type": "module",
6
6
  "engines": {
@@ -13,18 +13,20 @@
13
13
  "files": [
14
14
  "build/**",
15
15
  "shell/**",
16
+ "scripts/**",
16
17
  "*.md",
17
18
  "LICENSE"
18
19
  ],
19
20
  "scripts": {
20
21
  "build": "tsc",
21
- "dev": "node --loader ts-node/esm src/index.ts -V",
22
+ "dev": "node --import=tsx src/index.ts -V",
22
23
  "test": "node --experimental-vm-modules node_modules/jest/bin/jest.js",
23
24
  "test:e2e": "tui-test",
24
25
  "lint": "eslint src/ --ext .ts,.tsx && prettier src/ --check",
25
26
  "lint:fix": "eslint src/ --ext .ts,.tsx --fix && prettier src/ --write",
26
- "debug": "node --inspect --loader ts-node/esm src/index.ts -V",
27
- "pre-commit": "lint-staged"
27
+ "debug": "node --inspect --import=tsx src/index.ts -V",
28
+ "pre-commit": "lint-staged",
29
+ "postinstall": "node ./scripts/postinstall.js"
28
30
  },
29
31
  "repository": {
30
32
  "type": "git",
@@ -40,7 +42,7 @@
40
42
  "homepage": "https://github.com/microsoft/inshellisense#readme",
41
43
  "dependencies": {
42
44
  "@homebridge/node-pty-prebuilt-multiarch": "^0.11.12",
43
- "@withfig/autocomplete": "2.651.0",
45
+ "@withfig/autocomplete": "2.675.0",
44
46
  "@xterm/addon-unicode11": "^0.8.0",
45
47
  "@xterm/headless": "^5.5.0",
46
48
  "ajv": "^8.12.0",
@@ -77,7 +79,7 @@
77
79
  "lint-staged": "^15.2.2",
78
80
  "prettier": "3.0.3",
79
81
  "ts-jest": "^29.1.1",
80
- "ts-node": "^10.9.2",
82
+ "tsx": "^4.19.1",
81
83
  "typescript": "^5.2.2"
82
84
  },
83
85
  "lint-staged": {
@@ -0,0 +1,9 @@
1
+ // Copyright (c) Microsoft Corporation.
2
+ // Licensed under the MIT License.
3
+
4
+ import fs from "node:fs";
5
+
6
+ if (fs.existsSync("./build/commands/init.js")) {
7
+ const init = (await import("../build/commands/init.js")).default;
8
+ init.parse(["--generate-full-configs"], { from: "user" });
9
+ }
@@ -1,4 +1,9 @@
1
1
  if [[ -f $USER_ZDOTDIR/.zlogin ]]; then
2
+ IS_ZDOTDIR=$ZDOTDIR
2
3
  ZDOTDIR=$USER_ZDOTDIR
4
+
3
5
  . $ZDOTDIR/.zlogin
4
- fi
6
+
7
+ USER_ZDOTDIR=$ZDOTDIR
8
+ ZDOTDIR=$IS_ZDOTDIR
9
+ fi
@@ -1,4 +1,9 @@
1
1
  if [[ -f $USER_ZDOTDIR/.zprofile ]]; then
2
+ IS_ZDOTDIR=$ZDOTDIR
2
3
  ZDOTDIR=$USER_ZDOTDIR
4
+
3
5
  . $USER_ZDOTDIR/.zprofile
4
- fi
6
+
7
+ USER_ZDOTDIR=$ZDOTDIR
8
+ ZDOTDIR=$IS_ZDOTDIR
9
+ fi
@@ -20,4 +20,4 @@ if [ "$ISTERM_TESTING" = "1" ]
20
20
  function is_user_prompt; printf '> '; end
21
21
  end
22
22
 
23
- function fish_prompt; printf (__is_prompt_start); printf (is_user_prompt); printf (__is_prompt_end); end
23
+ function fish_prompt; __is_prompt_start; is_user_prompt; __is_prompt_end; end
package/todo.md DELETED
@@ -1,4 +0,0 @@
1
- - [x] improve wrapping handling
2
- `const wrapped = line?.isWrapped || (this.#terminal.buffer.active.cursorY + this.#terminal.buffer.active.baseY) != lineY`
3
- `lineY < this.#terminal.rows`
4
- - [ ] handle paths with spaces in them on bash (ignore pwsh for now as it fails cd anyway)