@microsoft/inshellisense 0.0.1-rc.12 → 0.0.1-rc.13
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 +27 -0
- package/build/commands/init.js +18 -0
- package/build/commands/root.js +1 -1
- package/build/index.js +2 -1
- package/build/isterm/commandManager.js +1 -1
- package/build/runtime/parser.js +18 -7
- package/build/runtime/suggestion.js +20 -2
- package/build/runtime/utils.js +3 -2
- package/build/ui/ui-root.js +1 -4
- package/build/utils/shell.js +31 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -20,6 +20,33 @@ npm install -g @microsoft/inshellisense
|
|
|
20
20
|
|
|
21
21
|
After completing the installation, you can run `is` to start the autocomplete session for your desired shell. Additionally, inshellisense is also aliased under `inshellisense` after installation.
|
|
22
22
|
|
|
23
|
+
### Shell Plugin
|
|
24
|
+
|
|
25
|
+
If you'd like to automatically start inshellisense when you open your shell, run the respective command for your shell. After running the command, inshellisense will automatically open when you start any new shell session:
|
|
26
|
+
|
|
27
|
+
```shell
|
|
28
|
+
# bash
|
|
29
|
+
is init bash >> ~/.bashrc
|
|
30
|
+
|
|
31
|
+
# zsh
|
|
32
|
+
is init zsh >> ~/.zshrc
|
|
33
|
+
|
|
34
|
+
# fish
|
|
35
|
+
is init fish >> ~/.config/fish/config.fish
|
|
36
|
+
|
|
37
|
+
# pwsh
|
|
38
|
+
is init pwsh >> $profile
|
|
39
|
+
|
|
40
|
+
# powershell
|
|
41
|
+
is init powershell >> $profile
|
|
42
|
+
|
|
43
|
+
# xonsh
|
|
44
|
+
is init xonsh >> ~/.xonshrc
|
|
45
|
+
|
|
46
|
+
# nushell
|
|
47
|
+
is init nu >> $nu.env-path
|
|
48
|
+
```
|
|
49
|
+
|
|
23
50
|
### Usage
|
|
24
51
|
|
|
25
52
|
| Action | Command | Description |
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
// Copyright (c) Microsoft Corporation.
|
|
2
|
+
// Licensed under the MIT License.
|
|
3
|
+
import { Command } from "commander";
|
|
4
|
+
import { initSupportedShells as shells, getShellConfig } from "../utils/shell.js";
|
|
5
|
+
const supportedShells = shells.join(", ");
|
|
6
|
+
const action = (program) => async (shell) => {
|
|
7
|
+
if (!shells.map((s) => s.valueOf()).includes(shell)) {
|
|
8
|
+
program.error(`Unsupported shell: '${shell}', supported shells: ${supportedShells}`, { exitCode: 1 });
|
|
9
|
+
}
|
|
10
|
+
const config = getShellConfig(shell);
|
|
11
|
+
process.stdout.write(`\n\n# ---------------- inshellisense shell plugin ----------------\n${config}`);
|
|
12
|
+
process.exit(0);
|
|
13
|
+
};
|
|
14
|
+
const cmd = new Command("init");
|
|
15
|
+
cmd.description(`generates shell configurations for the provided shell`);
|
|
16
|
+
cmd.argument("<shell>", `shell to generate configuration for, supported shells: ${supportedShells}`);
|
|
17
|
+
cmd.action(action(cmd));
|
|
18
|
+
export default cmd;
|
package/build/commands/root.js
CHANGED
package/build/index.js
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
import { Command, Option } from "commander";
|
|
6
6
|
import complete from "./commands/complete.js";
|
|
7
7
|
import uninstall from "./commands/uninstall.js";
|
|
8
|
+
import init from "./commands/init.js";
|
|
8
9
|
import { action, supportedShells } from "./commands/root.js";
|
|
9
10
|
import { getVersion } from "./utils/version.js";
|
|
10
11
|
const program = new Command();
|
|
@@ -20,10 +21,10 @@ program
|
|
|
20
21
|
.action(action(program))
|
|
21
22
|
.option("-s, --shell <shell>", `shell to use for command execution, supported shells: ${supportedShells}`)
|
|
22
23
|
.option("-c, --check", `check if shell is in an inshellisense session`)
|
|
23
|
-
.option("--parent-term-exit", `when inshellisense is closed, kill the parent process`)
|
|
24
24
|
.addOption(hiddenOption("-T, --test", "used to make e2e tests reproducible across machines"))
|
|
25
25
|
.option("-V, --verbose", `enable verbose logging`)
|
|
26
26
|
.showHelpAfterError("(add --help for additional information)");
|
|
27
27
|
program.addCommand(complete);
|
|
28
28
|
program.addCommand(uninstall);
|
|
29
|
+
program.addCommand(init);
|
|
29
30
|
program.parse();
|
|
@@ -223,7 +223,7 @@ export class CommandManager {
|
|
|
223
223
|
let wrappedCommand = "";
|
|
224
224
|
let suggestions = "";
|
|
225
225
|
let isWrapped = false;
|
|
226
|
-
for (
|
|
226
|
+
for (; lineY < this.#terminal.buffer.active.baseY + this.#terminal.rows;) {
|
|
227
227
|
for (let i = lineY == this.#activeCommand.promptEndMarker.line ? this.#activeCommand.promptText.length : 0; i < this.#terminal.cols; i++) {
|
|
228
228
|
if (command.endsWith(" "))
|
|
229
229
|
break; // assume that a command that ends with 4 spaces is terminated, avoids capturing right prompts
|
package/build/runtime/parser.js
CHANGED
|
@@ -29,9 +29,10 @@ const lex = (command) => {
|
|
|
29
29
|
readingQuotedString = false;
|
|
30
30
|
const complete = idx + 1 < command.length && spaceRegex.test(command[idx + 1]);
|
|
31
31
|
tokens.push({
|
|
32
|
-
token: command.slice(readingIdx
|
|
32
|
+
token: command.slice(readingIdx + 1, idx),
|
|
33
33
|
complete,
|
|
34
34
|
isOption: false,
|
|
35
|
+
isQuoted: true,
|
|
35
36
|
});
|
|
36
37
|
}
|
|
37
38
|
else if ((readingFlag && spaceRegex.test(char)) || char === "=") {
|
|
@@ -42,7 +43,7 @@ const lex = (command) => {
|
|
|
42
43
|
isOption: true,
|
|
43
44
|
});
|
|
44
45
|
}
|
|
45
|
-
else if (readingCmd && spaceRegex.test(char)) {
|
|
46
|
+
else if (readingCmd && spaceRegex.test(char) && command.at(idx - 1) !== "\\") {
|
|
46
47
|
readingCmd = false;
|
|
47
48
|
tokens.push({
|
|
48
49
|
token: command.slice(readingIdx, idx),
|
|
@@ -53,11 +54,21 @@ const lex = (command) => {
|
|
|
53
54
|
});
|
|
54
55
|
const reading = readingQuotedString || readingFlag || readingCmd;
|
|
55
56
|
if (reading) {
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
57
|
+
if (readingQuotedString) {
|
|
58
|
+
tokens.push({
|
|
59
|
+
token: command.slice(readingIdx + 1),
|
|
60
|
+
complete: false,
|
|
61
|
+
isOption: false,
|
|
62
|
+
isQuoted: true,
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
else {
|
|
66
|
+
tokens.push({
|
|
67
|
+
token: command.slice(readingIdx),
|
|
68
|
+
complete: false,
|
|
69
|
+
isOption: readingFlag,
|
|
70
|
+
});
|
|
71
|
+
}
|
|
61
72
|
}
|
|
62
73
|
return tokens;
|
|
63
74
|
};
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { runGenerator } from "./generator.js";
|
|
5
5
|
import { runTemplates } from "./template.js";
|
|
6
|
+
import log from "../utils/log.js";
|
|
6
7
|
var SuggestionIcons;
|
|
7
8
|
(function (SuggestionIcons) {
|
|
8
9
|
SuggestionIcons["File"] = "\uD83D\uDCC4";
|
|
@@ -43,6 +44,9 @@ const getIcon = (icon, suggestionType) => {
|
|
|
43
44
|
const getLong = (suggestion) => {
|
|
44
45
|
return suggestion instanceof Array ? suggestion.reduce((p, c) => (p.length > c.length ? p : c)) : suggestion;
|
|
45
46
|
};
|
|
47
|
+
const getPathy = (type) => {
|
|
48
|
+
return type === "file" || type === "folder";
|
|
49
|
+
};
|
|
46
50
|
const toSuggestion = (suggestion, name, type) => {
|
|
47
51
|
if (suggestion.name == null)
|
|
48
52
|
return;
|
|
@@ -53,6 +57,7 @@ const toSuggestion = (suggestion, name, type) => {
|
|
|
53
57
|
allNames: suggestion.name instanceof Array ? suggestion.name : [suggestion.name],
|
|
54
58
|
priority: suggestion.priority ?? 50,
|
|
55
59
|
insertValue: suggestion.insertValue,
|
|
60
|
+
pathy: getPathy(suggestion.type),
|
|
56
61
|
};
|
|
57
62
|
};
|
|
58
63
|
function filter(suggestions, filterStrategy, partialCmd, suggestionType) {
|
|
@@ -74,6 +79,7 @@ function filter(suggestions, filterStrategy, partialCmd, suggestionType) {
|
|
|
74
79
|
allNames: s.name,
|
|
75
80
|
priority: s.priority ?? 50,
|
|
76
81
|
insertValue: s.insertValue,
|
|
82
|
+
pathy: getPathy(s.type),
|
|
77
83
|
}
|
|
78
84
|
: undefined;
|
|
79
85
|
}
|
|
@@ -85,6 +91,7 @@ function filter(suggestions, filterStrategy, partialCmd, suggestionType) {
|
|
|
85
91
|
allNames: [s.name],
|
|
86
92
|
priority: s.priority ?? 50,
|
|
87
93
|
insertValue: s.insertValue,
|
|
94
|
+
pathy: getPathy(s.type),
|
|
88
95
|
}
|
|
89
96
|
: undefined;
|
|
90
97
|
})
|
|
@@ -104,6 +111,7 @@ function filter(suggestions, filterStrategy, partialCmd, suggestionType) {
|
|
|
104
111
|
allNames: s.name,
|
|
105
112
|
insertValue: s.insertValue,
|
|
106
113
|
priority: s.priority ?? 50,
|
|
114
|
+
pathy: getPathy(s.type),
|
|
107
115
|
}
|
|
108
116
|
: undefined;
|
|
109
117
|
}
|
|
@@ -115,6 +123,7 @@ function filter(suggestions, filterStrategy, partialCmd, suggestionType) {
|
|
|
115
123
|
allNames: [s.name],
|
|
116
124
|
insertValue: s.insertValue,
|
|
117
125
|
priority: s.priority ?? 50,
|
|
126
|
+
pathy: getPathy(s.type),
|
|
118
127
|
}
|
|
119
128
|
: undefined;
|
|
120
129
|
})
|
|
@@ -144,6 +153,14 @@ const optionSuggestions = (options, acceptedTokens, filterStrategy, partialCmd)
|
|
|
144
153
|
const validOptions = options?.filter((o) => o.exclusiveOn?.every((exclusiveOption) => !usedOptions.has(exclusiveOption)) ?? true);
|
|
145
154
|
return filter(validOptions ?? [], filterStrategy, partialCmd, "option");
|
|
146
155
|
};
|
|
156
|
+
const getEscapedPath = (value) => {
|
|
157
|
+
return value?.replaceAll(" ", "\\ ");
|
|
158
|
+
};
|
|
159
|
+
function adjustPathSuggestions(suggestions, partialToken) {
|
|
160
|
+
if (partialToken == null || partialToken.isQuoted)
|
|
161
|
+
return suggestions;
|
|
162
|
+
return suggestions.map((s) => s.pathy ? { ...s, insertValue: getEscapedPath(s.insertValue), name: s.insertValue == null ? getEscapedPath(s.name) : s.name } : s);
|
|
163
|
+
}
|
|
147
164
|
const removeAcceptedSuggestions = (suggestions, acceptedTokens) => {
|
|
148
165
|
const seen = new Set(acceptedTokens.map((t) => t.token));
|
|
149
166
|
return suggestions.filter((s) => s.allNames.every((n) => !seen.has(n)));
|
|
@@ -163,6 +180,7 @@ const removeEmptySuggestion = (suggestions) => {
|
|
|
163
180
|
return suggestions.filter((s) => s.name.length > 0);
|
|
164
181
|
};
|
|
165
182
|
export const getSubcommandDrivenRecommendation = async (subcommand, persistentOptions, partialToken, argsDepleted, argsFromSubcommand, acceptedTokens, cwd) => {
|
|
183
|
+
log.debug({ msg: "suggestion point", subcommand, persistentOptions, partialToken, argsDepleted, argsFromSubcommand, acceptedTokens, cwd });
|
|
166
184
|
if (argsDepleted && argsFromSubcommand) {
|
|
167
185
|
return;
|
|
168
186
|
}
|
|
@@ -184,7 +202,7 @@ export const getSubcommandDrivenRecommendation = async (subcommand, persistentOp
|
|
|
184
202
|
suggestions.push(...(await templateSuggestions(activeArg?.template, activeArg?.filterStrategy, partialCmd, cwd)));
|
|
185
203
|
}
|
|
186
204
|
return {
|
|
187
|
-
suggestions: removeDuplicateSuggestion(removeEmptySuggestion(removeAcceptedSuggestions(suggestions.sort((a, b) => b.priority - a.priority), acceptedTokens))),
|
|
205
|
+
suggestions: removeDuplicateSuggestion(removeEmptySuggestion(removeAcceptedSuggestions(adjustPathSuggestions(suggestions.sort((a, b) => b.priority - a.priority), partialToken), acceptedTokens))),
|
|
188
206
|
};
|
|
189
207
|
};
|
|
190
208
|
export const getArgDrivenRecommendation = async (args, subcommand, persistentOptions, partialToken, acceptedTokens, variadicArgBound, cwd) => {
|
|
@@ -204,7 +222,7 @@ export const getArgDrivenRecommendation = async (args, subcommand, persistentOpt
|
|
|
204
222
|
suggestions.push(...optionSuggestions(allOptions, acceptedTokens, activeArg?.filterStrategy, partialCmd));
|
|
205
223
|
}
|
|
206
224
|
return {
|
|
207
|
-
suggestions: removeDuplicateSuggestion(removeEmptySuggestion(removeAcceptedSuggestions(suggestions.sort((a, b) => b.priority - a.priority), acceptedTokens))),
|
|
225
|
+
suggestions: removeDuplicateSuggestion(removeEmptySuggestion(removeAcceptedSuggestions(adjustPathSuggestions(suggestions.sort((a, b) => b.priority - a.priority), partialToken), acceptedTokens))),
|
|
208
226
|
argumentDescription: activeArg.description ?? activeArg.name,
|
|
209
227
|
};
|
|
210
228
|
};
|
package/build/runtime/utils.js
CHANGED
|
@@ -6,7 +6,7 @@ import fsAsync from "node:fs/promises";
|
|
|
6
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
|
-
const child = spawn(command, args, { cwd, env });
|
|
9
|
+
const child = spawn(command, args, { cwd, env: { ...env, ISTERM: "1" } });
|
|
10
10
|
setTimeout(() => child.kill("SIGKILL"), timeout);
|
|
11
11
|
let stdout = "";
|
|
12
12
|
let stderr = "";
|
|
@@ -28,7 +28,8 @@ export const buildExecuteShellCommand = (timeout) => async ({ command, env, args
|
|
|
28
28
|
export const resolveCwd = async (cmdToken, cwd, shell) => {
|
|
29
29
|
if (cmdToken == null)
|
|
30
30
|
return { cwd, pathy: false, complete: false };
|
|
31
|
-
const { token } = cmdToken;
|
|
31
|
+
const { token: rawToken, isQuoted } = cmdToken;
|
|
32
|
+
const token = !isQuoted ? rawToken.replaceAll("\\ ", " ") : rawToken;
|
|
32
33
|
const sep = getPathSeperator(shell);
|
|
33
34
|
if (!token.includes(sep))
|
|
34
35
|
return { cwd, pathy: false, complete: false };
|
package/build/ui/ui-root.js
CHANGED
|
@@ -12,7 +12,7 @@ export const renderConfirmation = (live) => {
|
|
|
12
12
|
const statusMessage = live ? chalk.green("live") : chalk.red("not found");
|
|
13
13
|
return `inshellisense session [${statusMessage}]\n`;
|
|
14
14
|
};
|
|
15
|
-
export const render = async (shell, underTest
|
|
15
|
+
export const render = async (shell, underTest) => {
|
|
16
16
|
const term = await isterm.spawn({ shell, rows: process.stdout.rows, cols: process.stdout.columns, underTest });
|
|
17
17
|
const suggestionManager = new SuggestionManager(term, shell);
|
|
18
18
|
let hasActiveSuggestions = false;
|
|
@@ -129,9 +129,6 @@ export const render = async (shell, underTest, parentTermExit) => {
|
|
|
129
129
|
}
|
|
130
130
|
});
|
|
131
131
|
term.onExit(({ exitCode }) => {
|
|
132
|
-
if (parentTermExit && process.ppid) {
|
|
133
|
-
process.kill(process.ppid);
|
|
134
|
-
}
|
|
135
132
|
process.exit(exitCode);
|
|
136
133
|
});
|
|
137
134
|
process.stdout.on("resize", () => {
|
package/build/utils/shell.js
CHANGED
|
@@ -29,6 +29,7 @@ export const supportedShells = [
|
|
|
29
29
|
Shell.Xonsh,
|
|
30
30
|
Shell.Nushell,
|
|
31
31
|
].filter((shell) => shell != null);
|
|
32
|
+
export const initSupportedShells = supportedShells.filter((shell) => shell != Shell.Cmd);
|
|
32
33
|
export const userZdotdir = process.env?.ZDOTDIR ?? os.homedir() ?? `~`;
|
|
33
34
|
export const zdotdir = path.join(os.tmpdir(), `is-zsh`);
|
|
34
35
|
const configFolder = ".inshellisense";
|
|
@@ -99,3 +100,33 @@ export const getBackspaceSequence = (press, shell) => shell === Shell.Pwsh || sh
|
|
|
99
100
|
export const getPathSeperator = (shell) => (shell == Shell.Bash || shell == Shell.Xonsh || shell == Shell.Nushell ? "/" : path.sep);
|
|
100
101
|
// nu fully re-writes the prompt every keystroke resulting in duplicate start/end sequences on the same line
|
|
101
102
|
export const getShellPromptRewrites = (shell) => shell == Shell.Nushell;
|
|
103
|
+
export const getShellConfig = (shell) => {
|
|
104
|
+
switch (shell) {
|
|
105
|
+
case Shell.Zsh:
|
|
106
|
+
case Shell.Bash:
|
|
107
|
+
return `if [[ -z "\${ISTERM}" && $- = *i* && $- != *c* ]]; then
|
|
108
|
+
is -s ${shell} ; exit
|
|
109
|
+
fi`;
|
|
110
|
+
case Shell.Powershell:
|
|
111
|
+
case Shell.Pwsh:
|
|
112
|
+
return `$__IsCommandFlag = ([Environment]::GetCommandLineArgs() | ForEach-Object { $_.contains("-Command") }) -contains $true
|
|
113
|
+
$__IsNoExitFlag = ([Environment]::GetCommandLineArgs() | ForEach-Object { $_.contains("-NoExit") }) -contains $true
|
|
114
|
+
$__IsInteractive = -not $__IsCommandFlag -or ($__IsCommandFlag -and $__IsNoExitFlag)
|
|
115
|
+
if ([string]::IsNullOrEmpty($env:ISTERM) -and [Environment]::UserInteractive -and $__IsInteractive) {
|
|
116
|
+
is -s ${shell}
|
|
117
|
+
Stop-Process -Id $pid
|
|
118
|
+
}`;
|
|
119
|
+
case Shell.Fish:
|
|
120
|
+
return `if test -z "$ISTERM" && status --is-interactive
|
|
121
|
+
is -s fish ; kill %self
|
|
122
|
+
end`;
|
|
123
|
+
case Shell.Xonsh:
|
|
124
|
+
return `if 'ISTERM' not in \${...} and $XONSH_INTERACTIVE:
|
|
125
|
+
is -s xonsh ; exit`;
|
|
126
|
+
case Shell.Nushell:
|
|
127
|
+
return `if "ISTERM" not-in $env and $nu.is-interactive {
|
|
128
|
+
is -s nu ; exit
|
|
129
|
+
}`;
|
|
130
|
+
}
|
|
131
|
+
return "";
|
|
132
|
+
};
|