@microsoft/inshellisense 0.0.1-rc.12 → 0.0.1-rc.13

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
@@ -20,6 +20,33 @@ npm install -g @microsoft/inshellisense
20
20
 
21
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.
22
22
 
23
+ ### Shell Plugin
24
+
25
+ If you'd like to automatically start inshellisense when you open your shell, run the respective command for your shell. After running the command, inshellisense will automatically open when you start any new shell session:
26
+
27
+ ```shell
28
+ # bash
29
+ is init bash >> ~/.bashrc
30
+
31
+ # zsh
32
+ is init zsh >> ~/.zshrc
33
+
34
+ # fish
35
+ is init fish >> ~/.config/fish/config.fish
36
+
37
+ # pwsh
38
+ is init pwsh >> $profile
39
+
40
+ # powershell
41
+ is init powershell >> $profile
42
+
43
+ # xonsh
44
+ is init xonsh >> ~/.xonshrc
45
+
46
+ # nushell
47
+ is init nu >> $nu.env-path
48
+ ```
49
+
23
50
  ### Usage
24
51
 
25
52
  | Action | Command | Description |
@@ -0,0 +1,18 @@
1
+ // Copyright (c) Microsoft Corporation.
2
+ // Licensed under the MIT License.
3
+ import { Command } from "commander";
4
+ import { initSupportedShells as shells, getShellConfig } from "../utils/shell.js";
5
+ const supportedShells = shells.join(", ");
6
+ const action = (program) => async (shell) => {
7
+ if (!shells.map((s) => s.valueOf()).includes(shell)) {
8
+ program.error(`Unsupported shell: '${shell}', supported shells: ${supportedShells}`, { exitCode: 1 });
9
+ }
10
+ const config = getShellConfig(shell);
11
+ process.stdout.write(`\n\n# ---------------- inshellisense shell plugin ----------------\n${config}`);
12
+ process.exit(0);
13
+ };
14
+ 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}`);
17
+ cmd.action(action(cmd));
18
+ export default cmd;
@@ -32,5 +32,5 @@ export const action = (program) => async (options) => {
32
32
  await setupBashPreExec();
33
33
  }
34
34
  await loadAliases(shell);
35
- await render(shell, options.test ?? false, options.parentTermExit ?? false);
35
+ await render(shell, options.test ?? false);
36
36
  };
package/build/index.js CHANGED
@@ -5,6 +5,7 @@
5
5
  import { Command, Option } from "commander";
6
6
  import complete from "./commands/complete.js";
7
7
  import uninstall from "./commands/uninstall.js";
8
+ import init from "./commands/init.js";
8
9
  import { action, supportedShells } from "./commands/root.js";
9
10
  import { getVersion } from "./utils/version.js";
10
11
  const program = new Command();
@@ -20,10 +21,10 @@ program
20
21
  .action(action(program))
21
22
  .option("-s, --shell <shell>", `shell to use for command execution, supported shells: ${supportedShells}`)
22
23
  .option("-c, --check", `check if shell is in an inshellisense session`)
23
- .option("--parent-term-exit", `when inshellisense is closed, kill the parent process`)
24
24
  .addOption(hiddenOption("-T, --test", "used to make e2e tests reproducible across machines"))
25
25
  .option("-V, --verbose", `enable verbose logging`)
26
26
  .showHelpAfterError("(add --help for additional information)");
27
27
  program.addCommand(complete);
28
28
  program.addCommand(uninstall);
29
+ program.addCommand(init);
29
30
  program.parse();
@@ -223,7 +223,7 @@ export class CommandManager {
223
223
  let wrappedCommand = "";
224
224
  let suggestions = "";
225
225
  let isWrapped = false;
226
- for (;;) {
226
+ for (; lineY < this.#terminal.buffer.active.baseY + this.#terminal.rows;) {
227
227
  for (let i = lineY == this.#activeCommand.promptEndMarker.line ? this.#activeCommand.promptText.length : 0; i < this.#terminal.cols; i++) {
228
228
  if (command.endsWith(" "))
229
229
  break; // assume that a command that ends with 4 spaces is terminated, avoids capturing right prompts
@@ -29,9 +29,10 @@ const lex = (command) => {
29
29
  readingQuotedString = false;
30
30
  const complete = idx + 1 < command.length && spaceRegex.test(command[idx + 1]);
31
31
  tokens.push({
32
- token: command.slice(readingIdx, idx + 1),
32
+ token: command.slice(readingIdx + 1, idx),
33
33
  complete,
34
34
  isOption: false,
35
+ isQuoted: true,
35
36
  });
36
37
  }
37
38
  else if ((readingFlag && spaceRegex.test(char)) || char === "=") {
@@ -42,7 +43,7 @@ const lex = (command) => {
42
43
  isOption: true,
43
44
  });
44
45
  }
45
- else if (readingCmd && spaceRegex.test(char)) {
46
+ else if (readingCmd && spaceRegex.test(char) && command.at(idx - 1) !== "\\") {
46
47
  readingCmd = false;
47
48
  tokens.push({
48
49
  token: command.slice(readingIdx, idx),
@@ -53,11 +54,21 @@ const lex = (command) => {
53
54
  });
54
55
  const reading = readingQuotedString || readingFlag || readingCmd;
55
56
  if (reading) {
56
- tokens.push({
57
- token: command.slice(readingIdx),
58
- complete: false,
59
- isOption: readingFlag,
60
- });
57
+ if (readingQuotedString) {
58
+ tokens.push({
59
+ token: command.slice(readingIdx + 1),
60
+ complete: false,
61
+ isOption: false,
62
+ isQuoted: true,
63
+ });
64
+ }
65
+ else {
66
+ tokens.push({
67
+ token: command.slice(readingIdx),
68
+ complete: false,
69
+ isOption: readingFlag,
70
+ });
71
+ }
61
72
  }
62
73
  return tokens;
63
74
  };
@@ -3,6 +3,7 @@
3
3
  import path from "node:path";
4
4
  import { runGenerator } from "./generator.js";
5
5
  import { runTemplates } from "./template.js";
6
+ import log from "../utils/log.js";
6
7
  var SuggestionIcons;
7
8
  (function (SuggestionIcons) {
8
9
  SuggestionIcons["File"] = "\uD83D\uDCC4";
@@ -43,6 +44,9 @@ const getIcon = (icon, suggestionType) => {
43
44
  const getLong = (suggestion) => {
44
45
  return suggestion instanceof Array ? suggestion.reduce((p, c) => (p.length > c.length ? p : c)) : suggestion;
45
46
  };
47
+ const getPathy = (type) => {
48
+ return type === "file" || type === "folder";
49
+ };
46
50
  const toSuggestion = (suggestion, name, type) => {
47
51
  if (suggestion.name == null)
48
52
  return;
@@ -53,6 +57,7 @@ const toSuggestion = (suggestion, name, type) => {
53
57
  allNames: suggestion.name instanceof Array ? suggestion.name : [suggestion.name],
54
58
  priority: suggestion.priority ?? 50,
55
59
  insertValue: suggestion.insertValue,
60
+ pathy: getPathy(suggestion.type),
56
61
  };
57
62
  };
58
63
  function filter(suggestions, filterStrategy, partialCmd, suggestionType) {
@@ -74,6 +79,7 @@ function filter(suggestions, filterStrategy, partialCmd, suggestionType) {
74
79
  allNames: s.name,
75
80
  priority: s.priority ?? 50,
76
81
  insertValue: s.insertValue,
82
+ pathy: getPathy(s.type),
77
83
  }
78
84
  : undefined;
79
85
  }
@@ -85,6 +91,7 @@ function filter(suggestions, filterStrategy, partialCmd, suggestionType) {
85
91
  allNames: [s.name],
86
92
  priority: s.priority ?? 50,
87
93
  insertValue: s.insertValue,
94
+ pathy: getPathy(s.type),
88
95
  }
89
96
  : undefined;
90
97
  })
@@ -104,6 +111,7 @@ function filter(suggestions, filterStrategy, partialCmd, suggestionType) {
104
111
  allNames: s.name,
105
112
  insertValue: s.insertValue,
106
113
  priority: s.priority ?? 50,
114
+ pathy: getPathy(s.type),
107
115
  }
108
116
  : undefined;
109
117
  }
@@ -115,6 +123,7 @@ function filter(suggestions, filterStrategy, partialCmd, suggestionType) {
115
123
  allNames: [s.name],
116
124
  insertValue: s.insertValue,
117
125
  priority: s.priority ?? 50,
126
+ pathy: getPathy(s.type),
118
127
  }
119
128
  : undefined;
120
129
  })
@@ -144,6 +153,14 @@ const optionSuggestions = (options, acceptedTokens, filterStrategy, partialCmd)
144
153
  const validOptions = options?.filter((o) => o.exclusiveOn?.every((exclusiveOption) => !usedOptions.has(exclusiveOption)) ?? true);
145
154
  return filter(validOptions ?? [], filterStrategy, partialCmd, "option");
146
155
  };
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);
163
+ }
147
164
  const removeAcceptedSuggestions = (suggestions, acceptedTokens) => {
148
165
  const seen = new Set(acceptedTokens.map((t) => t.token));
149
166
  return suggestions.filter((s) => s.allNames.every((n) => !seen.has(n)));
@@ -163,6 +180,7 @@ const removeEmptySuggestion = (suggestions) => {
163
180
  return suggestions.filter((s) => s.name.length > 0);
164
181
  };
165
182
  export const getSubcommandDrivenRecommendation = async (subcommand, persistentOptions, partialToken, argsDepleted, argsFromSubcommand, acceptedTokens, cwd) => {
183
+ log.debug({ msg: "suggestion point", subcommand, persistentOptions, partialToken, argsDepleted, argsFromSubcommand, acceptedTokens, cwd });
166
184
  if (argsDepleted && argsFromSubcommand) {
167
185
  return;
168
186
  }
@@ -184,7 +202,7 @@ export const getSubcommandDrivenRecommendation = async (subcommand, persistentOp
184
202
  suggestions.push(...(await templateSuggestions(activeArg?.template, activeArg?.filterStrategy, partialCmd, cwd)));
185
203
  }
186
204
  return {
187
- suggestions: removeDuplicateSuggestion(removeEmptySuggestion(removeAcceptedSuggestions(suggestions.sort((a, b) => b.priority - a.priority), acceptedTokens))),
205
+ suggestions: removeDuplicateSuggestion(removeEmptySuggestion(removeAcceptedSuggestions(adjustPathSuggestions(suggestions.sort((a, b) => b.priority - a.priority), partialToken), acceptedTokens))),
188
206
  };
189
207
  };
190
208
  export const getArgDrivenRecommendation = async (args, subcommand, persistentOptions, partialToken, acceptedTokens, variadicArgBound, cwd) => {
@@ -204,7 +222,7 @@ export const getArgDrivenRecommendation = async (args, subcommand, persistentOpt
204
222
  suggestions.push(...optionSuggestions(allOptions, acceptedTokens, activeArg?.filterStrategy, partialCmd));
205
223
  }
206
224
  return {
207
- suggestions: removeDuplicateSuggestion(removeEmptySuggestion(removeAcceptedSuggestions(suggestions.sort((a, b) => b.priority - a.priority), acceptedTokens))),
225
+ suggestions: removeDuplicateSuggestion(removeEmptySuggestion(removeAcceptedSuggestions(adjustPathSuggestions(suggestions.sort((a, b) => b.priority - a.priority), partialToken), acceptedTokens))),
208
226
  argumentDescription: activeArg.description ?? activeArg.name,
209
227
  };
210
228
  };
@@ -6,7 +6,7 @@ import fsAsync from "node:fs/promises";
6
6
  import { getPathSeperator } from "../utils/shell.js";
7
7
  import log from "../utils/log.js";
8
8
  export const buildExecuteShellCommand = (timeout) => async ({ command, env, args, cwd }) => {
9
- const child = spawn(command, args, { cwd, env });
9
+ const child = spawn(command, args, { cwd, env: { ...env, ISTERM: "1" } });
10
10
  setTimeout(() => child.kill("SIGKILL"), timeout);
11
11
  let stdout = "";
12
12
  let stderr = "";
@@ -28,7 +28,8 @@ export const buildExecuteShellCommand = (timeout) => async ({ command, env, args
28
28
  export const resolveCwd = async (cmdToken, cwd, shell) => {
29
29
  if (cmdToken == null)
30
30
  return { cwd, pathy: false, complete: false };
31
- const { token } = cmdToken;
31
+ const { token: rawToken, isQuoted } = cmdToken;
32
+ const token = !isQuoted ? rawToken.replaceAll("\\ ", " ") : rawToken;
32
33
  const sep = getPathSeperator(shell);
33
34
  if (!token.includes(sep))
34
35
  return { cwd, pathy: false, complete: false };
@@ -12,7 +12,7 @@ export const renderConfirmation = (live) => {
12
12
  const statusMessage = live ? chalk.green("live") : chalk.red("not found");
13
13
  return `inshellisense session [${statusMessage}]\n`;
14
14
  };
15
- export const render = async (shell, underTest, parentTermExit) => {
15
+ export const render = async (shell, underTest) => {
16
16
  const term = await isterm.spawn({ shell, rows: process.stdout.rows, cols: process.stdout.columns, underTest });
17
17
  const suggestionManager = new SuggestionManager(term, shell);
18
18
  let hasActiveSuggestions = false;
@@ -129,9 +129,6 @@ export const render = async (shell, underTest, parentTermExit) => {
129
129
  }
130
130
  });
131
131
  term.onExit(({ exitCode }) => {
132
- if (parentTermExit && process.ppid) {
133
- process.kill(process.ppid);
134
- }
135
132
  process.exit(exitCode);
136
133
  });
137
134
  process.stdout.on("resize", () => {
@@ -29,6 +29,7 @@ export const supportedShells = [
29
29
  Shell.Xonsh,
30
30
  Shell.Nushell,
31
31
  ].filter((shell) => shell != null);
32
+ export const initSupportedShells = supportedShells.filter((shell) => shell != Shell.Cmd);
32
33
  export const userZdotdir = process.env?.ZDOTDIR ?? os.homedir() ?? `~`;
33
34
  export const zdotdir = path.join(os.tmpdir(), `is-zsh`);
34
35
  const configFolder = ".inshellisense";
@@ -99,3 +100,33 @@ export const getBackspaceSequence = (press, shell) => shell === Shell.Pwsh || sh
99
100
  export const getPathSeperator = (shell) => (shell == Shell.Bash || shell == Shell.Xonsh || shell == Shell.Nushell ? "/" : path.sep);
100
101
  // nu fully re-writes the prompt every keystroke resulting in duplicate start/end sequences on the same line
101
102
  export const getShellPromptRewrites = (shell) => shell == Shell.Nushell;
103
+ export const getShellConfig = (shell) => {
104
+ switch (shell) {
105
+ case Shell.Zsh:
106
+ case Shell.Bash:
107
+ return `if [[ -z "\${ISTERM}" && $- = *i* && $- != *c* ]]; then
108
+ is -s ${shell} ; exit
109
+ fi`;
110
+ case Shell.Powershell:
111
+ case Shell.Pwsh:
112
+ return `$__IsCommandFlag = ([Environment]::GetCommandLineArgs() | ForEach-Object { $_.contains("-Command") }) -contains $true
113
+ $__IsNoExitFlag = ([Environment]::GetCommandLineArgs() | ForEach-Object { $_.contains("-NoExit") }) -contains $true
114
+ $__IsInteractive = -not $__IsCommandFlag -or ($__IsCommandFlag -and $__IsNoExitFlag)
115
+ if ([string]::IsNullOrEmpty($env:ISTERM) -and [Environment]::UserInteractive -and $__IsInteractive) {
116
+ is -s ${shell}
117
+ Stop-Process -Id $pid
118
+ }`;
119
+ case Shell.Fish:
120
+ return `if test -z "$ISTERM" && status --is-interactive
121
+ is -s fish ; kill %self
122
+ end`;
123
+ case Shell.Xonsh:
124
+ return `if 'ISTERM' not in \${...} and $XONSH_INTERACTIVE:
125
+ is -s xonsh ; exit`;
126
+ case Shell.Nushell:
127
+ return `if "ISTERM" not-in $env and $nu.is-interactive {
128
+ is -s nu ; exit
129
+ }`;
130
+ }
131
+ return "";
132
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@microsoft/inshellisense",
3
- "version": "0.0.1-rc.12",
3
+ "version": "0.0.1-rc.13",
4
4
  "description": "IDE style command line auto complete",
5
5
  "type": "module",
6
6
  "engines": {