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

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
@@ -22,12 +22,11 @@ After completing the installation, you can run `is` to start the autocomplete se
22
22
 
23
23
  ### Usage
24
24
 
25
- | Action | Command | Description |
26
- | ------------------------------------- | --------------------------------- | ------------------------------------------------ |
27
- | Start | `is` | Start inshellisense session on the current shell |
28
- | Stop | `exit` | Stop inshellisense session on the current shell |
29
- | Check If Inside Inshellisense Session | `is -c` | Check if shell inside inshellisense session |
30
-
25
+ | Action | Command | Description |
26
+ | ------------------------------------- | ------- | ------------------------------------------------ |
27
+ | Start | `is` | Start inshellisense session on the current shell |
28
+ | Stop | `exit` | Stop inshellisense session on the current shell |
29
+ | Check If Inside Inshellisense Session | `is -c` | Check if shell inside inshellisense session |
31
30
 
32
31
  #### Keybindings
33
32
 
@@ -51,6 +50,7 @@ inshellisense supports the following shells:
51
50
  - [powershell](https://learn.microsoft.com/en-us/powershell/scripting/windows-powershell/starting-windows-powershell) (Windows Powershell)
52
51
  - [cmd](https://learn.microsoft.com/en-us/windows-server/administration/windows-commands/cmd) _(experimental)_
53
52
  - [xonsh](https://xon.sh/)
53
+ - [nushell](https://www.nushell.sh/)
54
54
 
55
55
  ## Configuration
56
56
 
@@ -5,6 +5,8 @@ import { Shell, supportedShells as shells, setupZshDotfiles, setupBashPreExec }
5
5
  import { inferShell } from "../utils/shell.js";
6
6
  import { loadConfig } from "../utils/config.js";
7
7
  import log from "../utils/log.js";
8
+ import { loadAliases } from "../runtime/alias.js";
9
+ import { loadLocalSpecsSet } from "../runtime/runtime.js";
8
10
  export const supportedShells = shells.join(", ");
9
11
  export const action = (program) => async (options) => {
10
12
  const inISTerm = process.env.ISTERM === "1";
@@ -15,6 +17,7 @@ export const action = (program) => async (options) => {
15
17
  if (options.verbose)
16
18
  await log.enable();
17
19
  await loadConfig(program);
20
+ await loadLocalSpecsSet();
18
21
  const shell = options.shell ?? (await inferShell());
19
22
  if (shell == null) {
20
23
  program.error(`Unable to identify shell, use the -s/--shell option to provide your shell`, { exitCode: 1 });
@@ -28,5 +31,6 @@ export const action = (program) => async (options) => {
28
31
  else if (shell == Shell.Bash) {
29
32
  await setupBashPreExec();
30
33
  }
34
+ await loadAliases(shell);
31
35
  await render(shell, options.test ?? false, options.parentTermExit ?? false);
32
36
  };
@@ -2,21 +2,23 @@
2
2
  // Licensed under the MIT License.
3
3
  import convert from "color-convert";
4
4
  import os from "node:os";
5
- import { Shell } from "../utils/shell.js";
5
+ import { getShellPromptRewrites, Shell } from "../utils/shell.js";
6
6
  import log from "../utils/log.js";
7
7
  import { getConfig } from "../utils/config.js";
8
8
  const maxPromptPollDistance = 10;
9
9
  export class CommandManager {
10
10
  #activeCommand;
11
- #previousCommandLines;
12
11
  #terminal;
12
+ #previousCommandLines;
13
13
  #shell;
14
+ #promptRewrites;
14
15
  #supportsProperOscPlacements = os.platform() !== "win32";
15
16
  constructor(terminal, shell) {
16
17
  this.#terminal = terminal;
17
18
  this.#shell = shell;
18
19
  this.#activeCommand = {};
19
20
  this.#previousCommandLines = new Set();
21
+ this.#promptRewrites = getShellPromptRewrites(shell);
20
22
  if (this.#supportsProperOscPlacements) {
21
23
  this.#terminal.parser.registerCsiHandler({ final: "J" }, (params) => {
22
24
  if (params.at(0) == 3 || params.at(0) == 2) {
@@ -27,12 +29,11 @@ export class CommandManager {
27
29
  }
28
30
  }
29
31
  handlePromptStart() {
30
- if (this.#activeCommand.promptStartMarker?.line == -1) {
31
- this.#previousCommandLines = new Set();
32
- }
33
32
  this.#activeCommand = { promptStartMarker: this.#terminal.registerMarker(0), hasOutput: false, cursorTerminated: false };
34
33
  }
35
34
  handlePromptEnd() {
35
+ if (this.#activeCommand.promptEndMarker != null)
36
+ return;
36
37
  this.#activeCommand.promptEndMarker = this.#terminal.registerMarker(0);
37
38
  if (this.#activeCommand.promptEndMarker?.line === this.#terminal.buffer.active.cursorY) {
38
39
  this.#activeCommand.promptEndX = this.#terminal.buffer.active.cursorX;
@@ -46,6 +47,15 @@ export class CommandManager {
46
47
  this.handlePromptStart();
47
48
  this.#previousCommandLines = new Set();
48
49
  }
50
+ _extractPrompt(lineText, patterns) {
51
+ for (const { regex, postfix } of patterns) {
52
+ const customPrompt = lineText.match(new RegExp(regex))?.groups?.prompt;
53
+ const adjustedPrompt = this._adjustPrompt(customPrompt, lineText, postfix);
54
+ if (adjustedPrompt) {
55
+ return adjustedPrompt;
56
+ }
57
+ }
58
+ }
49
59
  _getWindowsPrompt(y) {
50
60
  const line = this.#terminal.buffer.active.getLine(y);
51
61
  if (!line) {
@@ -59,13 +69,9 @@ export class CommandManager {
59
69
  const inshellisenseConfig = getConfig();
60
70
  if (this.#shell == Shell.Bash) {
61
71
  if (inshellisenseConfig?.prompt?.bash != null) {
62
- for (const { regex, postfix } of inshellisenseConfig.prompt.bash) {
63
- const customPrompt = lineText.match(new RegExp(regex))?.groups?.prompt;
64
- const adjustedPrompt = this._adjustPrompt(customPrompt, lineText, postfix);
65
- if (adjustedPrompt) {
66
- return adjustedPrompt;
67
- }
68
- }
72
+ const extractedPrompt = this._extractPrompt(lineText, inshellisenseConfig.prompt.bash);
73
+ if (extractedPrompt)
74
+ return extractedPrompt;
69
75
  }
70
76
  const bashPrompt = lineText.match(/^(?<prompt>.*\$\s?)/)?.groups?.prompt;
71
77
  if (bashPrompt) {
@@ -75,15 +81,25 @@ export class CommandManager {
75
81
  }
76
82
  }
77
83
  }
84
+ if (this.#shell == Shell.Nushell) {
85
+ if (inshellisenseConfig?.prompt?.nu != null) {
86
+ const extractedPrompt = this._extractPrompt(lineText, inshellisenseConfig.prompt.nu);
87
+ if (extractedPrompt)
88
+ return extractedPrompt;
89
+ }
90
+ const nushellPrompt = lineText.match(/(?<prompt>.*>\s?)/)?.groups?.prompt;
91
+ if (nushellPrompt) {
92
+ const adjustedPrompt = this._adjustPrompt(nushellPrompt, lineText, ">");
93
+ if (adjustedPrompt) {
94
+ return adjustedPrompt;
95
+ }
96
+ }
97
+ }
78
98
  if (this.#shell == Shell.Xonsh) {
79
99
  if (inshellisenseConfig?.prompt?.xonsh != null) {
80
- for (const { regex, postfix } of inshellisenseConfig.prompt.xonsh) {
81
- const customPrompt = lineText.match(new RegExp(regex))?.groups?.prompt;
82
- const adjustedPrompt = this._adjustPrompt(customPrompt, lineText, postfix);
83
- if (adjustedPrompt) {
84
- return adjustedPrompt;
85
- }
86
- }
100
+ const extractedPrompt = this._extractPrompt(lineText, inshellisenseConfig.prompt.xonsh);
101
+ if (extractedPrompt)
102
+ return extractedPrompt;
87
103
  }
88
104
  let xonshPrompt = lineText.match(/(?<prompt>.*@\s?)/)?.groups?.prompt;
89
105
  if (xonshPrompt) {
@@ -102,22 +118,14 @@ export class CommandManager {
102
118
  }
103
119
  if (this.#shell == Shell.Powershell || this.#shell == Shell.Pwsh) {
104
120
  if (inshellisenseConfig?.prompt?.powershell != null) {
105
- for (const { regex, postfix } of inshellisenseConfig.prompt.powershell) {
106
- const customPrompt = lineText.match(new RegExp(regex))?.groups?.prompt;
107
- const adjustedPrompt = this._adjustPrompt(customPrompt, lineText, postfix);
108
- if (adjustedPrompt) {
109
- return adjustedPrompt;
110
- }
111
- }
121
+ const extractedPrompt = this._extractPrompt(lineText, inshellisenseConfig.prompt.powershell);
122
+ if (extractedPrompt)
123
+ return extractedPrompt;
112
124
  }
113
125
  if (inshellisenseConfig?.prompt?.pwsh != null) {
114
- for (const { regex, postfix } of inshellisenseConfig.prompt.pwsh) {
115
- const customPrompt = lineText.match(new RegExp(regex))?.groups?.prompt;
116
- const adjustedPrompt = this._adjustPrompt(customPrompt, lineText, postfix);
117
- if (adjustedPrompt) {
118
- return adjustedPrompt;
119
- }
120
- }
126
+ const extractedPrompt = this._extractPrompt(lineText, inshellisenseConfig.prompt.pwsh);
127
+ if (extractedPrompt)
128
+ return extractedPrompt;
121
129
  }
122
130
  const pwshPrompt = lineText.match(/(?<prompt>(\(.+\)\s)?(?:PS.+>\s?))/)?.groups?.prompt;
123
131
  if (pwshPrompt) {
@@ -195,7 +203,7 @@ export class CommandManager {
195
203
  // if we haven't fond the prompt yet, poll over the next 5 lines searching for it
196
204
  if (this.#activeCommand.promptText == null && withinPollDistance) {
197
205
  for (let i = globalCursorPosition; i < this.#activeCommand.promptEndMarker.line + maxPromptPollDistance; i++) {
198
- if (this.#previousCommandLines.has(i))
206
+ if (this.#previousCommandLines.has(i) && !this.#promptRewrites)
199
207
  continue;
200
208
  const promptResult = this._getWindowsPrompt(i);
201
209
  if (promptResult != null) {
@@ -212,9 +220,13 @@ export class CommandManager {
212
220
  let lineY = this.#activeCommand.promptEndMarker.line;
213
221
  let line = this.#terminal.buffer.active.getLine(this.#activeCommand.promptEndMarker.line);
214
222
  let command = "";
223
+ let wrappedCommand = "";
215
224
  let suggestions = "";
225
+ let isWrapped = false;
216
226
  for (;;) {
217
227
  for (let i = lineY == this.#activeCommand.promptEndMarker.line ? this.#activeCommand.promptText.length : 0; i < this.#terminal.cols; i++) {
228
+ if (command.endsWith(" "))
229
+ break; // assume that a command that ends with 4 spaces is terminated, avoids capturing right prompts
218
230
  const cell = line?.getCell(i);
219
231
  if (cell == null)
220
232
  continue;
@@ -222,6 +234,7 @@ export class CommandManager {
222
234
  const cleanedChars = chars == "" ? " " : chars;
223
235
  if (!this._isSuggestion(cell) && suggestions.length == 0) {
224
236
  command += cleanedChars;
237
+ wrappedCommand += cleanedChars;
225
238
  }
226
239
  else {
227
240
  suggestions += cleanedChars;
@@ -229,11 +242,16 @@ export class CommandManager {
229
242
  }
230
243
  lineY += 1;
231
244
  line = this.#terminal.buffer.active.getLine(lineY);
232
- if (!line?.isWrapped) {
245
+ const wrapped = line?.isWrapped || this.#terminal.buffer.active.cursorY + this.#terminal.buffer.active.baseY != lineY - 1;
246
+ isWrapped = isWrapped || wrapped;
247
+ if (!wrapped) {
233
248
  break;
234
249
  }
250
+ wrappedCommand = "";
235
251
  }
236
- const cursorAtEndOfInput = (this.#activeCommand.promptText.length + command.trim().length) % this.#terminal.cols <= this.#terminal.buffer.active.cursorX;
252
+ const cursorAtEndOfInput = isWrapped
253
+ ? wrappedCommand.trim().length % this.#terminal.cols <= this.#terminal.buffer.active.cursorX
254
+ : (this.#activeCommand.promptText.length + command.trimEnd().length) % this.#terminal.cols <= this.#terminal.buffer.active.cursorX;
237
255
  let hasOutput = false;
238
256
  let cell = undefined;
239
257
  for (let i = 0; i < this.#terminal.cols; i++) {
@@ -245,7 +263,10 @@ export class CommandManager {
245
263
  break;
246
264
  }
247
265
  }
248
- const commandPostfix = this.#activeCommand.promptText.length + command.trim().length < this.#terminal.buffer.active.cursorX ? " " : "";
266
+ const postfixActive = isWrapped
267
+ ? wrappedCommand.trim().length < this.#terminal.buffer.active.cursorX
268
+ : this.#activeCommand.promptText.length + command.trimEnd().length < this.#terminal.buffer.active.cursorX;
269
+ const commandPostfix = postfixActive ? " " : "";
249
270
  this.#activeCommand.persistentOutput = this.#activeCommand.hasOutput && hasOutput;
250
271
  this.#activeCommand.hasOutput = hasOutput;
251
272
  this.#activeCommand.suggestionsText = suggestions.trim();
@@ -7,7 +7,7 @@ import path from "node:path";
7
7
  import url from "node:url";
8
8
  import fs from "node:fs";
9
9
  import pty from "@homebridge/node-pty-prebuilt-multiarch";
10
- import { Shell, getPythonPath, userZdotdir, zdotdir } from "../utils/shell.js";
10
+ import { Shell, userZdotdir, zdotdir } from "../utils/shell.js";
11
11
  import { IsTermOscPs, IstermOscPt, IstermPromptStart, IstermPromptEnd } from "../utils/ansi.js";
12
12
  import xterm from "@xterm/headless";
13
13
  import { CommandManager } from "./commandManager.js";
@@ -143,6 +143,26 @@ export class ISTerm {
143
143
  cursorY: this.#term.buffer.active.cursorY,
144
144
  };
145
145
  }
146
+ _sameAccent(baseCell, targetCell) {
147
+ return baseCell?.isBold() == targetCell?.isBold() && baseCell?.isItalic() == targetCell?.isItalic() && baseCell?.isUnderline() == targetCell?.isUnderline();
148
+ }
149
+ _getAnsiAccents(cell) {
150
+ if (cell == null)
151
+ return "";
152
+ let boldAnsi = "";
153
+ if (cell.isBold()) {
154
+ boldAnsi = "\x1b[1m";
155
+ }
156
+ let italicAnsi = "";
157
+ if (cell.isItalic()) {
158
+ italicAnsi = "\x1b[3m";
159
+ }
160
+ let underlineAnsi = "";
161
+ if (cell.isUnderline()) {
162
+ underlineAnsi = "\x1b[4m";
163
+ }
164
+ return boldAnsi + italicAnsi + underlineAnsi;
165
+ }
146
166
  _sameColor(baseCell, targetCell) {
147
167
  return (baseCell?.getBgColorMode() == targetCell?.getBgColorMode() &&
148
168
  baseCell?.getBgColor() == targetCell?.getBgColor() &&
@@ -190,6 +210,9 @@ export class ISTerm {
190
210
  if (!this._sameColor(prevCell, cell)) {
191
211
  ansiLine.push(this._getAnsiColors(cell));
192
212
  }
213
+ if (!this._sameAccent(prevCell, cell)) {
214
+ ansiLine.push(this._getAnsiAccents(cell));
215
+ }
193
216
  ansiLine.push(chars == "" ? " " : chars);
194
217
  prevCell = cell;
195
218
  }
@@ -214,12 +237,12 @@ export class ISTerm {
214
237
  }
215
238
  }
216
239
  export const spawn = async (options) => {
217
- const { shellTarget, shellArgs } = await convertToPtyTarget(options.shell);
240
+ const { shellTarget, shellArgs } = await convertToPtyTarget(options.shell, options.underTest);
218
241
  return new ISTerm({ ...options, shellTarget, shellArgs });
219
242
  };
220
- const convertToPtyTarget = async (shell) => {
243
+ const convertToPtyTarget = async (shell, underTest) => {
221
244
  const platform = os.platform();
222
- let shellTarget = shell == Shell.Bash && platform == "win32" ? await gitBashPath() : platform == "win32" ? `${shell}.exe` : shell;
245
+ const shellTarget = shell == Shell.Bash && platform == "win32" ? await gitBashPath() : platform == "win32" ? `${shell}.exe` : shell;
223
246
  const shellFolderPath = path.join(path.dirname(url.fileURLToPath(import.meta.url)), "..", "..", "shell");
224
247
  let shellArgs = [];
225
248
  switch (shell) {
@@ -241,10 +264,14 @@ const convertToPtyTarget = async (shell) => {
241
264
  path.join(os.homedir(), ".config", "xonsh", "rc.d"),
242
265
  ];
243
266
  const configs = [sharedConfig, ...userConfigs].filter((config) => fs.existsSync(config));
244
- shellArgs = ["-m", "xonsh", "--rc", ...configs, path.join(shellFolderPath, "shellIntegration.xsh")];
245
- shellTarget = await getPythonPath();
267
+ shellArgs = ["--rc", ...configs, path.join(shellFolderPath, "shellIntegration.xsh")];
246
268
  break;
247
269
  }
270
+ case Shell.Nushell:
271
+ shellArgs = ["-e", `source \`${path.join(shellFolderPath, "shellIntegration.nu")}\``];
272
+ if (underTest)
273
+ shellArgs.push("-n");
274
+ break;
248
275
  }
249
276
  return { shellTarget, shellArgs };
250
277
  };
@@ -252,8 +279,9 @@ const convertToPtyEnv = (shell, underTest) => {
252
279
  const env = {
253
280
  ...process.env,
254
281
  ISTERM: "1",
255
- ISTERM_TESTING: underTest ? "1" : undefined,
256
282
  };
283
+ if (underTest)
284
+ env.ISTERM_TESTING = "1";
257
285
  switch (shell) {
258
286
  case Shell.Cmd: {
259
287
  if (underTest) {
@@ -0,0 +1,60 @@
1
+ // Copyright (c) Microsoft Corporation.
2
+ // Licensed under the MIT License.
3
+ import log from "../utils/log.js";
4
+ import { gitBashPath, Shell } from "../utils/shell.js";
5
+ import { parseCommand } from "./parser.js";
6
+ import { buildExecuteShellCommand } from "./utils.js";
7
+ import os from "node:os";
8
+ const loadedAliases = {};
9
+ const platform = os.platform();
10
+ const executeShellCommand = buildExecuteShellCommand(5000);
11
+ const loadBashAliases = async () => {
12
+ const shellTarget = platform == "win32" ? await gitBashPath() : Shell.Bash;
13
+ const { stdout, stderr, status } = await executeShellCommand({ command: shellTarget, args: ["-i", "-c", "alias"], cwd: process.cwd() });
14
+ if (status !== 0) {
15
+ log.debug({ msg: "failed to load bash aliases", stderr, status });
16
+ return;
17
+ }
18
+ return stdout
19
+ .trim()
20
+ .split("\n")
21
+ .forEach((line) => {
22
+ const [alias, ...commandSegments] = line.replace("alias ", "").replaceAll("'\\''", "'").split("=");
23
+ loadedAliases[alias] = parseCommand(commandSegments.join("=").slice(1, -1) + " ");
24
+ });
25
+ };
26
+ const loadZshAliases = async () => {
27
+ const { stdout, stderr, status } = await executeShellCommand({ command: Shell.Zsh, args: ["-i", "-c", "alias"], cwd: process.cwd() });
28
+ if (status !== 0) {
29
+ log.debug({ msg: "failed to load zsh aliases", stderr, status });
30
+ return;
31
+ }
32
+ return stdout
33
+ .trim()
34
+ .split("\n")
35
+ .forEach((line) => {
36
+ const [alias, ...commandSegments] = line.replaceAll("'\\''", "'").split("=");
37
+ loadedAliases[alias] = parseCommand(commandSegments.join("=").slice(1, -1) + " ");
38
+ });
39
+ };
40
+ export const loadAliases = async (shell) => {
41
+ switch (shell) {
42
+ case Shell.Bash:
43
+ await loadBashAliases();
44
+ break;
45
+ case Shell.Zsh:
46
+ await loadZshAliases();
47
+ break;
48
+ }
49
+ return [];
50
+ };
51
+ export const aliasExpand = (command) => {
52
+ if (!command.at(0)?.complete)
53
+ return command;
54
+ const alias = loadedAliases[command.at(0)?.token ?? ""];
55
+ if (alias) {
56
+ log.debug({ msg: "expanding alias", alias, command: command.slice(1) });
57
+ return [...alias, ...command.slice(1)];
58
+ }
59
+ return command;
60
+ };
@@ -8,25 +8,31 @@ import path from "node:path";
8
8
  import { parseCommand } from "./parser.js";
9
9
  import { getArgDrivenRecommendation, getSubcommandDrivenRecommendation } from "./suggestion.js";
10
10
  import { buildExecuteShellCommand, resolveCwd } from "./utils.js";
11
+ import { aliasExpand } from "./alias.js";
12
+ import { getConfig } from "../utils/config.js";
13
+ import log from "../utils/log.js";
11
14
  // eslint-disable-next-line @typescript-eslint/no-explicit-any -- recursive type, setting as any
12
15
  const specSet = {};
13
- speclist.forEach((s) => {
14
- let activeSet = specSet;
15
- const specRoutes = s.split("/");
16
- specRoutes.forEach((route, idx) => {
17
- if (typeof activeSet !== "object") {
18
- return;
19
- }
20
- if (idx === specRoutes.length - 1) {
21
- const prefix = versionedSpeclist.includes(s) ? "/index.js" : `.js`;
22
- activeSet[route] = `@withfig/autocomplete/build/${s}${prefix}`;
23
- }
24
- else {
25
- activeSet[route] = activeSet[route] || {};
26
- activeSet = activeSet[route];
27
- }
16
+ function loadSpecsSet(speclist, versionedSpeclist, specsPath) {
17
+ speclist.forEach((s) => {
18
+ let activeSet = specSet;
19
+ const specRoutes = s.split("/");
20
+ specRoutes.forEach((route, idx) => {
21
+ if (typeof activeSet !== "object") {
22
+ return;
23
+ }
24
+ if (idx === specRoutes.length - 1) {
25
+ const prefix = versionedSpeclist.includes(s) ? "/index.js" : `.js`;
26
+ activeSet[route] = `${specsPath}/${s}${prefix}`;
27
+ }
28
+ else {
29
+ activeSet[route] = activeSet[route] || {};
30
+ activeSet = activeSet[route];
31
+ }
32
+ });
28
33
  });
29
- });
34
+ }
35
+ loadSpecsSet(speclist, versionedSpeclist, `@withfig/autocomplete/build`);
30
36
  const loadedSpecs = {};
31
37
  const loadSpec = async (cmd) => {
32
38
  const rootToken = cmd.at(0);
@@ -50,12 +56,32 @@ const lazyLoadSpec = async (key) => {
50
56
  const lazyLoadSpecLocation = async (location) => {
51
57
  return; //TODO: implement spec location loading
52
58
  };
59
+ export const loadLocalSpecsSet = async () => {
60
+ const specsPath = getConfig()?.specs?.path;
61
+ if (!specsPath) {
62
+ return;
63
+ }
64
+ try {
65
+ await Promise.allSettled(specsPath.map((specPath) => import(path.join(specPath, "index.js"))
66
+ .then((res) => {
67
+ const { default: speclist, diffVersionedCompletions: versionedSpeclist } = res;
68
+ loadSpecsSet(speclist, versionedSpeclist, specPath);
69
+ })
70
+ .catch((e) => {
71
+ log.debug({ msg: "load local spec failed", e: e.message, specPath });
72
+ })));
73
+ }
74
+ catch (e) {
75
+ log.debug({ msg: "load local specs failed", e: e.message, specsPath });
76
+ }
77
+ };
53
78
  export const getSuggestions = async (cmd, cwd, shell) => {
54
- const activeCmd = parseCommand(cmd);
79
+ let activeCmd = parseCommand(cmd);
55
80
  const rootToken = activeCmd.at(0);
56
81
  if (activeCmd.length === 0 || !rootToken?.complete) {
57
82
  return;
58
83
  }
84
+ activeCmd = aliasExpand(activeCmd);
59
85
  const spec = await loadSpec(activeCmd);
60
86
  if (spec == null)
61
87
  return;
@@ -127,17 +153,29 @@ const genSubcommand = async (command, parentCommand) => {
127
153
  return parentCommand.subcommands[subcommandIdx];
128
154
  }
129
155
  else {
130
- parentCommand.subcommands[subcommandIdx] = { ...subcommand, ...partSpec, loadSpec: undefined };
156
+ parentCommand.subcommands[subcommandIdx] = {
157
+ ...subcommand,
158
+ ...partSpec,
159
+ loadSpec: undefined,
160
+ };
131
161
  return parentCommand.subcommands[subcommandIdx];
132
162
  }
133
163
  }
134
164
  case "string": {
135
165
  const spec = await lazyLoadSpec(subcommand.loadSpec);
136
- parentCommand.subcommands[subcommandIdx] = { ...subcommand, ...(getSubcommand(spec) ?? []), loadSpec: undefined };
166
+ parentCommand.subcommands[subcommandIdx] = {
167
+ ...subcommand,
168
+ ...(getSubcommand(spec) ?? []),
169
+ loadSpec: undefined,
170
+ };
137
171
  return parentCommand.subcommands[subcommandIdx];
138
172
  }
139
173
  case "object": {
140
- parentCommand.subcommands[subcommandIdx] = { ...subcommand, ...(subcommand.loadSpec ?? {}), loadSpec: undefined };
174
+ parentCommand.subcommands[subcommandIdx] = {
175
+ ...subcommand,
176
+ ...(subcommand.loadSpec ?? {}),
177
+ loadSpec: undefined,
178
+ };
141
179
  return parentCommand.subcommands[subcommandIdx];
142
180
  }
143
181
  case "undefined": {
@@ -164,7 +202,10 @@ const runOption = async (tokens, option, subcommand, cwd, persistentOptions, acc
164
202
  const args = option.args instanceof Array ? option.args : [option.args];
165
203
  return runArg(tokens.slice(1), args, subcommand, cwd, persistentOptions, acceptedTokens.concat(activeToken), true, false);
166
204
  }
167
- return runSubcommand(tokens.slice(1), subcommand, cwd, persistentOptions, acceptedTokens.concat({ ...activeToken, isPersistent }));
205
+ return runSubcommand(tokens.slice(1), subcommand, cwd, persistentOptions, acceptedTokens.concat({
206
+ ...activeToken,
207
+ isPersistent,
208
+ }));
168
209
  };
169
210
  const runArg = async (tokens, args, subcommand, cwd, persistentOptions, acceptedTokens, fromOption, fromVariadic) => {
170
211
  if (args.length === 0) {
@@ -4,11 +4,20 @@ import fsAsync from "node:fs/promises";
4
4
  import log from "../utils/log.js";
5
5
  const filepathsTemplate = async (cwd) => {
6
6
  const files = await fsAsync.readdir(cwd, { withFileTypes: true });
7
- return files.filter((f) => f.isFile() || f.isDirectory()).map((f) => ({ name: f.name, priority: 55, context: { templateType: "filepaths" } }));
7
+ return files
8
+ .filter((f) => f.isFile() || f.isDirectory())
9
+ .map((f) => ({ name: f.name, priority: 55, context: { templateType: "filepaths" }, type: f.isDirectory() ? "folder" : "file" }));
8
10
  };
9
11
  const foldersTemplate = async (cwd) => {
10
12
  const files = await fsAsync.readdir(cwd, { withFileTypes: true });
11
- return files.filter((f) => f.isDirectory()).map((f) => ({ name: f.name, priority: 55, context: { templateType: "folders" } }));
13
+ return files
14
+ .filter((f) => f.isDirectory())
15
+ .map((f) => ({
16
+ name: f.name,
17
+ priority: 55,
18
+ context: { templateType: "folders" },
19
+ type: "folder",
20
+ }));
12
21
  };
13
22
  // TODO: implement history template
14
23
  const historyTemplate = () => {
@@ -3,7 +3,7 @@
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 { Shell } from "../utils/shell.js";
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
9
  const child = spawn(command, args, { cwd, env });
@@ -29,7 +29,7 @@ export const resolveCwd = async (cmdToken, cwd, shell) => {
29
29
  if (cmdToken == null)
30
30
  return { cwd, pathy: false, complete: false };
31
31
  const { token } = cmdToken;
32
- const sep = shell == Shell.Bash ? "/" : path.sep;
32
+ const sep = getPathSeperator(shell);
33
33
  if (!token.includes(sep))
34
34
  return { cwd, pathy: false, complete: false };
35
35
  const resolvedCwd = path.isAbsolute(token) ? token : path.join(cwd, token);
@@ -4,7 +4,7 @@ import readline from "node:readline";
4
4
  import ansi from "ansi-escapes";
5
5
  import chalk from "chalk";
6
6
  import log from "../utils/log.js";
7
- import { Shell } from "../utils/shell.js";
7
+ import { getBackspaceSequence } from "../utils/shell.js";
8
8
  import isterm from "../isterm/index.js";
9
9
  import { eraseLinesBelow } from "../utils/ansi.js";
10
10
  import { SuggestionManager, MAX_LINES } from "./suggestionManager.js";
@@ -120,8 +120,8 @@ export const render = async (shell, underTest, parentTermExit) => {
120
120
  term.noop();
121
121
  }
122
122
  else if (!inputHandled) {
123
- if (press.name == "backspace" && (shell === Shell.Pwsh || shell === Shell.Powershell || shell === Shell.Cmd)) {
124
- term.write("\u007F");
123
+ if (press.name == "backspace") {
124
+ term.write(getBackspaceSequence(keyPress, shell));
125
125
  }
126
126
  else {
127
127
  term.write(press.sequence);
@@ -30,6 +30,11 @@ const promptPatternsSchema = {
30
30
  required: ["regex", "postfix"],
31
31
  },
32
32
  };
33
+ const specPathsSchema = {
34
+ type: "array",
35
+ items: { type: "string" },
36
+ nullable: true,
37
+ };
33
38
  const configSchema = {
34
39
  type: "object",
35
40
  nullable: true,
@@ -52,6 +57,14 @@ const configSchema = {
52
57
  pwsh: promptPatternsSchema,
53
58
  powershell: promptPatternsSchema,
54
59
  xonsh: promptPatternsSchema,
60
+ nu: promptPatternsSchema,
61
+ },
62
+ },
63
+ specs: {
64
+ type: "object",
65
+ nullable: true,
66
+ properties: {
67
+ path: specPathsSchema,
55
68
  },
56
69
  },
57
70
  },
@@ -95,6 +108,10 @@ export const loadConfig = async (program) => {
95
108
  powershell: config.prompt?.powershell,
96
109
  xonsh: config.prompt?.xonsh,
97
110
  pwsh: config.prompt?.pwsh,
111
+ nu: config.prompt?.nu,
112
+ },
113
+ specs: {
114
+ path: [`${os.homedir()}/.fig/autocomplete/build`, ...(config?.specs?.path ?? [])],
98
115
  },
99
116
  };
100
117
  }
@@ -17,6 +17,7 @@ export var Shell;
17
17
  Shell["Fish"] = "fish";
18
18
  Shell["Cmd"] = "cmd";
19
19
  Shell["Xonsh"] = "xonsh";
20
+ Shell["Nushell"] = "nu";
20
21
  })(Shell || (Shell = {}));
21
22
  export const supportedShells = [
22
23
  Shell.Bash,
@@ -26,6 +27,7 @@ export const supportedShells = [
26
27
  Shell.Fish,
27
28
  process.platform == "win32" ? Shell.Cmd : null,
28
29
  Shell.Xonsh,
30
+ Shell.Nushell,
29
31
  ].filter((shell) => shell != null);
30
32
  export const userZdotdir = process.env?.ZDOTDIR ?? os.homedir() ?? `~`;
31
33
  export const zdotdir = path.join(os.tmpdir(), `is-zsh`);
@@ -68,9 +70,6 @@ export const gitBashPath = async () => {
68
70
  }
69
71
  throw new Error("unable to find a git bash executable installed");
70
72
  };
71
- export const getPythonPath = async () => {
72
- return await which("python", { nothrow: true });
73
- };
74
73
  const getGitBashPaths = async () => {
75
74
  const gitDirs = new Set();
76
75
  const gitExePath = await which("git.exe", { nothrow: true });
@@ -96,3 +95,7 @@ const getGitBashPaths = async () => {
96
95
  gitBashPaths.push(`${process.env["UserProfile"]}\\scoop\\apps\\git-with-openssh\\current\\bin\\bash.exe`);
97
96
  return gitBashPaths;
98
97
  };
98
+ export const getBackspaceSequence = (press, shell) => shell === Shell.Pwsh || shell === Shell.Powershell || shell === Shell.Cmd || shell === Shell.Nushell ? "\u007F" : press[1].sequence;
99
+ export const getPathSeperator = (shell) => (shell == Shell.Bash || shell == Shell.Xonsh || shell == Shell.Nushell ? "/" : path.sep);
100
+ // nu fully re-writes the prompt every keystroke resulting in duplicate start/end sequences on the same line
101
+ export const getShellPromptRewrites = (shell) => shell == Shell.Nushell;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@microsoft/inshellisense",
3
- "version": "0.0.1-rc.10",
3
+ "version": "0.0.1-rc.12",
4
4
  "description": "IDE style command line auto complete",
5
5
  "type": "module",
6
6
  "engines": {
@@ -1,16 +1,6 @@
1
1
  if [ -r ~/.bashrc ]; then
2
2
  . ~/.bashrc
3
3
  fi
4
- if [ -r /etc/profile ]; then
5
- . /etc/profile
6
- fi
7
- if [ -r ~/.bash_profile ]; then
8
- . ~/.bash_profile
9
- elif [ -r ~/.bash_login ]; then
10
- . ~/.bash_login
11
- elif [ -r ~/.profile ]; then
12
- . ~/.profile
13
- fi
14
4
 
15
5
  if [ -r ~/.inshellisense/bash-preexec.sh ]; then
16
6
  . ~/.inshellisense/bash-preexec.sh
@@ -62,7 +52,7 @@ __is_precmd() {
62
52
  __is_update_prompt() {
63
53
  if [[ "$__is_custom_PS1" == "" || "$__is_custom_PS1" != "$PS1" ]]; then
64
54
  __is_original_PS1=$PS1
65
- if [ $ISTERM_TESTING == "1" ]; then
55
+ if [[ $ISTERM_TESTING == "1" ]]; then
66
56
  __is_original_PS1="> "
67
57
  fi
68
58
  __is_custom_PS1="\[$(__is_prompt_start)\]$__is_original_PS1\[$(__is_prompt_end)\]"
@@ -0,0 +1,28 @@
1
+ let __is_escape_value = {|x| $x | str replace --all "\\" "\\\\" | str replace --all ";" "\\x3b" }
2
+
3
+ let __is_update_cwd = {
4
+ let pwd = do $__is_escape_value $env.PWD
5
+ $"\e]6973;CWD;($pwd)\a"
6
+ }
7
+ let __is_original_PROMPT_COMMAND = if 'PROMPT_COMMAND' in $env { $env.PROMPT_COMMAND } else { "" }
8
+ let __is_custom_PROMPT_COMMAND = {
9
+ let promptCommandType = $__is_original_PROMPT_COMMAND | describe
10
+ mut cmd = if $promptCommandType == "closure" { do $__is_original_PROMPT_COMMAND } else { $__is_original_PROMPT_COMMAND }
11
+ let pwd = do $__is_update_cwd
12
+ if 'ISTERM_TESTING' in $env {
13
+ $cmd = ""
14
+ }
15
+ $"\e]6973;PS\a($cmd)($pwd)"
16
+ }
17
+ $env.PROMPT_COMMAND = $__is_custom_PROMPT_COMMAND
18
+
19
+ let __is_original_PROMPT_INDICATOR = if 'PROMPT_INDICATOR' in $env { $env.PROMPT_INDICATOR } else { "" }
20
+ let __is_custom_PROMPT_INDICATOR = {
21
+ let indicatorCommandType = $__is_original_PROMPT_INDICATOR | describe
22
+ mut ind = if $indicatorCommandType == "closure" { do $__is_original_PROMPT_INDICATOR } else { $__is_original_PROMPT_INDICATOR }
23
+ if 'ISTERM_TESTING' in $env {
24
+ $ind = "> "
25
+ }
26
+ $"($ind)\e]6973;PE\a"
27
+ }
28
+ $env.PROMPT_INDICATOR = $__is_custom_PROMPT_INDICATOR
@@ -23,7 +23,7 @@ def __is_update_cwd() -> str:
23
23
  $PROMPT_FIELDS['__is_prompt_start'] = __is_prompt_start
24
24
  $PROMPT_FIELDS['__is_prompt_end'] = __is_prompt_end
25
25
  $PROMPT_FIELDS['__is_update_cwd'] = __is_update_cwd
26
- if $ISTERM_TESTING:
26
+ if 'ISTERM_TESTING' in ${...}:
27
27
  $PROMPT = "> "
28
28
 
29
29
  $PROMPT = "{__is_prompt_start}{__is_update_cwd}" + $PROMPT + "{__is_prompt_end}"