@microsoft/inshellisense 0.0.1-rc.5 → 0.0.1-rc.7

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
@@ -2,7 +2,7 @@
2
2
 
3
3
  `inshellisense` provides IDE style autocomplete for shells. It's a terminal native runtime for [autocomplete](https://github.com/withfig/autocomplete) which has support for 600+ command line tools. `inshellisense` supports Windows, Linux, & macOS.
4
4
 
5
- <p align="center"><img alt="demo of inshellisense working" src="/docs/demo.gif" height="450px"/></p>
5
+ <p align="center"><img alt="demo of inshellisense working" src="/docs/demo.gif"/></p>
6
6
 
7
7
  ## Getting Started
8
8
 
@@ -21,6 +21,25 @@ npm install -g @microsoft/inshellisense
21
21
 
22
22
  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.
23
23
 
24
+ ### Usage
25
+
26
+ | Action | Command |
27
+ | ------------------------------------- | ------- |
28
+ | Start | `is` |
29
+ | Stop | `exit` |
30
+ | Check If Inside Inshellisense Session | `is -c` |
31
+
32
+ #### Keybindings
33
+
34
+ All other keys are passed through to the shell. The keybindings below are only captured when the inshellisense suggestions are visible, otherwise they are passed through to the shell as well.
35
+
36
+ | Action | Keybinding |
37
+ | ------------------------- | -------------- |
38
+ | Accept Current Suggestion | <kbd>tab</kbd> |
39
+ | View Next Suggestion | <kbd>↓</kbd> |
40
+ | View Previous Suggestion | <kbd>↑</kbd> |
41
+ | Dismiss Suggestions | <kbd>esc</kbd> |
42
+
24
43
  ## Integrations
25
44
 
26
45
  inshellisense supports the following shells:
