@microsoft/inshellisense 0.0.1-rc.2 → 0.0.1-rc.20

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.
Files changed (53) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +80 -6
  3. package/SECURITY.md +41 -41
  4. package/build/commands/complete.js +16 -0
  5. package/build/commands/doctor.js +11 -0
  6. package/build/commands/init.js +24 -0
  7. package/build/commands/root.js +27 -30
  8. package/build/commands/specs/list.js +26 -0
  9. package/build/commands/specs/root.js +8 -0
  10. package/build/commands/uninstall.js +1 -1
  11. package/build/index.js +20 -7
  12. package/build/isterm/commandManager.js +290 -0
  13. package/build/isterm/index.js +4 -0
  14. package/build/isterm/pty.js +372 -0
  15. package/build/runtime/alias.js +61 -0
  16. package/build/runtime/generator.js +24 -11
  17. package/build/runtime/parser.js +86 -16
  18. package/build/runtime/runtime.js +103 -45
  19. package/build/runtime/suggestion.js +70 -22
  20. package/build/runtime/template.js +33 -18
  21. package/build/runtime/utils.js +111 -12
  22. package/build/ui/suggestionManager.js +162 -0
  23. package/build/ui/ui-doctor.js +69 -0
  24. package/build/ui/ui-root.js +130 -64
  25. package/build/ui/ui-uninstall.js +3 -5
  26. package/build/ui/utils.js +57 -0
  27. package/build/utils/ansi.js +37 -0
  28. package/build/utils/config.js +132 -0
  29. package/build/utils/log.js +39 -0
  30. package/build/utils/shell.js +316 -0
  31. package/package.json +39 -6
  32. package/scripts/postinstall.js +9 -0
  33. package/shell/bash-preexec.sh +380 -0
  34. package/shell/shellIntegration-env.zsh +12 -0
  35. package/shell/shellIntegration-login.zsh +9 -0
  36. package/shell/shellIntegration-profile.zsh +9 -0
  37. package/shell/shellIntegration-rc.zsh +66 -0
  38. package/shell/shellIntegration.bash +125 -0
  39. package/shell/shellIntegration.fish +28 -0
  40. package/shell/shellIntegration.nu +36 -0
  41. package/shell/shellIntegration.ps1 +27 -0
  42. package/shell/shellIntegration.xsh +37 -0
  43. package/build/commands/bind.js +0 -12
  44. package/build/ui/input.js +0 -55
  45. package/build/ui/suggestions.js +0 -84
  46. package/build/ui/ui-bind.js +0 -69
  47. package/build/utils/bindings.js +0 -216
  48. package/build/utils/cache.js +0 -21
  49. package/shell/key-bindings-powershell.ps1 +0 -27
  50. package/shell/key-bindings-pwsh.ps1 +0 -27
  51. package/shell/key-bindings.bash +0 -7
  52. package/shell/key-bindings.fish +0 -8
  53. package/shell/key-bindings.zsh +0 -10
