@microsoft/inshellisense 0.0.1-rc.16 → 0.0.1-rc.18
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 +6 -5
- package/build/commands/complete-action.js +10 -0
- package/build/commands/doctor.js +11 -0
- package/build/commands/init.js +13 -7
- package/build/commands/root-action.js +38 -0
- package/build/commands/root.js +1 -0
- package/build/index.js +2 -0
- package/build/runtime/alias.js +3 -3
- package/build/runtime/generator.js +1 -1
- package/build/runtime/parser.js +68 -9
- package/build/runtime/runtime.js +23 -26
- package/build/runtime/suggestion.js +21 -17
- package/build/runtime/utils.js +73 -5
- package/build/ui/ui-doctor.js +41 -0
- package/build/utils/config.js +3 -4
- package/build/utils/log.js +10 -1
- package/build/utils/shell.js +115 -9
- package/package.json +8 -6
- package/scripts/postinstall.js +9 -0
- package/shell/shellIntegration-login.zsh +6 -1
- package/shell/shellIntegration-profile.zsh +6 -1
- package/shell/shellIntegration.fish +1 -1
- package/todo.md +0 -4
package/README.md
CHANGED
|
@@ -18,7 +18,7 @@ npm install -g @microsoft/inshellisense
|
|
|
18
18
|
|
|
19
19
|
### Quickstart
|
|
20
20
|
|
|
21
|
-
After completing the installation,
|
|
21
|
+
After completing the installation, run `is doctor` to verify your installation was successful. You can run `is` to start the autocomplete session for your desired shell. Additionally, inshellisense is also aliased under `inshellisense` after installation.
|
|
22
22
|
|
|
23
23
|
### Shell Plugin
|
|
24
24
|
|
|
@@ -35,19 +35,20 @@ is init zsh >> ~/.zshrc
|
|
|
35
35
|
is init fish >> ~/.config/fish/config.fish
|
|
36
36
|
|
|
37
37
|
# pwsh
|
|
38
|
-
is init pwsh
|
|
38
|
+
is init pwsh | Add-Content $profile
|
|
39
39
|
|
|
40
40
|
# powershell
|
|
41
|
-
is init powershell
|
|
41
|
+
is init powershell | Add-Content $profile
|
|
42
42
|
|
|
43
43
|
# xonsh
|
|
44
44
|
is init xonsh >> ~/.xonshrc
|
|
45
45
|
|
|
46
46
|
# nushell
|
|
47
|
-
is init nu
|
|
47
|
+
is init nu | save $nu.env-path --append
|
|
48
48
|
```
|
|
49
49
|
|
|
50
|
-
|
|
50
|
+
> [!NOTE]
|
|
51
|
+
> When updating your shell configuration in the future, make sure the inshellisense plugin is the last command in the file. Including commands after it may break the configuration (ex. initializing your shell plugin manager after the inshellisense plugin)
|
|
51
52
|
|
|
52
53
|
### Usage
|
|
53
54
|
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
// Copyright (c) Microsoft Corporation.
|
|
2
|
+
// Licensed under the MIT License.
|
|
3
|
+
import os from "node:os";
|
|
4
|
+
import { getSuggestions } from "../runtime/runtime.js";
|
|
5
|
+
import { Shell } from "../utils/shell.js";
|
|
6
|
+
export const action = async (input) => {
|
|
7
|
+
const suggestions = await getSuggestions(input, process.cwd(), os.platform() === "win32" ? Shell.Cmd : Shell.Bash);
|
|
8
|
+
process.stdout.write(JSON.stringify(suggestions));
|
|
9
|
+
process.exit(0);
|
|
10
|
+
};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
// Copyright (c) Microsoft Corporation.
|
|
2
|
+
// Licensed under the MIT License.
|
|
3
|
+
import { Command } from "commander";
|
|
4
|
+
import { render } from "../ui/ui-doctor.js";
|
|
5
|
+
const action = async () => {
|
|
6
|
+
await render();
|
|
7
|
+
};
|
|
8
|
+
const cmd = new Command("doctor");
|
|
9
|
+
cmd.description(`checks the health of this inshellisense installation`);
|
|
10
|
+
cmd.action(action);
|
|
11
|
+
export default cmd;
|
package/build/commands/init.js
CHANGED
|
@@ -1,18 +1,24 @@
|
|
|
1
1
|
// Copyright (c) Microsoft Corporation.
|
|
2
2
|
// Licensed under the MIT License.
|
|
3
3
|
import { Command } from "commander";
|
|
4
|
-
import { initSupportedShells as shells,
|
|
4
|
+
import { createShellConfigs, initSupportedShells as shells, getShellSourceCommand } from "../utils/shell.js";
|
|
5
5
|
const supportedShells = shells.join(", ");
|
|
6
|
-
const action = (program) => async (shell) => {
|
|
6
|
+
const action = (program) => async (shell, options) => {
|
|
7
|
+
if (options.generateFullConfigs) {
|
|
8
|
+
await createShellConfigs();
|
|
9
|
+
return;
|
|
10
|
+
}
|
|
11
|
+
if (shell == null)
|
|
12
|
+
program.error(`Shell is required, supported shells: ${supportedShells}`, { exitCode: 1 });
|
|
7
13
|
if (!shells.map((s) => s.valueOf()).includes(shell)) {
|
|
8
14
|
program.error(`Unsupported shell: '${shell}', supported shells: ${supportedShells}`, { exitCode: 1 });
|
|
9
15
|
}
|
|
10
|
-
const config =
|
|
11
|
-
process.stdout.write(`\n\n
|
|
12
|
-
process.exit(0);
|
|
16
|
+
const config = getShellSourceCommand(shell);
|
|
17
|
+
process.stdout.write(`\n\n${config}`);
|
|
13
18
|
};
|
|
14
19
|
const cmd = new Command("init");
|
|
15
|
-
cmd.description(`generates shell configurations for the
|
|
16
|
-
cmd.argument("
|
|
20
|
+
cmd.description(`generates shell configurations and prints the source command for the specified shell`);
|
|
21
|
+
cmd.argument("[shell]", `shell to generate for, supported shells: ${supportedShells}`);
|
|
22
|
+
cmd.option("--generate-full-configs");
|
|
17
23
|
cmd.action(action(cmd));
|
|
18
24
|
export default cmd;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
// Copyright (c) Microsoft Corporation.
|
|
2
|
+
// Licensed under the MIT License.
|
|
3
|
+
import { render, renderConfirmation } from "../ui/ui-root.js";
|
|
4
|
+
import { Shell, supportedShells as shells, setupZshDotfiles, setupBashPreExec } from "../utils/shell.js";
|
|
5
|
+
import { inferShell } from "../utils/shell.js";
|
|
6
|
+
import { loadConfig } from "../utils/config.js";
|
|
7
|
+
import log from "../utils/log.js";
|
|
8
|
+
import { loadAliases } from "../runtime/alias.js";
|
|
9
|
+
import { loadLocalSpecsSet } from "../runtime/runtime.js";
|
|
10
|
+
import { program } from "./root.js";
|
|
11
|
+
export const supportedShells = shells.join(", ");
|
|
12
|
+
export const action = async (options) => {
|
|
13
|
+
const inISTerm = process.env.ISTERM === "1";
|
|
14
|
+
if (options.check || inISTerm) {
|
|
15
|
+
process.stdout.write(renderConfirmation(inISTerm));
|
|
16
|
+
process.exit(0);
|
|
17
|
+
}
|
|
18
|
+
if (options.verbose)
|
|
19
|
+
await log.enable();
|
|
20
|
+
await loadConfig(program);
|
|
21
|
+
await loadLocalSpecsSet();
|
|
22
|
+
const shell = options.shell ?? (await inferShell());
|
|
23
|
+
if (shell == null) {
|
|
24
|
+
program.error(`Unable to identify shell, use the -s/--shell option to provide your shell`, { exitCode: 1 });
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
if (!shells.map((s) => s.valueOf()).includes(shell)) {
|
|
28
|
+
program.error(`Unsupported shell: '${shell}', supported shells: ${supportedShells}`, { exitCode: 1 });
|
|
29
|
+
}
|
|
30
|
+
if (shell == Shell.Zsh) {
|
|
31
|
+
await setupZshDotfiles();
|
|
32
|
+
}
|
|
33
|
+
else if (shell == Shell.Bash) {
|
|
34
|
+
await setupBashPreExec();
|
|
35
|
+
}
|
|
36
|
+
await loadAliases(shell);
|
|
37
|
+
await render(shell, options.test ?? false, options.login ?? false);
|
|
38
|
+
};
|
package/build/commands/root.js
CHANGED
|
@@ -18,6 +18,7 @@ export const action = (program) => async (options) => {
|
|
|
18
18
|
await log.enable();
|
|
19
19
|
await loadConfig(program);
|
|
20
20
|
await loadLocalSpecsSet();
|
|
21
|
+
log.overrideConsole();
|
|
21
22
|
const shell = options.shell ?? (await inferShell());
|
|
22
23
|
if (shell == null) {
|
|
23
24
|
program.error(`Unable to identify shell, use the -s/--shell option to provide your shell`, { exitCode: 1 });
|
package/build/index.js
CHANGED
|
@@ -7,6 +7,7 @@ import complete from "./commands/complete.js";
|
|
|
7
7
|
import uninstall from "./commands/uninstall.js";
|
|
8
8
|
import init from "./commands/init.js";
|
|
9
9
|
import specs from "./commands/specs/root.js";
|
|
10
|
+
import doctor from "./commands/doctor.js";
|
|
10
11
|
import { action, supportedShells } from "./commands/root.js";
|
|
11
12
|
import { getVersion } from "./utils/version.js";
|
|
12
13
|
const program = new Command();
|
|
@@ -31,4 +32,5 @@ program.addCommand(complete);
|
|
|
31
32
|
program.addCommand(uninstall);
|
|
32
33
|
program.addCommand(init);
|
|
33
34
|
program.addCommand(specs);
|
|
35
|
+
program.addCommand(doctor);
|
|
34
36
|
program.parse();
|
package/build/runtime/alias.js
CHANGED
|
@@ -7,7 +7,7 @@ import { buildExecuteShellCommand } from "./utils.js";
|
|
|
7
7
|
import os from "node:os";
|
|
8
8
|
const loadedAliases = {};
|
|
9
9
|
const platform = os.platform();
|
|
10
|
-
const executeShellCommand = buildExecuteShellCommand(5000);
|
|
10
|
+
const executeShellCommand = await buildExecuteShellCommand(5000);
|
|
11
11
|
const loadBashAliases = async () => {
|
|
12
12
|
const shellTarget = platform == "win32" ? await gitBashPath() : Shell.Bash;
|
|
13
13
|
const { stdout, stderr, status } = await executeShellCommand({ command: shellTarget, args: ["-i", "-c", "alias"], cwd: process.cwd() });
|
|
@@ -20,7 +20,7 @@ const loadBashAliases = async () => {
|
|
|
20
20
|
.split("\n")
|
|
21
21
|
.forEach((line) => {
|
|
22
22
|
const [alias, ...commandSegments] = line.replace("alias ", "").replaceAll("'\\''", "'").split("=");
|
|
23
|
-
loadedAliases[alias] = parseCommand(commandSegments.join("=").slice(1, -1) + " ");
|
|
23
|
+
loadedAliases[alias] = parseCommand(commandSegments.join("=").slice(1, -1) + " ", Shell.Bash);
|
|
24
24
|
});
|
|
25
25
|
};
|
|
26
26
|
const loadZshAliases = async () => {
|
|
@@ -34,7 +34,7 @@ const loadZshAliases = async () => {
|
|
|
34
34
|
.split("\n")
|
|
35
35
|
.forEach((line) => {
|
|
36
36
|
const [alias, ...commandSegments] = line.replaceAll("'\\''", "'").split("=");
|
|
37
|
-
loadedAliases[alias] = parseCommand(commandSegments.join("=").slice(1, -1) + " ");
|
|
37
|
+
loadedAliases[alias] = parseCommand(commandSegments.join("=").slice(1, -1) + " ", Shell.Zsh);
|
|
38
38
|
});
|
|
39
39
|
};
|
|
40
40
|
export const loadAliases = async (shell) => {
|
|
@@ -17,7 +17,7 @@ const getGeneratorContext = (cwd) => {
|
|
|
17
17
|
export const runGenerator = async (generator, tokens, cwd) => {
|
|
18
18
|
// TODO: support trigger
|
|
19
19
|
const { script, postProcess, scriptTimeout, splitOn, custom, template, filterTemplateSuggestions } = generator;
|
|
20
|
-
const executeShellCommand = buildExecuteShellCommand(scriptTimeout ?? 5000);
|
|
20
|
+
const executeShellCommand = await buildExecuteShellCommand(scriptTimeout ?? 5000);
|
|
21
21
|
const suggestions = [];
|
|
22
22
|
try {
|
|
23
23
|
if (script) {
|
package/build/runtime/parser.js
CHANGED
|
@@ -1,19 +1,48 @@
|
|
|
1
1
|
// Copyright (c) Microsoft Corporation.
|
|
2
2
|
// Licensed under the MIT License.
|
|
3
|
+
import wcwidth from "wcwidth";
|
|
4
|
+
import { getShellWhitespaceEscapeChar } from "./utils.js";
|
|
3
5
|
const cmdDelim = /(\|\|)|(&&)|(;)|(\|)/;
|
|
4
6
|
const spaceRegex = /\s/;
|
|
5
|
-
export const parseCommand = (command) => {
|
|
7
|
+
export const parseCommand = (command, shell) => {
|
|
6
8
|
const lastCommand = command.split(cmdDelim).at(-1)?.trimStart();
|
|
7
|
-
|
|
9
|
+
const tokens = lastCommand ? lex(lastCommand, shell) : [];
|
|
10
|
+
return sanitizeTokens(tokens, shell);
|
|
8
11
|
};
|
|
9
|
-
const
|
|
12
|
+
const sanitizeTokens = (cmdTokens, shell) => unwrapQuotedTokens(unescapeSpaceTokens(cmdTokens, shell), shell);
|
|
13
|
+
// remove escapes around spaces
|
|
14
|
+
const unescapeSpaceTokens = (cmdTokens, shell) => {
|
|
15
|
+
const escapeChar = getShellWhitespaceEscapeChar(shell);
|
|
16
|
+
return cmdTokens.map((cmdToken) => {
|
|
17
|
+
const { token, isQuoted } = cmdToken;
|
|
18
|
+
if (!isQuoted && token.includes(`${escapeChar} `)) {
|
|
19
|
+
return { ...cmdToken, token: token.replaceAll(`${escapeChar} `, " ") };
|
|
20
|
+
}
|
|
21
|
+
return cmdToken;
|
|
22
|
+
});
|
|
23
|
+
};
|
|
24
|
+
// need to unwrap tokens that are quoted with content after the quotes like `"hello"world`
|
|
25
|
+
const unwrapQuotedTokens = (cmdTokens, shell) => {
|
|
26
|
+
const escapeChar = getShellWhitespaceEscapeChar(shell);
|
|
27
|
+
return cmdTokens.map((cmdToken) => {
|
|
28
|
+
const { token, isQuoteContinued } = cmdToken;
|
|
29
|
+
if (isQuoteContinued) {
|
|
30
|
+
const quoteChar = token[0];
|
|
31
|
+
const unquotedToken = token.replaceAll(`${escapeChar}${quoteChar}`, "\u001B").replaceAll(quoteChar, "").replaceAll("\u001B", quoteChar);
|
|
32
|
+
return { ...cmdToken, token: unquotedToken };
|
|
33
|
+
}
|
|
34
|
+
return cmdToken;
|
|
35
|
+
});
|
|
36
|
+
};
|
|
37
|
+
const lex = (command, shell) => {
|
|
10
38
|
const tokens = [];
|
|
11
|
-
|
|
39
|
+
const escapeChar = getShellWhitespaceEscapeChar(shell);
|
|
40
|
+
let [readingQuotedString, readingQuoteContinuedString, readingFlag, readingCmd] = [false, false, false, false];
|
|
12
41
|
let readingIdx = 0;
|
|
13
42
|
let readingQuoteChar = "";
|
|
14
43
|
[...command].forEach((char, idx) => {
|
|
15
|
-
const reading = readingQuotedString || readingFlag || readingCmd;
|
|
16
|
-
if (!reading && (char === `'` || char === `"`)) {
|
|
44
|
+
const reading = readingQuotedString || readingQuoteContinuedString || readingFlag || readingCmd;
|
|
45
|
+
if (!reading && (char === `'` || char === `"` || char == "`")) {
|
|
17
46
|
[readingQuotedString, readingIdx, readingQuoteChar] = [true, idx, char];
|
|
18
47
|
return;
|
|
19
48
|
}
|
|
@@ -25,46 +54,76 @@ const lex = (command) => {
|
|
|
25
54
|
[readingCmd, readingIdx] = [true, idx];
|
|
26
55
|
return;
|
|
27
56
|
}
|
|
28
|
-
if (readingQuotedString && char === readingQuoteChar && command.at(idx - 1) !== "
|
|
57
|
+
if (readingQuotedString && char === readingQuoteChar && command.at(idx - 1) !== escapeChar && !spaceRegex.test(command.at(idx + 1) ?? " ")) {
|
|
58
|
+
readingQuotedString = false;
|
|
59
|
+
readingQuoteContinuedString = true;
|
|
60
|
+
}
|
|
61
|
+
else if (readingQuotedString && char === readingQuoteChar && command.at(idx - 1) !== escapeChar) {
|
|
29
62
|
readingQuotedString = false;
|
|
30
63
|
const complete = idx + 1 < command.length && spaceRegex.test(command[idx + 1]);
|
|
31
64
|
tokens.push({
|
|
32
65
|
token: command.slice(readingIdx + 1, idx),
|
|
66
|
+
tokenLength: wcwidth(command.slice(readingIdx + 1, idx)) + 2,
|
|
33
67
|
complete,
|
|
34
68
|
isOption: false,
|
|
35
69
|
isQuoted: true,
|
|
36
70
|
});
|
|
37
71
|
}
|
|
72
|
+
else if (readingQuoteContinuedString && spaceRegex.test(char) && command.at(idx - 1) !== escapeChar) {
|
|
73
|
+
readingQuoteContinuedString = false;
|
|
74
|
+
tokens.push({
|
|
75
|
+
token: command.slice(readingIdx, idx),
|
|
76
|
+
tokenLength: wcwidth(command.slice(readingIdx, idx)),
|
|
77
|
+
complete: true,
|
|
78
|
+
isOption: false,
|
|
79
|
+
isQuoted: true,
|
|
80
|
+
isQuoteContinued: true,
|
|
81
|
+
});
|
|
82
|
+
}
|
|
38
83
|
else if ((readingFlag && spaceRegex.test(char)) || char === "=") {
|
|
39
84
|
readingFlag = false;
|
|
40
85
|
tokens.push({
|
|
41
86
|
token: command.slice(readingIdx, idx),
|
|
87
|
+
tokenLength: wcwidth(command.slice(readingIdx, idx)),
|
|
42
88
|
complete: true,
|
|
43
89
|
isOption: true,
|
|
44
90
|
});
|
|
45
91
|
}
|
|
46
|
-
else if (readingCmd && spaceRegex.test(char) && command.at(idx - 1) !==
|
|
92
|
+
else if (readingCmd && spaceRegex.test(char) && command.at(idx - 1) !== escapeChar) {
|
|
47
93
|
readingCmd = false;
|
|
48
94
|
tokens.push({
|
|
49
95
|
token: command.slice(readingIdx, idx),
|
|
96
|
+
tokenLength: wcwidth(command.slice(readingIdx, idx)),
|
|
50
97
|
complete: true,
|
|
51
98
|
isOption: false,
|
|
52
99
|
});
|
|
53
100
|
}
|
|
54
101
|
});
|
|
55
|
-
const reading = readingQuotedString || readingFlag || readingCmd;
|
|
102
|
+
const reading = readingQuotedString || readingQuoteContinuedString || readingFlag || readingCmd;
|
|
56
103
|
if (reading) {
|
|
57
104
|
if (readingQuotedString) {
|
|
58
105
|
tokens.push({
|
|
59
106
|
token: command.slice(readingIdx + 1),
|
|
107
|
+
tokenLength: wcwidth(command.slice(readingIdx + 1)) + 1,
|
|
108
|
+
complete: false,
|
|
109
|
+
isOption: false,
|
|
110
|
+
isQuoted: true,
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
else if (readingQuoteContinuedString) {
|
|
114
|
+
tokens.push({
|
|
115
|
+
token: command.slice(readingIdx),
|
|
116
|
+
tokenLength: wcwidth(command.slice(readingIdx)),
|
|
60
117
|
complete: false,
|
|
61
118
|
isOption: false,
|
|
62
119
|
isQuoted: true,
|
|
120
|
+
isQuoteContinued: true,
|
|
63
121
|
});
|
|
64
122
|
}
|
|
65
123
|
else {
|
|
66
124
|
tokens.push({
|
|
67
125
|
token: command.slice(readingIdx),
|
|
126
|
+
tokenLength: wcwidth(command.slice(readingIdx)),
|
|
68
127
|
complete: false,
|
|
69
128
|
isOption: readingFlag,
|
|
70
129
|
});
|
package/build/runtime/runtime.js
CHANGED
|
@@ -76,7 +76,7 @@ export const loadLocalSpecsSet = async () => {
|
|
|
76
76
|
}
|
|
77
77
|
};
|
|
78
78
|
export const getSuggestions = async (cmd, cwd, shell) => {
|
|
79
|
-
let activeCmd = parseCommand(cmd);
|
|
79
|
+
let activeCmd = parseCommand(cmd, shell);
|
|
80
80
|
const rootToken = activeCmd.at(0);
|
|
81
81
|
if (activeCmd.length === 0 || !rootToken?.complete) {
|
|
82
82
|
return;
|
|
@@ -94,13 +94,10 @@ export const getSuggestions = async (cmd, cwd, shell) => {
|
|
|
94
94
|
lastCommand.isPath = true;
|
|
95
95
|
lastCommand.isPathComplete = pathyComplete;
|
|
96
96
|
}
|
|
97
|
-
const result = await runSubcommand(activeCmd.slice(1), subcommand, resolvedCwd);
|
|
97
|
+
const result = await runSubcommand(activeCmd.slice(1), subcommand, resolvedCwd, shell);
|
|
98
98
|
if (result == null)
|
|
99
99
|
return;
|
|
100
|
-
|
|
101
|
-
if (pathy) {
|
|
102
|
-
charactersToDrop = pathyComplete ? 0 : path.basename(lastCommand?.token ?? "").length;
|
|
103
|
-
}
|
|
100
|
+
const charactersToDrop = lastCommand?.complete ? 0 : lastCommand?.tokenLength;
|
|
104
101
|
return { ...result, charactersToDrop };
|
|
105
102
|
};
|
|
106
103
|
export const getSpecNames = () => {
|
|
@@ -123,7 +120,7 @@ const getSubcommand = (spec) => {
|
|
|
123
120
|
}
|
|
124
121
|
return spec;
|
|
125
122
|
};
|
|
126
|
-
const executeShellCommand = buildExecuteShellCommand(5000);
|
|
123
|
+
const executeShellCommand = await buildExecuteShellCommand(5000);
|
|
127
124
|
const genSubcommand = async (command, parentCommand) => {
|
|
128
125
|
if (!parentCommand.subcommands || parentCommand.subcommands.length === 0)
|
|
129
126
|
return;
|
|
@@ -195,7 +192,7 @@ const getPersistentTokens = (tokens) => {
|
|
|
195
192
|
const getArgs = (args) => {
|
|
196
193
|
return args instanceof Array ? args : args != null ? [args] : [];
|
|
197
194
|
};
|
|
198
|
-
const runOption = async (tokens, option, subcommand, cwd, persistentOptions, acceptedTokens) => {
|
|
195
|
+
const runOption = async (tokens, option, subcommand, cwd, shell, persistentOptions, acceptedTokens) => {
|
|
199
196
|
if (tokens.length === 0) {
|
|
200
197
|
throw new Error("invalid state reached, option expected but no tokens found");
|
|
201
198
|
}
|
|
@@ -203,40 +200,40 @@ const runOption = async (tokens, option, subcommand, cwd, persistentOptions, acc
|
|
|
203
200
|
const isPersistent = persistentOptions.some((o) => (typeof o.name === "string" ? o.name === activeToken.token : o.name.includes(activeToken.token)));
|
|
204
201
|
if ((option.args instanceof Array && option.args.length > 0) || option.args != null) {
|
|
205
202
|
const args = option.args instanceof Array ? option.args : [option.args];
|
|
206
|
-
return runArg(tokens.slice(1), args, subcommand, cwd, persistentOptions, acceptedTokens.concat(activeToken), true, false);
|
|
203
|
+
return runArg(tokens.slice(1), args, subcommand, cwd, shell, persistentOptions, acceptedTokens.concat(activeToken), true, false);
|
|
207
204
|
}
|
|
208
|
-
return runSubcommand(tokens.slice(1), subcommand, cwd, persistentOptions, acceptedTokens.concat({
|
|
205
|
+
return runSubcommand(tokens.slice(1), subcommand, cwd, shell, persistentOptions, acceptedTokens.concat({
|
|
209
206
|
...activeToken,
|
|
210
207
|
isPersistent,
|
|
211
208
|
}));
|
|
212
209
|
};
|
|
213
|
-
const runArg = async (tokens, args, subcommand, cwd, persistentOptions, acceptedTokens, fromOption, fromVariadic) => {
|
|
210
|
+
const runArg = async (tokens, args, subcommand, cwd, shell, persistentOptions, acceptedTokens, fromOption, fromVariadic) => {
|
|
214
211
|
if (args.length === 0) {
|
|
215
|
-
return runSubcommand(tokens, subcommand, cwd, persistentOptions, acceptedTokens, true, !fromOption);
|
|
212
|
+
return runSubcommand(tokens, subcommand, cwd, shell, persistentOptions, acceptedTokens, true, !fromOption);
|
|
216
213
|
}
|
|
217
214
|
else if (tokens.length === 0) {
|
|
218
|
-
return await getArgDrivenRecommendation(args, subcommand, persistentOptions, undefined, acceptedTokens, fromVariadic, cwd);
|
|
215
|
+
return await getArgDrivenRecommendation(args, subcommand, persistentOptions, undefined, acceptedTokens, fromVariadic, cwd, shell);
|
|
219
216
|
}
|
|
220
217
|
else if (!tokens.at(0)?.complete) {
|
|
221
|
-
return await getArgDrivenRecommendation(args, subcommand, persistentOptions, tokens[0], acceptedTokens, fromVariadic, cwd);
|
|
218
|
+
return await getArgDrivenRecommendation(args, subcommand, persistentOptions, tokens[0], acceptedTokens, fromVariadic, cwd, shell);
|
|
222
219
|
}
|
|
223
220
|
const activeToken = tokens[0];
|
|
224
221
|
if (args.every((a) => a.isOptional)) {
|
|
225
222
|
if (activeToken.isOption) {
|
|
226
223
|
const option = getOption(activeToken, persistentOptions.concat(subcommand.options ?? []));
|
|
227
224
|
if (option != null) {
|
|
228
|
-
return runOption(tokens, option, subcommand, cwd, persistentOptions, acceptedTokens);
|
|
225
|
+
return runOption(tokens, option, subcommand, cwd, shell, persistentOptions, acceptedTokens);
|
|
229
226
|
}
|
|
230
227
|
return;
|
|
231
228
|
}
|
|
232
229
|
const nextSubcommand = await genSubcommand(activeToken.token, subcommand);
|
|
233
230
|
if (nextSubcommand != null) {
|
|
234
|
-
return runSubcommand(tokens.slice(1), nextSubcommand, cwd, persistentOptions, getPersistentTokens(acceptedTokens.concat(activeToken)));
|
|
231
|
+
return runSubcommand(tokens.slice(1), nextSubcommand, cwd, shell, persistentOptions, getPersistentTokens(acceptedTokens.concat(activeToken)));
|
|
235
232
|
}
|
|
236
233
|
}
|
|
237
234
|
const activeArg = args[0];
|
|
238
235
|
if (activeArg.isVariadic) {
|
|
239
|
-
return runArg(tokens.slice(1), args, subcommand, cwd, persistentOptions, acceptedTokens.concat(activeToken), fromOption, true);
|
|
236
|
+
return runArg(tokens.slice(1), args, subcommand, cwd, shell, persistentOptions, acceptedTokens.concat(activeToken), fromOption, true);
|
|
240
237
|
}
|
|
241
238
|
else if (activeArg.isCommand) {
|
|
242
239
|
if (tokens.length <= 0) {
|
|
@@ -248,16 +245,16 @@ const runArg = async (tokens, args, subcommand, cwd, persistentOptions, accepted
|
|
|
248
245
|
const subcommand = getSubcommand(spec);
|
|
249
246
|
if (subcommand == null)
|
|
250
247
|
return;
|
|
251
|
-
return runSubcommand(tokens.slice(1), subcommand, cwd);
|
|
248
|
+
return runSubcommand(tokens.slice(1), subcommand, cwd, shell);
|
|
252
249
|
}
|
|
253
|
-
return runArg(tokens.slice(1), args.slice(1), subcommand, cwd, persistentOptions, acceptedTokens.concat(activeToken), fromOption, false);
|
|
250
|
+
return runArg(tokens.slice(1), args.slice(1), subcommand, cwd, shell, persistentOptions, acceptedTokens.concat(activeToken), fromOption, false);
|
|
254
251
|
};
|
|
255
|
-
const runSubcommand = async (tokens, subcommand, cwd, persistentOptions = [], acceptedTokens = [], argsDepleted = false, argsUsed = false) => {
|
|
252
|
+
const runSubcommand = async (tokens, subcommand, cwd, shell, persistentOptions = [], acceptedTokens = [], argsDepleted = false, argsUsed = false) => {
|
|
256
253
|
if (tokens.length === 0) {
|
|
257
|
-
return getSubcommandDrivenRecommendation(subcommand, persistentOptions, undefined, argsDepleted, argsUsed, acceptedTokens, cwd);
|
|
254
|
+
return getSubcommandDrivenRecommendation(subcommand, persistentOptions, undefined, argsDepleted, argsUsed, acceptedTokens, cwd, shell);
|
|
258
255
|
}
|
|
259
256
|
else if (!tokens.at(0)?.complete) {
|
|
260
|
-
return getSubcommandDrivenRecommendation(subcommand, persistentOptions, tokens[0], argsDepleted, argsUsed, acceptedTokens, cwd);
|
|
257
|
+
return getSubcommandDrivenRecommendation(subcommand, persistentOptions, tokens[0], argsDepleted, argsUsed, acceptedTokens, cwd, shell);
|
|
261
258
|
}
|
|
262
259
|
const activeToken = tokens[0];
|
|
263
260
|
const activeArgsLength = subcommand.args instanceof Array ? subcommand.args.length : 1;
|
|
@@ -265,21 +262,21 @@ const runSubcommand = async (tokens, subcommand, cwd, persistentOptions = [], ac
|
|
|
265
262
|
if (activeToken.isOption) {
|
|
266
263
|
const option = getOption(activeToken, allOptions);
|
|
267
264
|
if (option != null) {
|
|
268
|
-
return runOption(tokens, option, subcommand, cwd, persistentOptions, acceptedTokens);
|
|
265
|
+
return runOption(tokens, option, subcommand, cwd, shell, persistentOptions, acceptedTokens);
|
|
269
266
|
}
|
|
270
267
|
return;
|
|
271
268
|
}
|
|
272
269
|
const nextSubcommand = await genSubcommand(activeToken.token, subcommand);
|
|
273
270
|
if (nextSubcommand != null) {
|
|
274
|
-
return runSubcommand(tokens.slice(1), nextSubcommand, cwd, getPersistentOptions(persistentOptions, subcommand.options), getPersistentTokens(acceptedTokens.concat(activeToken)));
|
|
271
|
+
return runSubcommand(tokens.slice(1), nextSubcommand, cwd, shell, getPersistentOptions(persistentOptions, subcommand.options), getPersistentTokens(acceptedTokens.concat(activeToken)));
|
|
275
272
|
}
|
|
276
273
|
if (activeArgsLength <= 0) {
|
|
277
274
|
return; // not subcommand or option & no args exist
|
|
278
275
|
}
|
|
279
276
|
const args = getArgs(subcommand.args);
|
|
280
277
|
if (args.length != 0) {
|
|
281
|
-
return runArg(tokens, args, subcommand, cwd, allOptions, acceptedTokens, false, false);
|
|
278
|
+
return runArg(tokens, args, subcommand, cwd, shell, allOptions, acceptedTokens, false, false);
|
|
282
279
|
}
|
|
283
280
|
// if the subcommand has no args specified, fallback to the subcommand and ignore this item
|
|
284
|
-
return runSubcommand(tokens.slice(1), subcommand, cwd, persistentOptions, acceptedTokens.concat(activeToken));
|
|
281
|
+
return runSubcommand(tokens.slice(1), subcommand, cwd, shell, persistentOptions, acceptedTokens.concat(activeToken));
|
|
285
282
|
};
|
|
@@ -4,7 +4,9 @@ import path from "node:path";
|
|
|
4
4
|
import { runGenerator } from "./generator.js";
|
|
5
5
|
import { runTemplates } from "./template.js";
|
|
6
6
|
import log from "../utils/log.js";
|
|
7
|
-
|
|
7
|
+
import { escapePath } from "./utils.js";
|
|
8
|
+
import { addPathSeparator, getPathDirname, removePathSeparator } from "../utils/shell.js";
|
|
9
|
+
export var SuggestionIcons;
|
|
8
10
|
(function (SuggestionIcons) {
|
|
9
11
|
SuggestionIcons["File"] = "\uD83D\uDCC4";
|
|
10
12
|
SuggestionIcons["Folder"] = "\uD83D\uDCC1";
|
|
@@ -57,7 +59,7 @@ const toSuggestion = (suggestion, name, type) => {
|
|
|
57
59
|
allNames: suggestion.name instanceof Array ? suggestion.name : [suggestion.name],
|
|
58
60
|
priority: suggestion.priority ?? 50,
|
|
59
61
|
insertValue: suggestion.insertValue,
|
|
60
|
-
|
|
62
|
+
type: suggestion.type,
|
|
61
63
|
};
|
|
62
64
|
};
|
|
63
65
|
function filter(suggestions, filterStrategy, partialCmd, suggestionType) {
|
|
@@ -79,7 +81,7 @@ function filter(suggestions, filterStrategy, partialCmd, suggestionType) {
|
|
|
79
81
|
allNames: s.name,
|
|
80
82
|
priority: s.priority ?? 50,
|
|
81
83
|
insertValue: s.insertValue,
|
|
82
|
-
|
|
84
|
+
type: s.type,
|
|
83
85
|
}
|
|
84
86
|
: undefined;
|
|
85
87
|
}
|
|
@@ -91,7 +93,7 @@ function filter(suggestions, filterStrategy, partialCmd, suggestionType) {
|
|
|
91
93
|
allNames: [s.name],
|
|
92
94
|
priority: s.priority ?? 50,
|
|
93
95
|
insertValue: s.insertValue,
|
|
94
|
-
|
|
96
|
+
type: s.type,
|
|
95
97
|
}
|
|
96
98
|
: undefined;
|
|
97
99
|
})
|
|
@@ -111,7 +113,7 @@ function filter(suggestions, filterStrategy, partialCmd, suggestionType) {
|
|
|
111
113
|
allNames: s.name,
|
|
112
114
|
insertValue: s.insertValue,
|
|
113
115
|
priority: s.priority ?? 50,
|
|
114
|
-
|
|
116
|
+
type: s.type,
|
|
115
117
|
}
|
|
116
118
|
: undefined;
|
|
117
119
|
}
|
|
@@ -123,7 +125,7 @@ function filter(suggestions, filterStrategy, partialCmd, suggestionType) {
|
|
|
123
125
|
allNames: [s.name],
|
|
124
126
|
insertValue: s.insertValue,
|
|
125
127
|
priority: s.priority ?? 50,
|
|
126
|
-
|
|
128
|
+
type: s.type,
|
|
127
129
|
}
|
|
128
130
|
: undefined;
|
|
129
131
|
})
|
|
@@ -153,13 +155,15 @@ const optionSuggestions = (options, acceptedTokens, filterStrategy, partialCmd)
|
|
|
153
155
|
const validOptions = options?.filter((o) => o.exclusiveOn?.every((exclusiveOption) => !usedOptions.has(exclusiveOption)) ?? true);
|
|
154
156
|
return filter(validOptions ?? [], filterStrategy, partialCmd, "option");
|
|
155
157
|
};
|
|
156
|
-
|
|
157
|
-
return
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
158
|
+
function adjustPathSuggestions(suggestions, partialToken, shell) {
|
|
159
|
+
return suggestions.map((s) => {
|
|
160
|
+
const pathy = getPathy(s.type);
|
|
161
|
+
const rawInsertValue = removePathSeparator(s.insertValue ?? s.name ?? "");
|
|
162
|
+
const insertValue = s.type == "folder" ? addPathSeparator(rawInsertValue, shell) : rawInsertValue;
|
|
163
|
+
const partialDir = getPathDirname(partialToken?.token ?? "", shell);
|
|
164
|
+
const fullPath = partialToken?.isPath ? `${partialDir}${insertValue}` : insertValue;
|
|
165
|
+
return pathy ? { ...s, insertValue: escapePath(fullPath, shell), name: removePathSeparator(s.name) } : s;
|
|
166
|
+
});
|
|
163
167
|
}
|
|
164
168
|
const removeAcceptedSuggestions = (suggestions, acceptedTokens) => {
|
|
165
169
|
const seen = new Set(acceptedTokens.map((t) => t.token));
|
|
@@ -179,7 +183,7 @@ const removeDuplicateSuggestion = (suggestions) => {
|
|
|
179
183
|
const removeEmptySuggestion = (suggestions) => {
|
|
180
184
|
return suggestions.filter((s) => s.name.length > 0);
|
|
181
185
|
};
|
|
182
|
-
export const getSubcommandDrivenRecommendation = async (subcommand, persistentOptions, partialToken, argsDepleted, argsFromSubcommand, acceptedTokens, cwd) => {
|
|
186
|
+
export const getSubcommandDrivenRecommendation = async (subcommand, persistentOptions, partialToken, argsDepleted, argsFromSubcommand, acceptedTokens, cwd, shell) => {
|
|
183
187
|
log.debug({ msg: "suggestion point", subcommand, persistentOptions, partialToken, argsDepleted, argsFromSubcommand, acceptedTokens, cwd });
|
|
184
188
|
if (argsDepleted && argsFromSubcommand) {
|
|
185
189
|
return;
|
|
@@ -202,10 +206,10 @@ export const getSubcommandDrivenRecommendation = async (subcommand, persistentOp
|
|
|
202
206
|
suggestions.push(...(await templateSuggestions(activeArg?.template, activeArg?.filterStrategy, partialCmd, cwd)));
|
|
203
207
|
}
|
|
204
208
|
return {
|
|
205
|
-
suggestions: removeDuplicateSuggestion(removeEmptySuggestion(removeAcceptedSuggestions(adjustPathSuggestions(suggestions.sort((a, b) => b.priority - a.priority), partialToken), acceptedTokens))),
|
|
209
|
+
suggestions: removeDuplicateSuggestion(removeEmptySuggestion(removeAcceptedSuggestions(adjustPathSuggestions(suggestions.sort((a, b) => b.priority - a.priority), partialToken, shell), acceptedTokens))),
|
|
206
210
|
};
|
|
207
211
|
};
|
|
208
|
-
export const getArgDrivenRecommendation = async (args, subcommand, persistentOptions, partialToken, acceptedTokens, variadicArgBound, cwd) => {
|
|
212
|
+
export const getArgDrivenRecommendation = async (args, subcommand, persistentOptions, partialToken, acceptedTokens, variadicArgBound, cwd, shell) => {
|
|
209
213
|
let partialCmd = partialToken?.token;
|
|
210
214
|
if (partialToken?.isPath) {
|
|
211
215
|
partialCmd = partialToken.isPathComplete ? "" : path.basename(partialCmd ?? "");
|
|
@@ -222,7 +226,7 @@ export const getArgDrivenRecommendation = async (args, subcommand, persistentOpt
|
|
|
222
226
|
suggestions.push(...optionSuggestions(allOptions, acceptedTokens, activeArg?.filterStrategy, partialCmd));
|
|
223
227
|
}
|
|
224
228
|
return {
|
|
225
|
-
suggestions: removeDuplicateSuggestion(removeEmptySuggestion(removeAcceptedSuggestions(adjustPathSuggestions(suggestions.sort((a, b) => b.priority - a.priority), partialToken), acceptedTokens))),
|
|
229
|
+
suggestions: removeDuplicateSuggestion(removeEmptySuggestion(removeAcceptedSuggestions(adjustPathSuggestions(suggestions.sort((a, b) => b.priority - a.priority), partialToken, shell), acceptedTokens))),
|
|
226
230
|
argumentDescription: activeArg.description ?? activeArg.name,
|
|
227
231
|
};
|
|
228
232
|
};
|
package/build/runtime/utils.js
CHANGED
|
@@ -3,10 +3,77 @@
|
|
|
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 {
|
|
6
|
+
import { getPathSeparator, gitBashPath, Shell } from "../utils/shell.js";
|
|
7
7
|
import log from "../utils/log.js";
|
|
8
|
-
|
|
9
|
-
|
|
8
|
+
const getExecutionShell = async () => {
|
|
9
|
+
if (process.platform !== "win32")
|
|
10
|
+
return;
|
|
11
|
+
try {
|
|
12
|
+
return await gitBashPath();
|
|
13
|
+
}
|
|
14
|
+
catch (e) {
|
|
15
|
+
log.debug({ msg: "failed to load posix shell for windows child_process.spawn, some generators might fail", error: e });
|
|
16
|
+
}
|
|
17
|
+
};
|
|
18
|
+
const bashSpecialCharacters = /[&|<>\s]/g;
|
|
19
|
+
// escape whitespace & special characters in an argument when not quoted
|
|
20
|
+
const shouldEscapeArg = (arg) => {
|
|
21
|
+
const hasSpecialCharacter = bashSpecialCharacters.test(arg);
|
|
22
|
+
const isSingleCharacter = arg.length === 1;
|
|
23
|
+
return hasSpecialCharacter && !isSingleCharacter && !isQuoted(arg, `"`);
|
|
24
|
+
};
|
|
25
|
+
/* based on libuv process.c used by nodejs, only quotes are escaped for shells. if using git bash need to escape whitespace & special characters in an argument */
|
|
26
|
+
const escapeArgs = (shell, args) => {
|
|
27
|
+
// only escape args for git bash
|
|
28
|
+
if (process.platform !== "win32" || shell == undefined)
|
|
29
|
+
return args;
|
|
30
|
+
return args.map((arg) => (shouldEscapeArg(arg) ? `"${arg.replaceAll('"', '\\"')}"` : arg));
|
|
31
|
+
};
|
|
32
|
+
const isQuoted = (value, quoteChar) => (value?.startsWith(quoteChar) && value?.endsWith(quoteChar)) ?? false;
|
|
33
|
+
const quoteString = (value, quoteChar) => {
|
|
34
|
+
if (isQuoted(value, quoteChar))
|
|
35
|
+
return value;
|
|
36
|
+
const escapedValue = value.replaceAll(`\\${quoteChar}`, quoteChar).replaceAll(quoteChar, `\\${quoteChar}`);
|
|
37
|
+
return `${quoteChar}${escapedValue}${quoteChar}`;
|
|
38
|
+
};
|
|
39
|
+
const needsQuoted = (value, quoteChar) => isQuoted(value, quoteChar) || value.includes(" ");
|
|
40
|
+
const getShellQuoteChar = (shell) => {
|
|
41
|
+
switch (shell) {
|
|
42
|
+
case Shell.Zsh:
|
|
43
|
+
case Shell.Bash:
|
|
44
|
+
case Shell.Fish:
|
|
45
|
+
return `"`;
|
|
46
|
+
case Shell.Xonsh:
|
|
47
|
+
return `'`;
|
|
48
|
+
case Shell.Nushell:
|
|
49
|
+
return "`";
|
|
50
|
+
case Shell.Pwsh:
|
|
51
|
+
case Shell.Powershell:
|
|
52
|
+
return `'`;
|
|
53
|
+
case Shell.Cmd:
|
|
54
|
+
return `"`;
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
export const getShellWhitespaceEscapeChar = (shell) => {
|
|
58
|
+
switch (shell) {
|
|
59
|
+
case Shell.Zsh:
|
|
60
|
+
case Shell.Bash:
|
|
61
|
+
case Shell.Fish:
|
|
62
|
+
case Shell.Xonsh:
|
|
63
|
+
case Shell.Nushell:
|
|
64
|
+
return "\\";
|
|
65
|
+
case Shell.Pwsh:
|
|
66
|
+
case Shell.Powershell:
|
|
67
|
+
return "`";
|
|
68
|
+
case Shell.Cmd:
|
|
69
|
+
return "^";
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
export const escapePath = (value, shell) => value != null && needsQuoted(value, getShellQuoteChar(shell)) ? quoteString(value, getShellQuoteChar(shell)) : value;
|
|
73
|
+
export const buildExecuteShellCommand = async (timeout) => async ({ command, env, args, cwd }) => {
|
|
74
|
+
const executionShell = await getExecutionShell();
|
|
75
|
+
const escapedArgs = escapeArgs(executionShell, args);
|
|
76
|
+
const child = spawn(command, escapedArgs, { cwd, env: { ...process.env, ...env, ISTERM: "1" }, shell: executionShell });
|
|
10
77
|
setTimeout(() => child.kill("SIGKILL"), timeout);
|
|
11
78
|
let stdout = "";
|
|
12
79
|
let stderr = "";
|
|
@@ -29,8 +96,9 @@ export const resolveCwd = async (cmdToken, cwd, shell) => {
|
|
|
29
96
|
if (cmdToken == null)
|
|
30
97
|
return { cwd, pathy: false, complete: false };
|
|
31
98
|
const { token: rawToken, isQuoted } = cmdToken;
|
|
32
|
-
const
|
|
33
|
-
const
|
|
99
|
+
const escapedToken = !isQuoted ? rawToken.replaceAll(" ", "\\ ") : rawToken;
|
|
100
|
+
const token = escapedToken;
|
|
101
|
+
const sep = getPathSeparator(shell);
|
|
34
102
|
if (!token.includes(sep))
|
|
35
103
|
return { cwd, pathy: false, complete: false };
|
|
36
104
|
const resolvedCwd = path.isAbsolute(token) ? token : path.join(cwd, token);
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
// Copyright (c) Microsoft Corporation.
|
|
2
|
+
// Licensed under the MIT License.
|
|
3
|
+
import chalk from "chalk";
|
|
4
|
+
import { checkLegacyConfigs, checkShellConfigs } from "../utils/shell.js";
|
|
5
|
+
export const render = async () => {
|
|
6
|
+
let errors = 0;
|
|
7
|
+
errors += await renderLegacyConfigIssues();
|
|
8
|
+
errors += renderShellConfigIssues();
|
|
9
|
+
process.exit(errors);
|
|
10
|
+
};
|
|
11
|
+
const renderLegacyConfigIssues = async () => {
|
|
12
|
+
const shellsWithLegacyConfigs = await checkLegacyConfigs();
|
|
13
|
+
if (shellsWithLegacyConfigs.length > 0) {
|
|
14
|
+
process.stderr.write(chalk.red("•") + chalk.bold(" detected legacy configurations\n"));
|
|
15
|
+
process.stderr.write(" the following shells have legacy configurations:\n");
|
|
16
|
+
shellsWithLegacyConfigs.forEach((shell) => {
|
|
17
|
+
process.stderr.write(chalk.red(" - ") + shell + "\n");
|
|
18
|
+
});
|
|
19
|
+
process.stderr.write(chalk.yellow(" remove any inshellisense configurations from your shell profile and re-add them following the instructions in the README\n"));
|
|
20
|
+
return 1;
|
|
21
|
+
}
|
|
22
|
+
else {
|
|
23
|
+
process.stdout.write(chalk.green("✓") + " no legacy configurations found\n");
|
|
24
|
+
}
|
|
25
|
+
return 0;
|
|
26
|
+
};
|
|
27
|
+
const renderShellConfigIssues = () => {
|
|
28
|
+
const shellsWithoutConfigs = checkShellConfigs();
|
|
29
|
+
if (shellsWithoutConfigs.length > 0) {
|
|
30
|
+
process.stderr.write(chalk.red("•") + " the following shells do not have configurations:\n");
|
|
31
|
+
shellsWithoutConfigs.forEach((shell) => {
|
|
32
|
+
process.stderr.write(chalk.red(" - ") + shell + "\n");
|
|
33
|
+
});
|
|
34
|
+
process.stderr.write(chalk.yellow(" run " + chalk.underline(chalk.cyan("is init --generate-full-configs")) + " to generate new configurations\n"));
|
|
35
|
+
return 1;
|
|
36
|
+
}
|
|
37
|
+
else {
|
|
38
|
+
process.stdout.write(chalk.green("✓") + " all shells have configurations\n");
|
|
39
|
+
}
|
|
40
|
+
return 0;
|
|
41
|
+
};
|
package/build/utils/config.js
CHANGED
|
@@ -125,9 +125,8 @@ export const loadConfig = async (program) => {
|
|
|
125
125
|
});
|
|
126
126
|
globalConfig.specs = { path: [`${os.homedir()}/.fig/autocomplete/build`, ...(globalConfig.specs?.path ?? [])] };
|
|
127
127
|
};
|
|
128
|
-
export const deleteCacheFolder =
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
fs.rmSync(cliConfigPath, { recursive: true });
|
|
128
|
+
export const deleteCacheFolder = () => {
|
|
129
|
+
if (fs.existsSync(cachePath)) {
|
|
130
|
+
fs.rmSync(cachePath, { recursive: true });
|
|
132
131
|
}
|
|
133
132
|
};
|
package/build/utils/log.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
// Copyright (c) Microsoft Corporation.
|
|
2
2
|
// Licensed under the MIT License.
|
|
3
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
3
4
|
import os from "node:os";
|
|
4
5
|
import path from "node:path";
|
|
5
6
|
import fs from "node:fs";
|
|
@@ -23,8 +24,16 @@ const debug = (content) => {
|
|
|
23
24
|
}
|
|
24
25
|
});
|
|
25
26
|
};
|
|
27
|
+
const getLogFunction = (level) => (...data) => debug({ msg: `console.${level}`, data: data.toString() });
|
|
28
|
+
const logConsole = {
|
|
29
|
+
...console,
|
|
30
|
+
log: getLogFunction("log"),
|
|
31
|
+
error: getLogFunction("error"),
|
|
32
|
+
};
|
|
33
|
+
// eslint-disable-next-line no-global-assign
|
|
34
|
+
const overrideConsole = () => (console = logConsole);
|
|
26
35
|
export const enable = async () => {
|
|
27
36
|
await reset();
|
|
28
37
|
logEnabled = true;
|
|
29
38
|
};
|
|
30
|
-
export default { reset, debug, enable };
|
|
39
|
+
export default { reset, debug, enable, overrideConsole };
|
package/build/utils/shell.js
CHANGED
|
@@ -8,7 +8,10 @@ import fs from "node:fs";
|
|
|
8
8
|
import url from "node:url";
|
|
9
9
|
import os from "node:os";
|
|
10
10
|
import fsAsync from "node:fs/promises";
|
|
11
|
+
import util from "node:util";
|
|
12
|
+
import childProcess from "node:child_process";
|
|
11
13
|
import log from "./log.js";
|
|
14
|
+
const exec = util.promisify(childProcess.exec);
|
|
12
15
|
export var Shell;
|
|
13
16
|
(function (Shell) {
|
|
14
17
|
Shell["Bash"] = "bash";
|
|
@@ -35,6 +38,79 @@ export const aliasSupportedShells = [Shell.Bash, Shell.Zsh];
|
|
|
35
38
|
export const userZdotdir = process.env?.ZDOTDIR ?? os.homedir() ?? `~`;
|
|
36
39
|
export const zdotdir = path.join(os.tmpdir(), `is-zsh`);
|
|
37
40
|
const configFolder = ".inshellisense";
|
|
41
|
+
export const checkShellConfigs = () => {
|
|
42
|
+
const shellsWithoutConfigs = [];
|
|
43
|
+
const configFolderPath = path.join(os.homedir(), configFolder);
|
|
44
|
+
for (const shell of supportedShells) {
|
|
45
|
+
const shellConfigName = getShellConfigName(shell);
|
|
46
|
+
if (shellConfigName == null)
|
|
47
|
+
continue;
|
|
48
|
+
if (!fs.existsSync(path.join(configFolderPath, shell, shellConfigName))) {
|
|
49
|
+
shellsWithoutConfigs.push(shell);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return shellsWithoutConfigs;
|
|
53
|
+
};
|
|
54
|
+
export const checkLegacyConfigs = async () => {
|
|
55
|
+
const shellsWithLegacyConfig = [];
|
|
56
|
+
for (const shell of supportedShells) {
|
|
57
|
+
const profilePath = await getProfilePath(shell);
|
|
58
|
+
if (profilePath != null && fs.existsSync(profilePath)) {
|
|
59
|
+
const profile = await fsAsync.readFile(profilePath, "utf8");
|
|
60
|
+
if (profile.includes("inshellisense shell plugin")) {
|
|
61
|
+
shellsWithLegacyConfig.push(shell);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return shellsWithLegacyConfig;
|
|
66
|
+
};
|
|
67
|
+
const getProfilePath = async (shell) => {
|
|
68
|
+
switch (shell) {
|
|
69
|
+
case Shell.Bash:
|
|
70
|
+
return path.join(os.homedir(), ".bashrc");
|
|
71
|
+
case Shell.Powershell:
|
|
72
|
+
return (await exec(`echo $profile`, { shell })).stdout.trim();
|
|
73
|
+
case Shell.Pwsh:
|
|
74
|
+
return (await exec(`echo $profile`, { shell })).stdout.trim();
|
|
75
|
+
case Shell.Zsh:
|
|
76
|
+
return path.join(os.homedir(), ".zshrc");
|
|
77
|
+
case Shell.Fish:
|
|
78
|
+
return path.join(os.homedir(), ".config", "fish", "config.fish");
|
|
79
|
+
case Shell.Xonsh:
|
|
80
|
+
return path.join(os.homedir(), ".xonshrc");
|
|
81
|
+
case Shell.Nushell:
|
|
82
|
+
return (await exec(`echo $nu.env-path`, { shell })).stdout.trim();
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
export const createShellConfigs = async () => {
|
|
86
|
+
const configFolderPath = path.join(os.homedir(), configFolder);
|
|
87
|
+
for (const shell of supportedShells) {
|
|
88
|
+
const shellConfigName = getShellConfigName(shell);
|
|
89
|
+
if (shellConfigName == null)
|
|
90
|
+
continue;
|
|
91
|
+
await fsAsync.mkdir(path.join(configFolderPath, shell), { recursive: true });
|
|
92
|
+
await fsAsync.writeFile(path.join(configFolderPath, shell, shellConfigName), getShellConfig(shell));
|
|
93
|
+
}
|
|
94
|
+
};
|
|
95
|
+
const getShellConfigName = (shell) => {
|
|
96
|
+
switch (shell) {
|
|
97
|
+
case Shell.Bash:
|
|
98
|
+
return "init.sh";
|
|
99
|
+
case Shell.Powershell:
|
|
100
|
+
case Shell.Pwsh:
|
|
101
|
+
return "init.ps1";
|
|
102
|
+
case Shell.Zsh:
|
|
103
|
+
return "init.zsh";
|
|
104
|
+
case Shell.Fish:
|
|
105
|
+
return "init.fish";
|
|
106
|
+
case Shell.Xonsh:
|
|
107
|
+
return "init.xsh";
|
|
108
|
+
case Shell.Nushell:
|
|
109
|
+
return "init.nu";
|
|
110
|
+
default:
|
|
111
|
+
return undefined;
|
|
112
|
+
}
|
|
113
|
+
};
|
|
38
114
|
export const setupBashPreExec = async () => {
|
|
39
115
|
const shellFolderPath = path.join(path.dirname(url.fileURLToPath(import.meta.url)), "..", "..", "shell");
|
|
40
116
|
const globalConfigPath = path.join(os.homedir(), configFolder);
|
|
@@ -50,7 +126,7 @@ export const setupZshDotfiles = async () => {
|
|
|
50
126
|
await fsAsync.cp(path.join(shellFolderPath, "shellIntegration-env.zsh"), path.join(zdotdir, ".zshenv"));
|
|
51
127
|
await fsAsync.cp(path.join(shellFolderPath, "shellIntegration-login.zsh"), path.join(zdotdir, ".zlogin"));
|
|
52
128
|
};
|
|
53
|
-
const
|
|
129
|
+
const findParentProcess = async () => {
|
|
54
130
|
try {
|
|
55
131
|
return (await find("pid", process.ppid)).at(0);
|
|
56
132
|
}
|
|
@@ -86,7 +162,7 @@ export const inferShell = async () => {
|
|
|
86
162
|
/* empty */
|
|
87
163
|
}
|
|
88
164
|
// try getting shell from parent process
|
|
89
|
-
const processResult = await
|
|
165
|
+
const processResult = await findParentProcess();
|
|
90
166
|
const name = processResult?.name;
|
|
91
167
|
return name != null ? supportedShells.find((shell) => name.includes(shell)) : undefined;
|
|
92
168
|
};
|
|
@@ -125,13 +201,43 @@ const getGitBashPaths = async () => {
|
|
|
125
201
|
return gitBashPaths;
|
|
126
202
|
};
|
|
127
203
|
export const getBackspaceSequence = (press, shell) => shell === Shell.Pwsh || shell === Shell.Powershell || shell === Shell.Cmd || shell === Shell.Nushell ? "\u007F" : press[1].sequence;
|
|
128
|
-
export const
|
|
204
|
+
export const getPathSeparator = (shell) => (shell == Shell.Bash || shell == Shell.Xonsh || shell == Shell.Nushell ? "/" : path.sep);
|
|
205
|
+
export const removePathSeparator = (dir) => {
|
|
206
|
+
return dir.endsWith("/") || dir.endsWith("\\") ? dir.slice(0, -1) : dir;
|
|
207
|
+
};
|
|
208
|
+
export const addPathSeparator = (dir, shell) => {
|
|
209
|
+
const pathSep = getPathSeparator(shell);
|
|
210
|
+
return dir.endsWith(pathSep) ? dir : dir + pathSep;
|
|
211
|
+
};
|
|
212
|
+
export const getPathDirname = (dir, shell) => {
|
|
213
|
+
const pathSep = getPathSeparator(shell);
|
|
214
|
+
return dir.endsWith(pathSep) || path.dirname(dir) == "." ? dir : addPathSeparator(path.dirname(dir), shell);
|
|
215
|
+
};
|
|
129
216
|
// nu fully re-writes the prompt every keystroke resulting in duplicate start/end sequences on the same line
|
|
130
217
|
export const getShellPromptRewrites = (shell) => shell == Shell.Nushell;
|
|
218
|
+
export const getShellSourceCommand = (shell) => {
|
|
219
|
+
switch (shell) {
|
|
220
|
+
case Shell.Bash:
|
|
221
|
+
return `[ -f ~/.inshellisense/bash/init.sh ] && source ~/.inshellisense/bash/init.sh`;
|
|
222
|
+
case Shell.Powershell:
|
|
223
|
+
return `if ( Test-Path '~/.inshellisense/powershell/init.ps1' -PathType Leaf ) { . ~/.inshellisense/powershell/init.ps1 } `;
|
|
224
|
+
case Shell.Pwsh:
|
|
225
|
+
return `if ( Test-Path '~/.inshellisense/pwsh/init.ps1' -PathType Leaf ) { . ~/.inshellisense/pwsh/init.ps1 } `;
|
|
226
|
+
case Shell.Zsh:
|
|
227
|
+
return `[[ -f ~/.inshellisense/zsh/init.zsh ]] && source ~/.inshellisense/zsh/init.zsh`;
|
|
228
|
+
case Shell.Fish:
|
|
229
|
+
return `test -f ~/.inshellisense/fish/init.fish && source ~/.inshellisense/fish/init.fish`;
|
|
230
|
+
case Shell.Xonsh:
|
|
231
|
+
return `p"~/.inshellisense/xonsh/init.xsh".exists() && source "~/.inshellisense/xonsh/init.xsh"`;
|
|
232
|
+
case Shell.Nushell:
|
|
233
|
+
return `if ( '~/.inshellisense/nu/init.nu' | path exists ) { source ~/.inshellisense/nu/init.nu } `;
|
|
234
|
+
}
|
|
235
|
+
return "";
|
|
236
|
+
};
|
|
131
237
|
export const getShellConfig = (shell) => {
|
|
132
238
|
switch (shell) {
|
|
133
239
|
case Shell.Zsh:
|
|
134
|
-
return `if [[ -z "\${ISTERM}" && $- = *i* && $- != *c* ]]; then
|
|
240
|
+
return `if [[ -z "\${ISTERM}" && $- = *i* && $- != *c* && -z "\${VSCODE_RESOLVING_ENVIRONMENT}" ]]; then
|
|
135
241
|
if [[ -o login ]]; then
|
|
136
242
|
is -s zsh --login ; exit
|
|
137
243
|
else
|
|
@@ -139,7 +245,7 @@ export const getShellConfig = (shell) => {
|
|
|
139
245
|
fi
|
|
140
246
|
fi`;
|
|
141
247
|
case Shell.Bash:
|
|
142
|
-
return `if [[ -z "\${ISTERM}" && $- = *i* && $- != *c* ]]; then
|
|
248
|
+
return `if [[ -z "\${ISTERM}" && $- = *i* && $- != *c* && -z "\${VSCODE_RESOLVING_ENVIRONMENT}" ]]; then
|
|
143
249
|
shopt -q login_shell
|
|
144
250
|
login_shell=$?
|
|
145
251
|
if [ $login_shell -eq 0 ]; then
|
|
@@ -153,12 +259,12 @@ fi`;
|
|
|
153
259
|
return `$__IsCommandFlag = ([Environment]::GetCommandLineArgs() | ForEach-Object { $_.contains("-Command") }) -contains $true
|
|
154
260
|
$__IsNoExitFlag = ([Environment]::GetCommandLineArgs() | ForEach-Object { $_.contains("-NoExit") }) -contains $true
|
|
155
261
|
$__IsInteractive = -not $__IsCommandFlag -or ($__IsCommandFlag -and $__IsNoExitFlag)
|
|
156
|
-
if ([string]::IsNullOrEmpty($env:ISTERM) -and [Environment]::UserInteractive -and $__IsInteractive) {
|
|
262
|
+
if ([string]::IsNullOrEmpty($env:ISTERM) -and [Environment]::UserInteractive -and $__IsInteractive -and [string]::IsNullOrEmpty($env:VSCODE_RESOLVING_ENVIRONMENT)) {
|
|
157
263
|
is -s ${shell}
|
|
158
264
|
Stop-Process -Id $pid
|
|
159
265
|
}`;
|
|
160
266
|
case Shell.Fish:
|
|
161
|
-
return `if test -z "$ISTERM" && status --is-interactive
|
|
267
|
+
return `if test -z "$ISTERM" && status --is-interactive && test -z "$VSCODE_RESOLVING_ENVIRONMENT"
|
|
162
268
|
if status --is-login
|
|
163
269
|
is -s fish --login ; kill %self
|
|
164
270
|
else
|
|
@@ -166,13 +272,13 @@ if ([string]::IsNullOrEmpty($env:ISTERM) -and [Environment]::UserInteractive -an
|
|
|
166
272
|
end
|
|
167
273
|
end`;
|
|
168
274
|
case Shell.Xonsh:
|
|
169
|
-
return `if 'ISTERM' not in \${...} and $XONSH_INTERACTIVE:
|
|
275
|
+
return `if 'ISTERM' not in \${...} and $XONSH_INTERACTIVE and 'VSCODE_RESOLVING_ENVIRONMENT' not in \${...}:
|
|
170
276
|
if $XONSH_LOGIN:
|
|
171
277
|
is -s xonsh --login ; exit
|
|
172
278
|
else:
|
|
173
279
|
is -s xonsh ; exit`;
|
|
174
280
|
case Shell.Nushell:
|
|
175
|
-
return `if "ISTERM" not-in $env and $nu.is-interactive {
|
|
281
|
+
return `if "ISTERM" not-in $env and $nu.is-interactive and "VSCODE_RESOLVING_ENVIRONMENT" not-in $env {
|
|
176
282
|
if $nu.is-login { is -s nu --login ; exit } else { is -s nu ; exit }
|
|
177
283
|
}`;
|
|
178
284
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@microsoft/inshellisense",
|
|
3
|
-
"version": "0.0.1-rc.
|
|
3
|
+
"version": "0.0.1-rc.18",
|
|
4
4
|
"description": "IDE style command line auto complete",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"engines": {
|
|
@@ -13,18 +13,20 @@
|
|
|
13
13
|
"files": [
|
|
14
14
|
"build/**",
|
|
15
15
|
"shell/**",
|
|
16
|
+
"scripts/**",
|
|
16
17
|
"*.md",
|
|
17
18
|
"LICENSE"
|
|
18
19
|
],
|
|
19
20
|
"scripts": {
|
|
20
21
|
"build": "tsc",
|
|
21
|
-
"dev": "node --
|
|
22
|
+
"dev": "node --import=tsx src/index.ts -V",
|
|
22
23
|
"test": "node --experimental-vm-modules node_modules/jest/bin/jest.js",
|
|
23
24
|
"test:e2e": "tui-test",
|
|
24
25
|
"lint": "eslint src/ --ext .ts,.tsx && prettier src/ --check",
|
|
25
26
|
"lint:fix": "eslint src/ --ext .ts,.tsx --fix && prettier src/ --write",
|
|
26
|
-
"debug": "node --inspect --
|
|
27
|
-
"pre-commit": "lint-staged"
|
|
27
|
+
"debug": "node --inspect --import=tsx src/index.ts -V",
|
|
28
|
+
"pre-commit": "lint-staged",
|
|
29
|
+
"postinstall": "node ./scripts/postinstall.js"
|
|
28
30
|
},
|
|
29
31
|
"repository": {
|
|
30
32
|
"type": "git",
|
|
@@ -40,7 +42,7 @@
|
|
|
40
42
|
"homepage": "https://github.com/microsoft/inshellisense#readme",
|
|
41
43
|
"dependencies": {
|
|
42
44
|
"@homebridge/node-pty-prebuilt-multiarch": "^0.11.12",
|
|
43
|
-
"@withfig/autocomplete": "2.
|
|
45
|
+
"@withfig/autocomplete": "2.675.0",
|
|
44
46
|
"@xterm/addon-unicode11": "^0.8.0",
|
|
45
47
|
"@xterm/headless": "^5.5.0",
|
|
46
48
|
"ajv": "^8.12.0",
|
|
@@ -77,7 +79,7 @@
|
|
|
77
79
|
"lint-staged": "^15.2.2",
|
|
78
80
|
"prettier": "3.0.3",
|
|
79
81
|
"ts-jest": "^29.1.1",
|
|
80
|
-
"
|
|
82
|
+
"tsx": "^4.19.1",
|
|
81
83
|
"typescript": "^5.2.2"
|
|
82
84
|
},
|
|
83
85
|
"lint-staged": {
|
|
@@ -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
|
+
}
|
|
@@ -20,4 +20,4 @@ if [ "$ISTERM_TESTING" = "1" ]
|
|
|
20
20
|
function is_user_prompt; printf '> '; end
|
|
21
21
|
end
|
|
22
22
|
|
|
23
|
-
function fish_prompt;
|
|
23
|
+
function fish_prompt; __is_prompt_start; is_user_prompt; __is_prompt_end; end
|
package/todo.md
DELETED
|
@@ -1,4 +0,0 @@
|
|
|
1
|
-
- [x] improve wrapping handling
|
|
2
|
-
`const wrapped = line?.isWrapped || (this.#terminal.buffer.active.cursorY + this.#terminal.buffer.active.baseY) != lineY`
|
|
3
|
-
`lineY < this.#terminal.rows`
|
|
4
|
-
- [ ] handle paths with spaces in them on bash (ignore pwsh for now as it fails cd anyway)
|