@@ -30,6 +49,7 @@ inshellisense supports the following shells:
30
49
  - [fish](https://github.com/fish-shell/fish-shell)
31
50
  - [pwsh](https://github.com/PowerShell/PowerShell)
32
51
  - [powershell](https://learn.microsoft.com/en-us/powershell/scripting/windows-powershell/starting-windows-powershell) (Windows Powershell)
52
+ - [cmd](https://learn.microsoft.com/en-us/windows-server/administration/windows-commands/cmd) _(experimental)_
33
53
 
34
54
  ## Contributing
35
55
 
@@ -0,0 +1,15 @@
1
+ // Copyright (c) Microsoft Corporation.
2
+ // Licensed under the MIT License.
3
+ import os from "node:os";
4
+ import { Command } from "commander";
5
+ import { getSuggestions } from "../runtime/runtime.js";
6
+ import { Shell } from "../utils/shell.js";
7
+ const action = async (input) => {
8
+ const suggestions = await getSuggestions(input, process.cwd(), os.platform() === "win32" ? Shell.Cmd : Shell.Bash);
9
+ process.stdout.write(JSON.stringify(suggestions));
10
+ };
11
+ const cmd = new Command("complete");
12
+ cmd.description(`generates a completion for the provided input`);
13
+ cmd.argument("<input>");
14
+ cmd.action(action);
15
+ export default cmd;
@@ -1,11 +1,19 @@
1
1
  // Copyright (c) Microsoft Corporation.
2
2
  // Licensed under the MIT License.
3
- import { render } from "../ui/ui-root.js";
4
- import { Shell, supportedShells as shells, setupZshDotfiles } from "../utils/shell.js";
3
+ import { render, renderConfirmation } from "../ui/ui-root.js";
4
+ import { Shell, supportedShells as shells, setupZshDotfiles, setupBashPreExec } from "../utils/shell.js";
5
5
  import { inferShell } from "../utils/shell.js";
6
6
  import { loadConfig } from "../utils/config.js";
7
+ import log from "../utils/log.js";
7
8
  export const supportedShells = shells.join(", ");
8
9
  export const action = (program) => async (options) => {
10
+ const inISTerm = process.env.ISTERM === "1";
11
+ if (options.check || inISTerm) {
12
+ process.stdout.write(renderConfirmation(inISTerm));
13
+ return;
14
+ }
15
+ if (options.verbose)
16
+ await log.enable();
9
17
  await loadConfig(program);
10
18
  const shell = options.shell ?? (await inferShell());
11
19
  if (shell == null) {
@@ -17,5 +25,8 @@ export const action = (program) => async (options) => {
17
25
  if (shell == Shell.Zsh) {
18
26
  await setupZshDotfiles();
19
27
  }
28
+ else if (shell == Shell.Bash) {
29
+ await setupBashPreExec();
30
+ }
20
31
  await render(shell);
21
32
  };
package/build/index.js CHANGED
@@ -3,6 +3,7 @@
3
3
  // Licensed under the MIT License.
4
4
  /* eslint-disable header/header */
5
5
  import { Command } from "commander";
6
+ import complete from "./commands/complete.js";
6
7
  import uninstall from "./commands/uninstall.js";
7
8
  import { action, supportedShells } from "./commands/root.js";
8
9
  import { getVersion } from "./utils/version.js";
@@ -13,6 +14,9 @@ program
13
14
  .version(await getVersion(), "-v, --version", "output the current version")
14
15
  .action(action(program))
15
16
  .option("-s, --shell <shell>", `shell to use for command execution, supported shells: ${supportedShells}`)
17
+ .option("-c, --check", `check if shell is in an inshellisense session`)
18
+ .option("-V, --verbose", `enable verbose logging`)
16
19
  .showHelpAfterError("(add --help for additional information)");
20
+ program.addCommand(complete);
17
21
  program.addCommand(uninstall);
18
22
  program.parse();
@@ -26,6 +26,9 @@ export class CommandManager {
26
26
  }
27
27
  }
28
28
  handlePromptStart() {
29
+ if (this.#activeCommand.promptStartMarker?.line == -1) {
30
+ this.#previousCommandLines = new Set();
31
+ }
29
32
  this.#activeCommand = { promptStartMarker: this.#terminal.registerMarker(0), hasOutput: false, cursorTerminated: false };
30
33
  }
31
34
  handlePromptEnd() {
@@ -170,11 +173,13 @@ export class CommandManager {
170
173
  const cell = line?.getCell(i);
171
174
  if (cell == null)
172
175
  continue;
176
+ const chars = cell.getChars();
177
+ const cleanedChars = chars == "" ? " " : chars;
173
178
  if (!this._isSuggestion(cell) && suggestions.length == 0) {
174
- command += cell.getChars();
179
+ command += cleanedChars;
175
180
  }
176
181
  else {
177
- suggestions += cell.getChars();
182
+ suggestions += cleanedChars;
178
183
  }
179
184
  }
180
185
  lineY += 1;
@@ -24,10 +24,12 @@ export class ISTerm {
24
24
  onData;
25
25
  onExit;
26
26
  shellBuffer;
27
+ cwd = "";
27
28
  #pty;
28
29
  #ptyEmitter;
29
30
  #term;
30
31
  #commandManager;
32
+ #shell;
31
33
  constructor({ shell, cols, rows, env, shellTarget, shellArgs }) {
32
34
  this.#pty = pty.spawn(shellTarget, shellArgs ?? [], {
33
35
  name: "xterm-256color",
@@ -43,6 +45,7 @@ export class ISTerm {
43
45
  this.#term = new xterm.Terminal({ allowProposedApi: true, rows, cols });
44
46
  this.#term.parser.registerOscHandler(IsTermOscPs, (data) => this._handleIsSequence(data));
45
47
  this.#commandManager = new CommandManager(this.#term, shell);
48
+ this.#shell = shell;
46
49
  this.#ptyEmitter = new EventEmitter();
47
50
  this.#pty.onData((data) => {
48
51
  this.#term.write(data, () => {
@@ -59,6 +62,23 @@ export class ISTerm {
59
62
  };
60
63
  this.onExit = this.#pty.onExit;
61
64
  }
65
+ _deserializeIsMessage(message) {
66
+ return message.replaceAll(/\\(\\|x([0-9a-f]{2}))/gi, (_match, op, hex) => (hex ? String.fromCharCode(parseInt(hex, 16)) : op));
67
+ }
68
+ _sanitizedCwd(cwd) {
69
+ if (cwd.match(/^['"].*['"]$/)) {
70
+ cwd = cwd.substring(1, cwd.length - 1);
71
+ }
72
+ // Convert a drive prefix to windows style when using Git Bash
73
+ if (os.platform() === "win32" && this.#shell == Shell.Bash && cwd && cwd.match(/^\/[A-z]{1}\//)) {
74
+ cwd = `${cwd[1]}:\\` + cwd.substring(3, cwd.length);
75
+ }
76
+ // Make the drive letter uppercase on Windows (see vscode #9448)
77
+ if (os.platform() === "win32" && cwd && cwd[1] === ":") {
78
+ return cwd[0].toUpperCase() + cwd.substring(1);
79
+ }
80
+ return cwd;
81
+ }
62
82
  _handleIsSequence(data) {
63
83
  const argsIndex = data.indexOf(";");
64
84
  const sequence = argsIndex === -1 ? data : data.substring(0, argsIndex);
@@ -69,6 +89,13 @@ export class ISTerm {
69
89
  case IstermOscPt.PromptEnded:
70
90
  this.#commandManager.handlePromptEnd();
71
91
  break;
92
+ case IstermOscPt.CurrentWorkingDirectory: {
93
+ const cwd = data.split(";").at(1);
94
+ if (cwd != null) {
95
+ this.cwd = path.resolve(this._sanitizedCwd(this._deserializeIsMessage(cwd)));
96
+ }
97
+ break;
98
+ }
72
99
  default:
73
100
  return false;
74
101
  }
@@ -204,14 +231,18 @@ const convertToPtyTarget = async (shell) => {
204
231
  return { shellTarget, shellArgs };
205
232
  };
206
233
  const convertToPtyEnv = (shell) => {
234
+ const env = {
235
+ ...process.env,
236
+ ISTERM: "1",
237
+ };
207
238
  switch (shell) {
208
239
  case Shell.Cmd: {
209
240
  const prompt = process.env.PROMPT ? process.env.PROMPT : "$P$G";
210
- return { ...process.env, PROMPT: `${IstermPromptStart}${prompt}${IstermPromptEnd}` };
241
+ return { ...env, PROMPT: `${IstermPromptStart}${prompt}${IstermPromptEnd}` };
211
242
  }
212
243
  case Shell.Zsh: {
213
- return { ...process.env, ZDOTDIR: zdotdir, USER_ZDOTDIR: userZdotdir };
244
+ return { ...env, ZDOTDIR: zdotdir, USER_ZDOTDIR: userZdotdir };
214
245
  }
215
246
  }
216
- return process.env;
247
+ return env;
217
248
  };
@@ -1,11 +1,12 @@
1
1
  // Copyright (c) Microsoft Corporation.
2
2
  // Licensed under the MIT License.
3
+ import log from "../utils/log.js";
3
4
  import { runTemplates } from "./template.js";
4
5
  import { buildExecuteShellCommand } from "./utils.js";
5
- const getGeneratorContext = () => {
6
+ const getGeneratorContext = (cwd) => {
6
7
  return {
7
8
  environmentVariables: Object.fromEntries(Object.entries(process.env).filter((entry) => entry[1] != null)),
8
- currentWorkingDirectory: process.cwd(),
9
+ currentWorkingDirectory: cwd,
9
10
  currentProcess: "",
10
11
  sshPrefix: "",
11
12
  isDangerous: false,
@@ -13,30 +14,40 @@ const getGeneratorContext = () => {
13
14
  };
14
15
  };
15
16
  // TODO: add support for caching, trigger, & getQueryTerm
16
- export const runGenerator = async (generator, tokens) => {
17
- const { script, postProcess, scriptTimeout, splitOn, custom, template } = generator;
17
+ export const runGenerator = async (generator, tokens, cwd) => {
18
+ // TODO: support trigger
19
+ const { script, postProcess, scriptTimeout, splitOn, custom, template, filterTemplateSuggestions } = generator;
18
20
  const executeShellCommand = buildExecuteShellCommand(scriptTimeout ?? 5000);
19
21
  const suggestions = [];
20
22
  try {
21
23
  if (script) {
22
- const scriptOutput = typeof script === "function" ? script(tokens) : script != null ? await executeShellCommand(script) : "";
24
+ const shellInput = typeof script === "function" ? script(tokens) : script;
25
+ const scriptOutput = Array.isArray(shellInput)
26
+ ? await executeShellCommand({ command: shellInput.at(0) ?? "", args: shellInput.slice(1) })
27
+ : await executeShellCommand(shellInput);
23
28
  if (postProcess) {
24
- suggestions.push(...postProcess(scriptOutput, tokens));
29
+ suggestions.push(...postProcess(scriptOutput.stdout, tokens));
25
30
  }
26
31
  else if (splitOn) {
27
- suggestions.push(...scriptOutput.split(splitOn).map((s) => ({ name: s })));
32
+ suggestions.push(...scriptOutput.stdout.split(splitOn).map((s) => ({ name: s })));
28
33
  }
29
34
  }
30
35
  if (custom) {
31
- suggestions.push(...(await custom(tokens, executeShellCommand, getGeneratorContext())));
36
+ suggestions.push(...(await custom(tokens, executeShellCommand, getGeneratorContext(cwd))));
32
37
  }
33
38
  if (template != null) {
34
- suggestions.push(...(await runTemplates(template)));
39
+ const templateSuggestions = await runTemplates(template, cwd);
40
+ if (filterTemplateSuggestions) {
41
+ suggestions.push(...filterTemplateSuggestions(templateSuggestions));
42
+ }
43
+ else {
44
+ suggestions.push(...templateSuggestions);
45
+ }
35
46
  }
36
47
  return suggestions;
37
48
  }
38
49
  catch (e) {
39
- /* empty */
50
+ log.debug({ msg: "generator failed", e, script, splitOn, template });
40
51
  }
41
52
  return suggestions;
42
53
  };
@@ -1,6 +1,6 @@
1
1
  // Copyright (c) Microsoft Corporation.
2
2
  // Licensed under the MIT License.
3
- const cmdDelim = /(\|\|)|(&&)|(;)/;
3
+ const cmdDelim = /(\|\|)|(&&)|(;)|(\|)/;
4
4
  const spaceRegex = /\s/;
5
5
  export const parseCommand = (command) => {
6
6
  const lastCommand = command.split(cmdDelim).at(-1)?.trimStart();
@@ -56,7 +56,7 @@ const lex = (command) => {
56
56
  tokens.push({
57
57
  token: command.slice(readingIdx),
58
58
  complete: false,
59
- isOption: false,
59
+ isOption: readingFlag,
60
60
  });
61
61
  }
62
62
  return tokens;
@@ -4,9 +4,10 @@ import speclist, { diffVersionedCompletions as versionedSpeclist,
4
4
  // eslint-disable-next-line @typescript-eslint/ban-ts-comment
5
5
  // @ts-ignore
6
6
  } from "@withfig/autocomplete/build/index.js";
7
+ import path from "node:path";
7
8
  import { parseCommand } from "./parser.js";
8
9
  import { getArgDrivenRecommendation, getSubcommandDrivenRecommendation } from "./suggestion.js";
9
- import { buildExecuteShellCommand } from "./utils.js";
10
+ import { buildExecuteShellCommand, resolveCwd } from "./utils.js";
10
11
  // eslint-disable-next-line @typescript-eslint/no-explicit-any -- recursive type, setting as any
11
12
  const specSet = {};
12
13
  speclist.forEach((s) => {
@@ -46,7 +47,7 @@ const lazyLoadSpec = async (key) => {
46
47
  const lazyLoadSpecLocation = async (location) => {
47
48
  return; //TODO: implement spec location loading
48
49
  };
49
- export const getSuggestions = async (cmd) => {
50
+ export const getSuggestions = async (cmd, cwd, shell) => {
50
51
  const activeCmd = parseCommand(cmd);
51
52
  const rootToken = activeCmd.at(0);
52
53
  if (activeCmd.length === 0 || !rootToken?.complete) {
@@ -58,11 +59,19 @@ export const getSuggestions = async (cmd) => {
58
59
  const subcommand = getSubcommand(spec);
59
60
  if (subcommand == null)
60
61
  return;
61
- const result = await runSubcommand(activeCmd.slice(1), subcommand);
62
+ const lastCommand = activeCmd.at(-1);
63
+ const { cwd: resolvedCwd, pathy, complete: pathyComplete } = await resolveCwd(lastCommand, cwd, shell);
64
+ if (pathy && lastCommand) {
65
+ lastCommand.isPath = true;
66
+ lastCommand.isPathComplete = pathyComplete;
67
+ }
68
+ const result = await runSubcommand(activeCmd.slice(1), subcommand, resolvedCwd);
62
69
  if (result == null)
63
70
  return;
64
- const lastCommand = activeCmd.at(-1);
65
- const charactersToDrop = lastCommand?.complete ? 0 : lastCommand?.token.length ?? 0;
71
+ let charactersToDrop = lastCommand?.complete ? 0 : lastCommand?.token.length ?? 0;
72
+ if (pathy) {
73
+ charactersToDrop = pathyComplete ? 0 : path.basename(lastCommand?.token ?? "").length;
74
+ }
66
75
  return { ...result, charactersToDrop };
67
76
  };
68
77
  const getPersistentOptions = (persistentOptions, options) => {
@@ -142,7 +151,7 @@ const getPersistentTokens = (tokens) => {
142
151
  const getArgs = (args) => {
143
152
  return args instanceof Array ? args : args != null ? [args] : [];
144
153
  };
145
- const runOption = async (tokens, option, subcommand, persistentOptions, acceptedTokens) => {
154
+ const runOption = async (tokens, option, subcommand, cwd, persistentOptions, acceptedTokens) => {
146
155
  if (tokens.length === 0) {
147
156
  throw new Error("invalid state reached, option expected but no tokens found");
148
157
  }
@@ -150,37 +159,37 @@ const runOption = async (tokens, option, subcommand, persistentOptions, accepted
150
159
  const isPersistent = persistentOptions.some((o) => (typeof o.name === "string" ? o.name === activeToken.token : o.name.includes(activeToken.token)));
151
160
  if ((option.args instanceof Array && option.args.length > 0) || option.args != null) {
152
161
  const args = option.args instanceof Array ? option.args : [option.args];
153
- return runArg(tokens.slice(1), args, subcommand, persistentOptions, acceptedTokens.concat(activeToken), true, false);
162
+ return runArg(tokens.slice(1), args, subcommand, cwd, persistentOptions, acceptedTokens.concat(activeToken), true, false);
154
163
  }
155
- return runSubcommand(tokens.slice(1), subcommand, persistentOptions, acceptedTokens.concat({ ...activeToken, isPersistent }));
164
+ return runSubcommand(tokens.slice(1), subcommand, cwd, persistentOptions, acceptedTokens.concat({ ...activeToken, isPersistent }));
156
165
  };
157
- const runArg = async (tokens, args, subcommand, persistentOptions, acceptedTokens, fromOption, fromVariadic) => {
166
+ const runArg = async (tokens, args, subcommand, cwd, persistentOptions, acceptedTokens, fromOption, fromVariadic) => {
158
167
  if (args.length === 0) {
159
- return runSubcommand(tokens, subcommand, persistentOptions, acceptedTokens, true, !fromOption);
168
+ return runSubcommand(tokens, subcommand, cwd, persistentOptions, acceptedTokens, true, !fromOption);
160
169
  }
161
170
  else if (tokens.length === 0) {
162
- return await getArgDrivenRecommendation(args, subcommand, persistentOptions, undefined, acceptedTokens, fromVariadic);
171
+ return await getArgDrivenRecommendation(args, subcommand, persistentOptions, undefined, acceptedTokens, fromVariadic, cwd);
163
172
  }
164
173
  else if (!tokens.at(0)?.complete) {
165
- return await getArgDrivenRecommendation(args, subcommand, persistentOptions, tokens[0].token, acceptedTokens, fromVariadic);
174
+ return await getArgDrivenRecommendation(args, subcommand, persistentOptions, tokens[0], acceptedTokens, fromVariadic, cwd);
166
175
  }
167
176
  const activeToken = tokens[0];
168
177
  if (args.every((a) => a.isOptional)) {
169
178
  if (activeToken.isOption) {
170
179
  const option = getOption(activeToken, persistentOptions.concat(subcommand.options ?? []));
171
180
  if (option != null) {
172
- return runOption(tokens, option, subcommand, persistentOptions, acceptedTokens);
181
+ return runOption(tokens, option, subcommand, cwd, persistentOptions, acceptedTokens);
173
182
  }
174
183
  return;
175
184
  }
176
185
  const nextSubcommand = await genSubcommand(activeToken.token, subcommand);
177
186
  if (nextSubcommand != null) {
178
- return runSubcommand(tokens.slice(1), nextSubcommand, persistentOptions, getPersistentTokens(acceptedTokens.concat(activeToken)));
187
+ return runSubcommand(tokens.slice(1), nextSubcommand, cwd, persistentOptions, getPersistentTokens(acceptedTokens.concat(activeToken)));
179
188
  }
180
189
  }
181
190
  const activeArg = args[0];
182
191
  if (activeArg.isVariadic) {
183
- return runArg(tokens.slice(1), args, subcommand, persistentOptions, acceptedTokens.concat(activeToken), fromOption, true);
192
+ return runArg(tokens.slice(1), args, subcommand, cwd, persistentOptions, acceptedTokens.concat(activeToken), fromOption, true);
184
193
  }
185
194
  else if (activeArg.isCommand) {
186
195
  if (tokens.length <= 0) {
@@ -192,16 +201,16 @@ const runArg = async (tokens, args, subcommand, persistentOptions, acceptedToken
192
201
  const subcommand = getSubcommand(spec);
193
202
  if (subcommand == null)
194
203
  return;
195
- return runSubcommand(tokens.slice(1), subcommand);
204
+ return runSubcommand(tokens.slice(1), subcommand, cwd);
196
205
  }
197
- return runArg(tokens.slice(1), args.slice(1), subcommand, persistentOptions, acceptedTokens.concat(activeToken), fromOption, false);
206
+ return runArg(tokens.slice(1), args.slice(1), subcommand, cwd, persistentOptions, acceptedTokens.concat(activeToken), fromOption, false);
198
207
  };
199
- const runSubcommand = async (tokens, subcommand, persistentOptions = [], acceptedTokens = [], argsDepleted = false, argsUsed = false) => {
208
+ const runSubcommand = async (tokens, subcommand, cwd, persistentOptions = [], acceptedTokens = [], argsDepleted = false, argsUsed = false) => {
200
209
  if (tokens.length === 0) {
201
- return getSubcommandDrivenRecommendation(subcommand, persistentOptions, undefined, argsDepleted, argsUsed, acceptedTokens);
210
+ return getSubcommandDrivenRecommendation(subcommand, persistentOptions, undefined, argsDepleted, argsUsed, acceptedTokens, cwd);
202
211
  }
203
212
  else if (!tokens.at(0)?.complete) {
204
- return getSubcommandDrivenRecommendation(subcommand, persistentOptions, tokens[0].token, argsDepleted, argsUsed, acceptedTokens);
213
+ return getSubcommandDrivenRecommendation(subcommand, persistentOptions, tokens[0], argsDepleted, argsUsed, acceptedTokens, cwd);
205
214
  }
206
215
  const activeToken = tokens[0];
207
216
  const activeArgsLength = subcommand.args instanceof Array ? subcommand.args.length : 1;
@@ -209,16 +218,21 @@ const runSubcommand = async (tokens, subcommand, persistentOptions = [], accepte
209
218
  if (activeToken.isOption) {
210
219
  const option = getOption(activeToken, allOptions);
211
220
  if (option != null) {
212
- return runOption(tokens, option, subcommand, persistentOptions, acceptedTokens);
221
+ return runOption(tokens, option, subcommand, cwd, persistentOptions, acceptedTokens);
213
222
  }
214
223
  return;
215
224
  }
216
225
  const nextSubcommand = await genSubcommand(activeToken.token, subcommand);
217
226
  if (nextSubcommand != null) {
218
- return runSubcommand(tokens.slice(1), nextSubcommand, getPersistentOptions(persistentOptions, subcommand.options), getPersistentTokens(acceptedTokens.concat(activeToken)));
227
+ return runSubcommand(tokens.slice(1), nextSubcommand, cwd, getPersistentOptions(persistentOptions, subcommand.options), getPersistentTokens(acceptedTokens.concat(activeToken)));
219
228
  }
220
229
  if (activeArgsLength <= 0) {
221
230
  return; // not subcommand or option & no args exist
222
231
  }
223
- return runArg(tokens, getArgs(subcommand.args), subcommand, allOptions, acceptedTokens, false, false);
232
+ const args = getArgs(subcommand.args);
233
+ if (args.length != 0) {
234
+ return runArg(tokens, args, subcommand, cwd, allOptions, acceptedTokens, false, false);
235
+ }
236
+ // if the subcommand has no args specified, fallback to the subcommand and ignore this item
237
+ return runSubcommand(tokens.slice(1), subcommand, cwd, persistentOptions, acceptedTokens.concat(activeToken));
224
238
  };
@@ -1,5 +1,6 @@
1
1
  // Copyright (c) Microsoft Corporation.
2
2
  // Licensed under the MIT License.
3
+ import path from "node:path";
3
4
  import { runGenerator } from "./generator.js";
4
5
  import { runTemplates } from "./template.js";
5
6
  var SuggestionIcons;
@@ -116,14 +117,14 @@ function filter(suggestions, filterStrategy, partialCmd, suggestionType) {
116
117
  .filter((s) => s != null);
117
118
  }
118
119
  }
119
- const generatorSuggestions = async (generator, acceptedTokens, filterStrategy, partialCmd) => {
120
+ const generatorSuggestions = async (generator, acceptedTokens, filterStrategy, partialCmd, cwd) => {
120
121
  const generators = generator instanceof Array ? generator : generator ? [generator] : [];
121
122
  const tokens = acceptedTokens.map((t) => t.token);
122
- const suggestions = (await Promise.all(generators.map((gen) => runGenerator(gen, tokens)))).flat();
123
+ const suggestions = (await Promise.all(generators.map((gen) => runGenerator(gen, tokens, cwd)))).flat();
123
124
  return filter(suggestions, filterStrategy, partialCmd, undefined);
124
125
  };
125
- const templateSuggestions = async (templates, filterStrategy, partialCmd) => {
126
- return filter(await runTemplates(templates ?? []), filterStrategy, partialCmd, undefined);
126
+ const templateSuggestions = async (templates, filterStrategy, partialCmd, cwd) => {
127
+ return filter(await runTemplates(templates ?? [], cwd), filterStrategy, partialCmd, undefined);
127
128
  };
128
129
  const suggestionSuggestions = (suggestions, filterStrategy, partialCmd) => {
129
130
  const cleanedSuggestions = suggestions?.map((s) => (typeof s === "string" ? { name: s } : s)) ?? [];
@@ -137,17 +138,32 @@ const optionSuggestions = (options, acceptedTokens, filterStrategy, partialCmd)
137
138
  const validOptions = options?.filter((o) => o.exclusiveOn?.every((exclusiveOption) => !usedOptions.has(exclusiveOption)) ?? true);
138
139
  return filter(validOptions ?? [], filterStrategy, partialCmd, "option");
139
140
  };
140
- const removeDuplicateSuggestions = (suggestions, acceptedTokens) => {
141
+ const removeAcceptedSuggestions = (suggestions, acceptedTokens) => {
141
142
  const seen = new Set(acceptedTokens.map((t) => t.token));
142
143
  return suggestions.filter((s) => s.allNames.every((n) => !seen.has(n)));
143
144
  };
145
+ const removeDuplicateSuggestion = (suggestions) => {
146
+ const seen = new Set();
147
+ return suggestions
148
+ .map((s) => {
149
+ if (seen.has(s.name))
150
+ return null;
151
+ seen.add(s.name);
152
+ return s;
153
+ })
154
+ .filter((s) => s != null);
155
+ };
144
156
  const removeEmptySuggestion = (suggestions) => {
145
157
  return suggestions.filter((s) => s.name.length > 0);
146
158
  };
147
- export const getSubcommandDrivenRecommendation = async (subcommand, persistentOptions, partialCmd, argsDepleted, argsFromSubcommand, acceptedTokens) => {
159
+ export const getSubcommandDrivenRecommendation = async (subcommand, persistentOptions, partialToken, argsDepleted, argsFromSubcommand, acceptedTokens, cwd) => {
148
160
  if (argsDepleted && argsFromSubcommand) {
149
161
  return;
150
162
  }
163
+ let partialCmd = partialToken?.token;
164
+ if (partialToken?.isPath) {
165
+ partialCmd = partialToken.isPathComplete ? "" : path.basename(partialCmd ?? "");
166
+ }
151
167
  const suggestions = [];
152
168
  const argLength = subcommand.args instanceof Array ? subcommand.args.length : subcommand.args ? 1 : 0;
153
169
  const allOptions = persistentOptions.concat(subcommand.options ?? []);
@@ -157,28 +173,32 @@ export const getSubcommandDrivenRecommendation = async (subcommand, persistentOp
157
173
  }
158
174
  if (argLength != 0) {
159
175
  const activeArg = subcommand.args instanceof Array ? subcommand.args[0] : subcommand.args;
160
- suggestions.push(...(await generatorSuggestions(activeArg?.generators, acceptedTokens, activeArg?.filterStrategy, partialCmd)));
176
+ suggestions.push(...(await generatorSuggestions(activeArg?.generators, acceptedTokens, activeArg?.filterStrategy, partialCmd, cwd)));
161
177
  suggestions.push(...suggestionSuggestions(activeArg?.suggestions, activeArg?.filterStrategy, partialCmd));
162
- suggestions.push(...(await templateSuggestions(activeArg?.template, activeArg?.filterStrategy, partialCmd)));
178
+ suggestions.push(...(await templateSuggestions(activeArg?.template, activeArg?.filterStrategy, partialCmd, cwd)));
163
179
  }
164
180
  return {
165
- suggestions: removeEmptySuggestion(removeDuplicateSuggestions(suggestions.sort((a, b) => b.priority - a.priority), acceptedTokens)),
181
+ suggestions: removeDuplicateSuggestion(removeEmptySuggestion(removeAcceptedSuggestions(suggestions.sort((a, b) => b.priority - a.priority), acceptedTokens))),
166
182
  };
167
183
  };
168
- export const getArgDrivenRecommendation = async (args, subcommand, persistentOptions, partialCmd, acceptedTokens, variadicArgBound) => {
184
+ export const getArgDrivenRecommendation = async (args, subcommand, persistentOptions, partialToken, acceptedTokens, variadicArgBound, cwd) => {
185
+ let partialCmd = partialToken?.token;
186
+ if (partialToken?.isPath) {
187
+ partialCmd = partialToken.isPathComplete ? "" : path.basename(partialCmd ?? "");
188
+ }
169
189
  const activeArg = args[0];
170
190
  const allOptions = persistentOptions.concat(subcommand.options ?? []);
171
191
  const suggestions = [
172
- ...(await generatorSuggestions(args[0].generators, acceptedTokens, activeArg?.filterStrategy, partialCmd)),
192
+ ...(await generatorSuggestions(args[0].generators, acceptedTokens, activeArg?.filterStrategy, partialCmd, cwd)),
173
193
  ...suggestionSuggestions(args[0].suggestions, activeArg?.filterStrategy, partialCmd),
174
- ...(await templateSuggestions(args[0].template, activeArg?.filterStrategy, partialCmd)),
194
+ ...(await templateSuggestions(args[0].template, activeArg?.filterStrategy, partialCmd, cwd)),
175
195
  ];
176
- if ((activeArg.isOptional && !activeArg.isVariadic) || (activeArg.isVariadic && activeArg.isOptional && !variadicArgBound)) {
196
+ if (activeArg.isOptional || (activeArg.isVariadic && variadicArgBound)) {
177
197
  suggestions.push(...subcommandSuggestions(subcommand.subcommands, activeArg?.filterStrategy, partialCmd));
178
198
  suggestions.push(...optionSuggestions(allOptions, acceptedTokens, activeArg?.filterStrategy, partialCmd));
179
199
  }
180
200
  return {
181
- suggestions: removeEmptySuggestion(removeDuplicateSuggestions(suggestions.sort((a, b) => b.priority - a.priority), acceptedTokens)),
201
+ suggestions: removeDuplicateSuggestion(removeEmptySuggestion(removeAcceptedSuggestions(suggestions.sort((a, b) => b.priority - a.priority), acceptedTokens))),
182
202
  argumentDescription: activeArg.description ?? activeArg.name,
183
203
  };
184
204
  };
@@ -1,14 +1,14 @@
1
1
  // Copyright (c) Microsoft Corporation.
2
2
  // Licensed under the MIT License.
3
3
  import fsAsync from "node:fs/promises";
4
- import process from "node:process";
5
- const filepathsTemplate = async () => {
6
- const files = await fsAsync.readdir(process.cwd(), { withFileTypes: true });
7
- return files.filter((f) => f.isFile() || f.isDirectory()).map((f) => ({ name: f.name, priority: 90 }));
4
+ import log from "../utils/log.js";
5
+ const filepathsTemplate = async (cwd) => {
6
+ const files = await fsAsync.readdir(cwd, { withFileTypes: true });
7
+ return files.filter((f) => f.isFile() || f.isDirectory()).map((f) => ({ name: f.name, priority: 90, context: { templateType: "filepaths" } }));
8
8
  };
9
- const foldersTemplate = async () => {
10
- const files = await fsAsync.readdir(process.cwd(), { withFileTypes: true });
11
- return files.filter((f) => f.isDirectory()).map((f) => ({ name: f.name, priority: 90 }));
9
+ const foldersTemplate = async (cwd) => {
10
+ const files = await fsAsync.readdir(cwd, { withFileTypes: true });
11
+ return files.filter((f) => f.isDirectory()).map((f) => ({ name: f.name, priority: 90, context: { templateType: "folders" } }));
12
12
  };
13
13
  // TODO: implement history template
14
14
  const historyTemplate = () => {
@@ -18,18 +18,24 @@ const historyTemplate = () => {
18
18
  const helpTemplate = () => {
19
19
  return [];
20
20
  };
21
- export const runTemplates = async (template) => {
21
+ export const runTemplates = async (template, cwd) => {
22
22
  const templates = template instanceof Array ? template : [template];
23
23
  return (await Promise.all(templates.map(async (t) => {
24
- switch (t) {
25
- case "filepaths":
26
- return await filepathsTemplate();
27
- case "folders":
28
- return await foldersTemplate();
29
- case "history":
30
- return historyTemplate();
31
- case "help":
32
- return helpTemplate();
24
+ try {
25
+ switch (t) {
26
+ case "filepaths":
27
+ return await filepathsTemplate(cwd);
28
+ case "folders":
29
+ return await foldersTemplate(cwd);
30
+ case "history":
31
+ return historyTemplate();
32
+ case "help":
33
+ return helpTemplate();
34
+ }
35
+ }
36
+ catch (e) {
37
+ log.debug({ msg: "template failed", e, template: t, cwd });
38
+ return [];
33
39
  }
34
40
  }))).flat();
35
41
  };