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

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
 
@@ -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,9 +29,6 @@ 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() {
@@ -46,6 +45,15 @@ export class CommandManager {
46
45
  this.handlePromptStart();
47
46
  this.#previousCommandLines = new Set();
48
47
  }
48
+ _extractPrompt(lineText, patterns) {
49
+ for (const { regex, postfix } of patterns) {
50
+ const customPrompt = lineText.match(new RegExp(regex))?.groups?.prompt;
51
+ const adjustedPrompt = this._adjustPrompt(customPrompt, lineText, postfix);
52
+ if (adjustedPrompt) {
53
+ return adjustedPrompt;
54
+ }
55
+ }
56
+ }
49
57
  _getWindowsPrompt(y) {
50
58
  const line = this.#terminal.buffer.active.getLine(y);
51
59
  if (!line) {
@@ -59,13 +67,9 @@ export class CommandManager {
59
67
  const inshellisenseConfig = getConfig();
60
68
  if (this.#shell == Shell.Bash) {
61
69
  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
- }
70
+ const extractedPrompt = this._extractPrompt(lineText, inshellisenseConfig.prompt.bash);
71
+ if (extractedPrompt)
72
+ return extractedPrompt;
69
73
  }
70
74
  const bashPrompt = lineText.match(/^(?<prompt>.*\$\s?)/)?.groups?.prompt;
71
75
  if (bashPrompt) {
@@ -75,15 +79,25 @@ export class CommandManager {
75
79
  }
76
80
  }
77
81
  }
82
+ if (this.#shell == Shell.Nushell) {
83
+ if (inshellisenseConfig?.prompt?.nu != null) {
84
+ const extractedPrompt = this._extractPrompt(lineText, inshellisenseConfig.prompt.nu);
85
+ if (extractedPrompt)
86
+ return extractedPrompt;
87
+ }
88
+ const nushellPrompt = lineText.match(/(?<prompt>.*>\s?)/)?.groups?.prompt;
89
+ if (nushellPrompt) {
90
+ const adjustedPrompt = this._adjustPrompt(nushellPrompt, lineText, ">");
91
+ if (adjustedPrompt) {
92
+ return adjustedPrompt;
93
+ }
94
+ }
95
+ }
78
96
  if (this.#shell == Shell.Xonsh) {
79
97
  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
- }
98
+ const extractedPrompt = this._extractPrompt(lineText, inshellisenseConfig.prompt.xonsh);
99
+ if (extractedPrompt)
100
+ return extractedPrompt;
87
101
  }
88
102
  let xonshPrompt = lineText.match(/(?<prompt>.*@\s?)/)?.groups?.prompt;
89
103
  if (xonshPrompt) {
@@ -102,22 +116,14 @@ export class CommandManager {
102
116
  }
103
117
  if (this.#shell == Shell.Powershell || this.#shell == Shell.Pwsh) {
104
118
  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
- }
119
+ const extractedPrompt = this._extractPrompt(lineText, inshellisenseConfig.prompt.powershell);
120
+ if (extractedPrompt)
121
+ return extractedPrompt;
112
122
  }
113
123
  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
- }
124
+ const extractedPrompt = this._extractPrompt(lineText, inshellisenseConfig.prompt.pwsh);
125
+ if (extractedPrompt)
126
+ return extractedPrompt;
121
127
  }
122
128
  const pwshPrompt = lineText.match(/(?<prompt>(\(.+\)\s)?(?:PS.+>\s?))/)?.groups?.prompt;
123
129
  if (pwshPrompt) {
@@ -195,7 +201,7 @@ export class CommandManager {
195
201
  // if we haven't fond the prompt yet, poll over the next 5 lines searching for it
196
202
  if (this.#activeCommand.promptText == null && withinPollDistance) {
197
203
  for (let i = globalCursorPosition; i < this.#activeCommand.promptEndMarker.line + maxPromptPollDistance; i++) {
198
- if (this.#previousCommandLines.has(i))
204
+ if (this.#previousCommandLines.has(i) && !this.#promptRewrites)
199
205
  continue;
200
206
  const promptResult = this._getWindowsPrompt(i);
201
207
  if (promptResult != null) {
@@ -215,6 +221,8 @@ export class CommandManager {
215
221
  let suggestions = "";
216
222
  for (;;) {
217
223
  for (let i = lineY == this.#activeCommand.promptEndMarker.line ? this.#activeCommand.promptText.length : 0; i < this.#terminal.cols; i++) {
224
+ if (command.endsWith(" "))
225
+ break; // assume that a command that ends with 4 spaces is terminated, avoids capturing right prompts
218
226
  const cell = line?.getCell(i);
219
227
  if (cell == null)
220
228
  continue;
@@ -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) {
@@ -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);
@@ -52,6 +52,7 @@ const configSchema = {
52
52
  pwsh: promptPatternsSchema,
53
53
  powershell: promptPatternsSchema,
54
54
  xonsh: promptPatternsSchema,
55
+ nu: promptPatternsSchema,
55
56
  },
56
57
  },
57
58
  },
@@ -95,6 +96,7 @@ export const loadConfig = async (program) => {
95
96
  powershell: config.prompt?.powershell,
96
97
  xonsh: config.prompt?.xonsh,
97
98
  pwsh: config.prompt?.pwsh,
99
+ nu: config.prompt?.nu,
98
100
  },
99
101
  };
100
102
  }
@@ -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.11",
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}"