@@ -0,0 +1,316 @@
1
+ // Copyright (c) Microsoft Corporation.
2
+ // Licensed under the MIT License.
3
+ import process from "node:process";
4
+ import find from "find-process";
5
+ import path from "node:path";
6
+ import which from "which";
7
+ import fs from "node:fs";
8
+ import url from "node:url";
9
+ import os from "node:os";
10
+ import fsAsync from "node:fs/promises";
11
+ import util from "node:util";
12
+ import childProcess from "node:child_process";
13
+ import log from "./log.js";
14
+ const exec = util.promisify(childProcess.exec);
15
+ const safeExec = async (command, options) => {
16
+ const defaultOptions = { timeout: 500, env: { ISTERM: "1" } };
17
+ try {
18
+ const { stdout, stderr } = await exec(command, { ...defaultOptions, ...options });
19
+ return { stdout, stderr };
20
+ }
21
+ catch (e) {
22
+ log.debug({ msg: `error executing exec command: ${e}` });
23
+ return { stdout: undefined, stderr: undefined };
24
+ }
25
+ };
26
+ export var Shell;
27
+ (function (Shell) {
28
+ Shell["Bash"] = "bash";
29
+ Shell["Powershell"] = "powershell";
30
+ Shell["Pwsh"] = "pwsh";
31
+ Shell["Zsh"] = "zsh";
32
+ Shell["Fish"] = "fish";
33
+ Shell["Cmd"] = "cmd";
34
+ Shell["Xonsh"] = "xonsh";
35
+ Shell["Nushell"] = "nu";
36
+ })(Shell || (Shell = {}));
37
+ export const supportedShells = [
38
+ Shell.Bash,
39
+ process.platform == "win32" ? Shell.Powershell : null,
40
+ Shell.Pwsh,
41
+ Shell.Zsh,
42
+ Shell.Fish,
43
+ process.platform == "win32" ? Shell.Cmd : null,
44
+ Shell.Xonsh,
45
+ Shell.Nushell,
46
+ ].filter((shell) => shell != null);
47
+ export const initSupportedShells = supportedShells.filter((shell) => shell != Shell.Cmd);
48
+ export const aliasSupportedShells = [Shell.Bash, Shell.Zsh];
49
+ export const userZdotdir = process.env?.ZDOTDIR ?? os.homedir() ?? `~`;
50
+ export const zdotdir = path.join(os.tmpdir(), `is-zsh`);
51
+ const configFolder = ".inshellisense";
52
+ export const checkShellConfigs = () => {
53
+ const shellsWithoutConfigs = [];
54
+ const configFolderPath = path.join(os.homedir(), configFolder);
55
+ for (const shell of supportedShells) {
56
+ const shellConfigName = getShellConfigName(shell);
57
+ if (shellConfigName == null)
58
+ continue;
59
+ if (!fs.existsSync(path.join(configFolderPath, shell, shellConfigName))) {
60
+ shellsWithoutConfigs.push(shell);
61
+ }
62
+ }
63
+ return shellsWithoutConfigs;
64
+ };
65
+ export const checkLegacyConfigs = async () => {
66
+ const shellsWithLegacyConfig = [];
67
+ for (const shell of supportedShells) {
68
+ const profilePath = await getProfilePath(shell);
69
+ if (profilePath != null && fs.existsSync(profilePath)) {
70
+ const profile = await fsAsync.readFile(profilePath, "utf8");
71
+ if (profile.includes("inshellisense shell plugin")) {
72
+ shellsWithLegacyConfig.push(shell);
73
+ }
74
+ }
75
+ }
76
+ return shellsWithLegacyConfig;
77
+ };
78
+ export const checkShellConfigPlugin = async () => {
79
+ const shellsWithoutPlugin = [];
80
+ const shellsWithBadPlugin = [];
81
+ for (const shell of supportedShells) {
82
+ const profilePath = await getProfilePath(shell);
83
+ if (profilePath != null && fs.existsSync(profilePath)) {
84
+ const profile = await fsAsync.readFile(profilePath, "utf8");
85
+ const profileContainsSource = profile.includes(getShellSourceCommand(shell));
86
+ const profileEndsWithSource = profile.trimEnd().endsWith(getShellSourceCommand(shell));
87
+ if (!profileContainsSource) {
88
+ shellsWithoutPlugin.push(shell);
89
+ }
90
+ else if (!profileEndsWithSource) {
91
+ shellsWithBadPlugin.push(shell);
92
+ }
93
+ }
94
+ }
95
+ return { shellsWithoutPlugin, shellsWithBadPlugin };
96
+ };
97
+ const getProfilePath = async (shell) => {
98
+ switch (shell) {
99
+ case Shell.Bash:
100
+ return path.join(os.homedir(), ".bashrc");
101
+ case Shell.Powershell:
102
+ return (await safeExec(`echo $profile`, { shell })).stdout?.trim();
103
+ case Shell.Pwsh:
104
+ return (await safeExec(`echo $profile`, { shell })).stdout?.trim();
105
+ case Shell.Zsh:
106
+ return path.join(os.homedir(), ".zshrc");
107
+ case Shell.Fish:
108
+ return path.join(os.homedir(), ".config", "fish", "config.fish");
109
+ case Shell.Xonsh:
110
+ return path.join(os.homedir(), ".xonshrc");
111
+ case Shell.Nushell:
112
+ return (await safeExec(`echo $nu.env-path`, { shell })).stdout?.trim();
113
+ }
114
+ };
115
+ export const createShellConfigs = async () => {
116
+ const configFolderPath = path.join(os.homedir(), configFolder);
117
+ for (const shell of supportedShells) {
118
+ const shellConfigName = getShellConfigName(shell);
119
+ if (shellConfigName == null)
120
+ continue;
121
+ await fsAsync.mkdir(path.join(configFolderPath, shell), { recursive: true });
122
+ await fsAsync.writeFile(path.join(configFolderPath, shell, shellConfigName), getShellConfig(shell));
123
+ }
124
+ };
125
+ const getShellConfigName = (shell) => {
126
+ switch (shell) {
127
+ case Shell.Bash:
128
+ return "init.sh";
129
+ case Shell.Powershell:
130
+ case Shell.Pwsh:
131
+ return "init.ps1";
132
+ case Shell.Zsh:
133
+ return "init.zsh";
134
+ case Shell.Fish:
135
+ return "init.fish";
136
+ case Shell.Xonsh:
137
+ return "init.xsh";
138
+ case Shell.Nushell:
139
+ return "init.nu";
140
+ default:
141
+ return undefined;
142
+ }
143
+ };
144
+ export const setupBashPreExec = async () => {
145
+ const shellFolderPath = path.join(path.dirname(url.fileURLToPath(import.meta.url)), "..", "..", "shell");
146
+ const globalConfigPath = path.join(os.homedir(), configFolder);
147
+ if (!fs.existsSync(globalConfigPath)) {
148
+ await fsAsync.mkdir(globalConfigPath, { recursive: true });
149
+ }
150
+ await fsAsync.cp(path.join(shellFolderPath, "bash-preexec.sh"), path.join(globalConfigPath, "bash-preexec.sh"));
151
+ };
152
+ export const setupZshDotfiles = async () => {
153
+ const shellFolderPath = path.join(path.dirname(url.fileURLToPath(import.meta.url)), "..", "..", "shell");
154
+ await fsAsync.cp(path.join(shellFolderPath, "shellIntegration-rc.zsh"), path.join(zdotdir, ".zshrc"));
155
+ await fsAsync.cp(path.join(shellFolderPath, "shellIntegration-profile.zsh"), path.join(zdotdir, ".zprofile"));
156
+ await fsAsync.cp(path.join(shellFolderPath, "shellIntegration-env.zsh"), path.join(zdotdir, ".zshenv"));
157
+ await fsAsync.cp(path.join(shellFolderPath, "shellIntegration-login.zsh"), path.join(zdotdir, ".zlogin"));
158
+ };
159
+ const findParentProcess = async () => {
160
+ try {
161
+ return (await find("pid", process.ppid)).at(0);
162
+ }
163
+ catch (e) {
164
+ log.debug({ msg: `error finding parent process: ${e}` });
165
+ }
166
+ };
167
+ export const inferShell = async () => {
168
+ // try getting shell from shell specific env variables
169
+ if (process.env.NU_VERSION != null) {
170
+ return Shell.Nushell;
171
+ }
172
+ else if (process.env.XONSHRC != null) {
173
+ return Shell.Xonsh;
174
+ }
175
+ else if (process.env.FISH_VERSION != null) {
176
+ return Shell.Fish;
177
+ }
178
+ else if (process.env.ZSH_VERSION != null) {
179
+ return Shell.Zsh;
180
+ }
181
+ else if (process.env.BASH_VERSION != null) {
182
+ return Shell.Bash;
183
+ }
184
+ // try getting shell from env
185
+ try {
186
+ const name = path.parse(process.env.SHELL ?? "").name;
187
+ const shellName = supportedShells.find((shell) => name.includes(shell));
188
+ if (shellName)
189
+ return shellName;
190
+ }
191
+ catch {
192
+ /* empty */
193
+ }
194
+ // try getting shell from parent process
195
+ const processResult = await findParentProcess();
196
+ const name = processResult?.name;
197
+ return name != null ? supportedShells.find((shell) => name.includes(shell)) : undefined;
198
+ };
199
+ export const gitBashPath = async () => {
200
+ const gitBashPaths = await getGitBashPaths();
201
+ for (const gitBashPath of gitBashPaths) {
202
+ if (fs.existsSync(gitBashPath)) {
203
+ return gitBashPath;
204
+ }
205
+ }
206
+ throw new Error("unable to find a git bash executable installed");
207
+ };
208
+ const getGitBashPaths = async () => {
209
+ const gitDirs = new Set();
210
+ const gitExePath = await which("git.exe", { nothrow: true });
211
+ if (gitExePath) {
212
+ const gitExeDir = path.dirname(gitExePath);
213
+ gitDirs.add(path.resolve(gitExeDir, "../.."));
214
+ }
215
+ const addValid = (set, value) => {
216
+ if (value)
217
+ set.add(value);
218
+ };
219
+ // Add common git install locations
220
+ addValid(gitDirs, process.env["ProgramW6432"]);
221
+ addValid(gitDirs, process.env["ProgramFiles"]);
222
+ addValid(gitDirs, process.env["ProgramFiles(X86)"]);
223
+ addValid(gitDirs, `${process.env["LocalAppData"]}\\Program`);
224
+ const gitBashPaths = [];
225
+ for (const gitDir of gitDirs) {
226
+ gitBashPaths.push(`${gitDir}\\Git\\bin\\bash.exe`, `${gitDir}\\Git\\usr\\bin\\bash.exe`, `${gitDir}\\usr\\bin\\bash.exe`);
227
+ }
228
+ // Add special installs that don't follow the standard directory structure
229
+ gitBashPaths.push(`${process.env["UserProfile"]}\\scoop\\apps\\git\\current\\bin\\bash.exe`);
230
+ gitBashPaths.push(`${process.env["UserProfile"]}\\scoop\\apps\\git-with-openssh\\current\\bin\\bash.exe`);
231
+ return gitBashPaths;
232
+ };
233
+ export const getBackspaceSequence = (press, shell) => shell === Shell.Pwsh || shell === Shell.Powershell || shell === Shell.Cmd || shell === Shell.Nushell ? "\u007F" : press[1].sequence;
234
+ export const getPathSeparator = (shell) => (shell == Shell.Bash || shell == Shell.Xonsh || shell == Shell.Nushell ? "/" : path.sep);
235
+ export const removePathSeparator = (dir) => {
236
+ return dir.endsWith("/") || dir.endsWith("\\") ? dir.slice(0, -1) : dir;
237
+ };
238
+ export const addPathSeparator = (dir, shell) => {
239
+ const pathSep = getPathSeparator(shell);
240
+ return dir.endsWith(pathSep) ? dir : dir + pathSep;
241
+ };
242
+ export const getPathDirname = (dir, shell) => {
243
+ const pathSep = getPathSeparator(shell);
244
+ return dir.endsWith(pathSep) || path.dirname(dir) == "." ? dir : addPathSeparator(path.dirname(dir), shell);
245
+ };
246
+ // nu fully re-writes the prompt every keystroke resulting in duplicate start/end sequences on the same line
247
+ export const getShellPromptRewrites = (shell) => shell == Shell.Nushell;
248
+ export const getShellSourceCommand = (shell) => {
249
+ switch (shell) {
250
+ case Shell.Bash:
251
+ return `[ -f ~/.inshellisense/bash/init.sh ] && source ~/.inshellisense/bash/init.sh`;
252
+ case Shell.Powershell:
253
+ return `if ( Test-Path '~/.inshellisense/powershell/init.ps1' -PathType Leaf ) { . ~/.inshellisense/powershell/init.ps1 } `;
254
+ case Shell.Pwsh:
255
+ return `if ( Test-Path '~/.inshellisense/pwsh/init.ps1' -PathType Leaf ) { . ~/.inshellisense/pwsh/init.ps1 } `;
256
+ case Shell.Zsh:
257
+ return `[[ -f ~/.inshellisense/zsh/init.zsh ]] && source ~/.inshellisense/zsh/init.zsh`;
258
+ case Shell.Fish:
259
+ return `test -f ~/.inshellisense/fish/init.fish && source ~/.inshellisense/fish/init.fish`;
260
+ case Shell.Xonsh:
261
+ return `p"~/.inshellisense/xonsh/init.xsh".exists() && source "~/.inshellisense/xonsh/init.xsh"`;
262
+ case Shell.Nushell:
263
+ return `if ( '~/.inshellisense/nu/init.nu' | path exists ) { source ~/.inshellisense/nu/init.nu } `;
264
+ }
265
+ return "";
266
+ };
267
+ export const getShellConfig = (shell) => {
268
+ switch (shell) {
269
+ case Shell.Zsh:
270
+ return `if [[ -z "\${ISTERM}" && $- = *i* && $- != *c* && -z "\${VSCODE_RESOLVING_ENVIRONMENT}" ]]; then
271
+ if [[ -o login ]]; then
272
+ is -s zsh --login ; exit
273
+ else
274
+ is -s zsh ; exit
275
+ fi
276
+ fi`;
277
+ case Shell.Bash:
278
+ return `if [[ -z "\${ISTERM}" && $- = *i* && $- != *c* && -z "\${VSCODE_RESOLVING_ENVIRONMENT}" ]]; then
279
+ shopt -q login_shell
280
+ login_shell=$?
281
+ if [ $login_shell -eq 0 ]; then
282
+ is -s bash --login ; exit
283
+ else
284
+ is -s bash ; exit
285
+ fi
286
+ fi`;
287
+ case Shell.Powershell:
288
+ case Shell.Pwsh:
289
+ return `$__IsCommandFlag = ([Environment]::GetCommandLineArgs() | ForEach-Object { $_.contains("-Command") }) -contains $true
290
+ $__IsNoExitFlag = ([Environment]::GetCommandLineArgs() | ForEach-Object { $_.contains("-NoExit") }) -contains $true
291
+ $__IsInteractive = -not $__IsCommandFlag -or ($__IsCommandFlag -and $__IsNoExitFlag)
292
+ if ([string]::IsNullOrEmpty($env:ISTERM) -and [Environment]::UserInteractive -and $__IsInteractive -and [string]::IsNullOrEmpty($env:VSCODE_RESOLVING_ENVIRONMENT)) {
293
+ is -s ${shell}
294
+ Stop-Process -Id $pid
295
+ }`;
296
+ case Shell.Fish:
297
+ return `if test -z "$ISTERM" && status --is-interactive && test -z "$VSCODE_RESOLVING_ENVIRONMENT"
298
+ if status --is-login
299
+ is -s fish --login ; kill %self
300
+ else
301
+ is -s fish ; kill %self
302
+ end
303
+ end`;
304
+ case Shell.Xonsh:
305
+ return `if 'ISTERM' not in \${...} and $XONSH_INTERACTIVE and 'VSCODE_RESOLVING_ENVIRONMENT' not in \${...}:
306
+ if $XONSH_LOGIN:
307
+ is -s xonsh --login ; exit
308
+ else:
309
+ is -s xonsh ; exit`;
310
+ case Shell.Nushell:
311
+ return `if "ISTERM" not-in $env and $nu.is-interactive and "VSCODE_RESOLVING_ENVIRONMENT" not-in $env {
312
+ if $nu.is-login { is -s nu --login ; exit } else { is -s nu ; exit }
313
+ }`;
314
+ }
315
+ return "";
316
+ };
package/package.json CHANGED
@@ -1,8 +1,11 @@
1
1
  {
2
2
  "name": "@microsoft/inshellisense",
3
- "version": "0.0.1-rc.2",
3
+ "version": "0.0.1-rc.20",
4
4
  "description": "IDE style command line auto complete",
5
5
  "type": "module",
6
+ "engines": {
7
+ "node": ">=16.6.0 <23.0.0"
8
+ },
6
9
  "bin": {
7
10
  "inshellisense": "./build/index.js",
8
11
  "is": "./build/index.js"
@@ -10,15 +13,21 @@
10
13
  "files": [
11
14
  "build/**",
12
15
  "shell/**",
16
+ "scripts/**",
13
17
  "*.md",
14
18
  "LICENSE"
15
19
  ],
16
20
  "scripts": {
17
21
  "build": "tsc",
18
- "start": "node ./build/index.js",
22
+ "dev": "node --import=tsx src/index.ts -V",
19
23
  "test": "node --experimental-vm-modules node_modules/jest/bin/jest.js",
24
+ "test:e2e": "tui-test",
20
25
  "lint": "eslint src/ --ext .ts,.tsx && prettier src/ --check",
21
- "lint:fix": "eslint src/ --ext .ts,.tsx --fix && prettier src/ --write"
26
+ "lint:fix": "eslint src/ --ext .ts,.tsx --fix && prettier src/ --write",
27
+ "debug": "node --inspect --import=tsx src/index.ts -V",
28
+ "reinstall": "rm -rf node_modules && npm install",
29
+ "pre-commit": "lint-staged",
30
+ "postinstall": "node ./scripts/postinstall.js"
22
31
  },
23
32
  "repository": {
24
33
  "type": "git",
@@ -33,27 +42,51 @@
33
42
  },
34
43
  "homepage": "https://github.com/microsoft/inshellisense#readme",
35
44
  "dependencies": {
36
- "@withfig/autocomplete": "^2.633.0",
45
+ "@homebridge/node-pty-prebuilt-multiarch": "^0.11.14",
46
+ "@withfig/autocomplete": "2.675.0",
47
+ "@xterm/addon-unicode11": "^0.8.0",
48
+ "@xterm/headless": "^5.5.0",
49
+ "ajv": "^8.12.0",
50
+ "ansi-escapes": "^6.2.0",
51
+ "ansi-styles": "^6.2.1",
37
52
  "chalk": "^5.3.0",
53
+ "color-convert": "^2.0.1",
38
54
  "commander": "^11.0.0",
39
- "ink": "^4.4.1",
40
- "react": "^18.2.0",
55
+ "find-process": "^1.4.7",
56
+ "strip-ansi": "^7.1.0",
57
+ "toml": "^3.0.0",
58
+ "wcwidth": "^1.0.1",
59
+ "which": "^4.0.0",
41
60
  "wrap-ansi": "^8.1.0"
42
61
  },
43
62
  "devDependencies": {
63
+ "@microsoft/tui-test": "^0.0.1-rc.3",
44
64
  "@tsconfig/node18": "^18.2.2",
65
+ "@types/color-convert": "^2.0.3",
45
66
  "@types/jest": "^29.5.5",
46
67
  "@types/react": "^18.2.24",
68
+ "@types/wcwidth": "^1.0.2",
69
+ "@types/which": "^3.0.3",
47
70
  "@typescript-eslint/eslint-plugin": "^6.7.4",
48
71
  "@typescript-eslint/parser": "^6.7.4",
49
72
  "@withfig/autocomplete-types": "^1.28.0",
73
+ "@xterm/xterm": "^5.5.0",
50
74
  "eslint": "^8.51.0",
51
75
  "eslint-config-prettier": "^9.0.0",
52
76
  "eslint-plugin-header": "^3.1.1",
53
77
  "eslint-plugin-react": "^7.33.2",
78
+ "husky": "^9.0.11",
54
79
  "jest": "^29.7.0",
80
+ "lint-staged": "^15.2.2",
55
81
  "prettier": "3.0.3",
56
82
  "ts-jest": "^29.1.1",
83
+ "tsx": "^4.19.1",
57
84
  "typescript": "^5.2.2"
85
+ },
86
+ "lint-staged": {
87
+ "{,src/**/}*.{ts,tsx}": [
88
+ "eslint --fix",
89
+ "prettier --write"
90
+ ]
58
91
  }
59
92
  }
@@ -0,0 +1,9 @@
1
+ // Copyright (c) Microsoft Corporation.
2
+ // Licensed under the MIT License.
3
+
4
+ import fs from "node:fs";
5
+
6
+ if (fs.existsSync("./build/commands/init.js")) {
7
+ const init = (await import("../build/commands/init.js")).default;
8
+ init.parse(["--generate-full-configs"], { from: "user" });
9
+ }