@microsoft/inshellisense 0.0.1-rc.12 → 0.0.1-rc.14
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 +2 -2
- package/build/commands/specs/list.js +26 -0
- package/build/commands/specs/root.js +8 -0
- package/build/index.js +7 -2
- package/build/isterm/commandManager.js +5 -1
- package/build/isterm/pty.js +55 -18
- package/build/runtime/alias.js +1 -0
- package/build/runtime/parser.js +18 -7
- package/build/runtime/runtime.js +3 -0
- package/build/runtime/suggestion.js +20 -2
- package/build/runtime/utils.js +3 -2
- package/build/ui/suggestionManager.js +3 -0
- package/build/ui/ui-root.js +2 -5
- package/build/utils/shell.js +52 -0
- package/package.json +4 -3
- package/shell/shellIntegration.bash +16 -2
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
|
@@ -12,7 +12,7 @@ export const action = (program) => async (options) => {
|
|
|
12
12
|
const inISTerm = process.env.ISTERM === "1";
|
|
13
13
|
if (options.check || inISTerm) {
|
|
14
14
|
process.stdout.write(renderConfirmation(inISTerm));
|
|
15
|
-
|
|
15
|
+
process.exit(0);
|
|
16
16
|
}
|
|
17
17
|
if (options.verbose)
|
|
18
18
|
await log.enable();
|
|
@@ -32,5 +32,5 @@ export const action = (program) => async (options) => {
|
|
|
32
32
|
await setupBashPreExec();
|
|
33
33
|
}
|
|
34
34
|
await loadAliases(shell);
|
|
35
|
-
await render(shell, options.test ?? false, options.
|
|
35
|
+
await render(shell, options.test ?? false, options.login ?? false);
|
|
36
36
|
};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
// Copyright (c) Microsoft Corporation.
|
|
2
|
+
// Licensed under the MIT License.
|
|
3
|
+
import { Command } from "commander";
|
|
4
|
+
import { loadConfig } from "../../utils/config.js";
|
|
5
|
+
import { getSpecNames, loadLocalSpecsSet } from "../../runtime/runtime.js";
|
|
6
|
+
import { getAliasNames, loadAliases } from "../../runtime/alias.js";
|
|
7
|
+
import { aliasSupportedShells } from "../../utils/shell.js";
|
|
8
|
+
const supportedShells = aliasSupportedShells.join(", ");
|
|
9
|
+
const action = (program) => async (options) => {
|
|
10
|
+
await loadConfig(program);
|
|
11
|
+
await loadLocalSpecsSet();
|
|
12
|
+
const { shell } = options;
|
|
13
|
+
if (shell && !aliasSupportedShells.map((s) => s.valueOf()).includes(shell)) {
|
|
14
|
+
program.error(`Unsupported shell: '${shell}', supported shells: ${supportedShells}`, { exitCode: 1 });
|
|
15
|
+
}
|
|
16
|
+
if (shell) {
|
|
17
|
+
await loadAliases(shell);
|
|
18
|
+
}
|
|
19
|
+
process.stdout.write(JSON.stringify([...getAliasNames(), ...getSpecNames()]));
|
|
20
|
+
process.exit(0);
|
|
21
|
+
};
|
|
22
|
+
const cmd = new Command("list");
|
|
23
|
+
cmd.description(`list the names of all available specs`);
|
|
24
|
+
cmd.option("-s, --shell <shell>", `shell to use alias specs, supported shells: ${supportedShells}`);
|
|
25
|
+
cmd.action(action(cmd));
|
|
26
|
+
export default cmd;
|
package/build/index.js
CHANGED
|
@@ -5,6 +5,8 @@
|
|
|
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";
|
|
9
|
+
import specs from "./commands/specs/root.js";
|
|
8
10
|
import { action, supportedShells } from "./commands/root.js";
|
|
9
11
|
import { getVersion } from "./utils/version.js";
|
|
10
12
|
const program = new Command();
|
|
@@ -18,12 +20,15 @@ program
|
|
|
18
20
|
.description("IDE style command line auto complete")
|
|
19
21
|
.version(await getVersion(), "-v, --version", "output the current version")
|
|
20
22
|
.action(action(program))
|
|
23
|
+
.option("-l, --login", `start shell as a login shell`)
|
|
21
24
|
.option("-s, --shell <shell>", `shell to use for command execution, supported shells: ${supportedShells}`)
|
|
22
25
|
.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
26
|
.addOption(hiddenOption("-T, --test", "used to make e2e tests reproducible across machines"))
|
|
25
27
|
.option("-V, --verbose", `enable verbose logging`)
|
|
26
|
-
.showHelpAfterError("(add --help for additional information)")
|
|
28
|
+
.showHelpAfterError("(add --help for additional information)")
|
|
29
|
+
.passThroughOptions();
|
|
27
30
|
program.addCommand(complete);
|
|
28
31
|
program.addCommand(uninstall);
|
|
32
|
+
program.addCommand(init);
|
|
33
|
+
program.addCommand(specs);
|
|
29
34
|
program.parse();
|
|
@@ -188,6 +188,9 @@ export class CommandManager {
|
|
|
188
188
|
cursorTerminated: this.#activeCommand.cursorTerminated,
|
|
189
189
|
};
|
|
190
190
|
}
|
|
191
|
+
clearActiveCommand() {
|
|
192
|
+
this.#activeCommand = {};
|
|
193
|
+
}
|
|
191
194
|
termSync() {
|
|
192
195
|
if (this.#activeCommand.promptEndMarker == null || this.#activeCommand.promptStartMarker == null) {
|
|
193
196
|
return;
|
|
@@ -197,6 +200,7 @@ export class CommandManager {
|
|
|
197
200
|
if (globalCursorPosition < this.#activeCommand.promptStartMarker.line) {
|
|
198
201
|
this.handleClear();
|
|
199
202
|
this.#activeCommand.promptEndMarker = this.#terminal.registerMarker(0);
|
|
203
|
+
return;
|
|
200
204
|
}
|
|
201
205
|
if (this.#activeCommand.promptEndMarker == null)
|
|
202
206
|
return;
|
|
@@ -223,7 +227,7 @@ export class CommandManager {
|
|
|
223
227
|
let wrappedCommand = "";
|
|
224
228
|
let suggestions = "";
|
|
225
229
|
let isWrapped = false;
|
|
226
|
-
for (
|
|
230
|
+
for (; lineY < this.#terminal.buffer.active.baseY + this.#terminal.rows;) {
|
|
227
231
|
for (let i = lineY == this.#activeCommand.promptEndMarker.line ? this.#activeCommand.promptText.length : 0; i < this.#terminal.cols; i++) {
|
|
228
232
|
if (command.endsWith(" "))
|
|
229
233
|
break; // assume that a command that ends with 4 spaces is terminated, avoids capturing right prompts
|
package/build/isterm/pty.js
CHANGED
|
@@ -31,13 +31,13 @@ export class ISTerm {
|
|
|
31
31
|
#term;
|
|
32
32
|
#commandManager;
|
|
33
33
|
#shell;
|
|
34
|
-
constructor({ shell, cols, rows, env, shellTarget, shellArgs, underTest }) {
|
|
34
|
+
constructor({ shell, cols, rows, env, shellTarget, shellArgs, underTest, login }) {
|
|
35
35
|
this.#pty = pty.spawn(shellTarget, shellArgs ?? [], {
|
|
36
36
|
name: "xterm-256color",
|
|
37
37
|
cols,
|
|
38
38
|
rows,
|
|
39
39
|
cwd: process.cwd(),
|
|
40
|
-
env: { ...convertToPtyEnv(shell, underTest), ...env },
|
|
40
|
+
env: { ...convertToPtyEnv(shell, underTest, login), ...env },
|
|
41
41
|
});
|
|
42
42
|
this.pid = this.#pty.pid;
|
|
43
43
|
this.cols = this.#pty.cols;
|
|
@@ -144,24 +144,37 @@ export class ISTerm {
|
|
|
144
144
|
};
|
|
145
145
|
}
|
|
146
146
|
_sameAccent(baseCell, targetCell) {
|
|
147
|
-
return baseCell?.isBold() == targetCell?.isBold() &&
|
|
147
|
+
return (baseCell?.isBold() == targetCell?.isBold() &&
|
|
148
|
+
baseCell?.isItalic() == targetCell?.isItalic() &&
|
|
149
|
+
baseCell?.isUnderline() == targetCell?.isUnderline() &&
|
|
150
|
+
baseCell?.extended.underlineStyle == targetCell?.extended.underlineStyle &&
|
|
151
|
+
baseCell?.hasExtendedAttrs() == targetCell?.hasExtendedAttrs() &&
|
|
152
|
+
baseCell?.isInverse() == targetCell?.isInverse() &&
|
|
153
|
+
baseCell?.isBlink() == targetCell?.isBlink() &&
|
|
154
|
+
baseCell?.isInvisible() == targetCell?.isInvisible() &&
|
|
155
|
+
baseCell?.isDim() == targetCell?.isDim() &&
|
|
156
|
+
baseCell?.isStrikethrough() == targetCell?.isStrikethrough());
|
|
148
157
|
}
|
|
149
158
|
_getAnsiAccents(cell) {
|
|
150
159
|
if (cell == null)
|
|
151
160
|
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
161
|
let underlineAnsi = "";
|
|
161
162
|
if (cell.isUnderline()) {
|
|
162
|
-
|
|
163
|
+
if (cell.hasExtendedAttrs() && cell.extended.underlineStyle) {
|
|
164
|
+
underlineAnsi = `\x1b[4:${cell.extended.underlineStyle}m`;
|
|
165
|
+
}
|
|
166
|
+
else {
|
|
167
|
+
underlineAnsi = "\x1b[4m";
|
|
168
|
+
}
|
|
163
169
|
}
|
|
164
|
-
|
|
170
|
+
const boldAnsi = cell.isBold() ? "\x1b[1m" : "";
|
|
171
|
+
const dimAnsi = cell.isDim() ? "\x1b[2m" : "";
|
|
172
|
+
const italicAnsi = cell.isItalic() ? "\x1b[3m" : "";
|
|
173
|
+
const blinkAnsi = cell.isBlink() ? "\x1b[5m" : "";
|
|
174
|
+
const inverseAnsi = cell.isInverse() ? "\x1b[7m" : "";
|
|
175
|
+
const invisibleAnsi = cell.isInvisible() ? "\x1b[8m" : "";
|
|
176
|
+
const strikethroughAnsi = cell.isStrikethrough() ? "\x1b[9m" : "";
|
|
177
|
+
return boldAnsi + italicAnsi + underlineAnsi + inverseAnsi + dimAnsi + blinkAnsi + invisibleAnsi + strikethroughAnsi;
|
|
165
178
|
}
|
|
166
179
|
_sameColor(baseCell, targetCell) {
|
|
167
180
|
return (baseCell?.getBgColorMode() == targetCell?.getBgColorMode() &&
|
|
@@ -196,6 +209,9 @@ export class ISTerm {
|
|
|
196
209
|
}
|
|
197
210
|
return bgAnsi + fgAnsi;
|
|
198
211
|
}
|
|
212
|
+
clearCommand() {
|
|
213
|
+
this.#commandManager.clearActiveCommand();
|
|
214
|
+
}
|
|
199
215
|
getCells(height, direction) {
|
|
200
216
|
const currentCursorPosition = this.#term.buffer.active.cursorY + this.#term.buffer.active.baseY;
|
|
201
217
|
const writeLine = (y) => {
|
|
@@ -207,10 +223,15 @@ export class ISTerm {
|
|
|
207
223
|
for (let x = 0; x < line.length; x++) {
|
|
208
224
|
const cell = line.getCell(x);
|
|
209
225
|
const chars = cell?.getChars() ?? "";
|
|
210
|
-
|
|
226
|
+
const sameColor = this._sameColor(prevCell, cell);
|
|
227
|
+
const sameAccents = this._sameAccent(prevCell, cell);
|
|
228
|
+
if (!sameColor || !sameAccents) {
|
|
229
|
+
ansiLine.push("\x1b[0m");
|
|
230
|
+
}
|
|
231
|
+
if (!sameColor) {
|
|
211
232
|
ansiLine.push(this._getAnsiColors(cell));
|
|
212
233
|
}
|
|
213
|
-
if (!
|
|
234
|
+
if (!sameAccents) {
|
|
214
235
|
ansiLine.push(this._getAnsiAccents(cell));
|
|
215
236
|
}
|
|
216
237
|
ansiLine.push(chars == "" ? " " : chars);
|
|
@@ -237,10 +258,10 @@ export class ISTerm {
|
|
|
237
258
|
}
|
|
238
259
|
}
|
|
239
260
|
export const spawn = async (options) => {
|
|
240
|
-
const { shellTarget, shellArgs } = await convertToPtyTarget(options.shell, options.underTest);
|
|
261
|
+
const { shellTarget, shellArgs } = await convertToPtyTarget(options.shell, options.underTest, options.login);
|
|
241
262
|
return new ISTerm({ ...options, shellTarget, shellArgs });
|
|
242
263
|
};
|
|
243
|
-
const convertToPtyTarget = async (shell, underTest) => {
|
|
264
|
+
const convertToPtyTarget = async (shell, underTest, login) => {
|
|
244
265
|
const platform = os.platform();
|
|
245
266
|
const shellTarget = shell == Shell.Bash && platform == "win32" ? await gitBashPath() : platform == "win32" ? `${shell}.exe` : shell;
|
|
246
267
|
const shellFolderPath = path.join(path.dirname(url.fileURLToPath(import.meta.url)), "..", "..", "shell");
|
|
@@ -273,15 +294,31 @@ const convertToPtyTarget = async (shell, underTest) => {
|
|
|
273
294
|
shellArgs.push("-n");
|
|
274
295
|
break;
|
|
275
296
|
}
|
|
297
|
+
if (login) {
|
|
298
|
+
switch (shell) {
|
|
299
|
+
case Shell.Powershell:
|
|
300
|
+
case Shell.Pwsh:
|
|
301
|
+
shellArgs.unshift("-login");
|
|
302
|
+
break;
|
|
303
|
+
case Shell.Zsh:
|
|
304
|
+
case Shell.Fish:
|
|
305
|
+
case Shell.Xonsh:
|
|
306
|
+
case Shell.Nushell:
|
|
307
|
+
shellArgs.unshift("--login");
|
|
308
|
+
break;
|
|
309
|
+
}
|
|
310
|
+
}
|
|
276
311
|
return { shellTarget, shellArgs };
|
|
277
312
|
};
|
|
278
|
-
const convertToPtyEnv = (shell, underTest) => {
|
|
313
|
+
const convertToPtyEnv = (shell, underTest, login) => {
|
|
279
314
|
const env = {
|
|
280
315
|
...process.env,
|
|
281
316
|
ISTERM: "1",
|
|
282
317
|
};
|
|
283
318
|
if (underTest)
|
|
284
319
|
env.ISTERM_TESTING = "1";
|
|
320
|
+
if (login)
|
|
321
|
+
env.ISTERM_LOGIN = "1";
|
|
285
322
|
switch (shell) {
|
|
286
323
|
case Shell.Cmd: {
|
|
287
324
|
if (underTest) {
|
package/build/runtime/alias.js
CHANGED
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
|
};
|
package/build/runtime/runtime.js
CHANGED
|
@@ -103,6 +103,9 @@ export const getSuggestions = async (cmd, cwd, shell) => {
|
|
|
103
103
|
}
|
|
104
104
|
return { ...result, charactersToDrop };
|
|
105
105
|
};
|
|
106
|
+
export const getSpecNames = () => {
|
|
107
|
+
return Object.keys(specSet).filter((spec) => !spec.startsWith("@") && spec != "-");
|
|
108
|
+
};
|
|
106
109
|
const getPersistentOptions = (persistentOptions, options) => {
|
|
107
110
|
const persistentOptionNames = new Set(persistentOptions.map((o) => (typeof o.name === "string" ? [o.name] : o.name)).flat());
|
|
108
111
|
return persistentOptions.concat((options ?? []).filter((o) => (typeof o.name == "string" ? !persistentOptionNames.has(o.name) : o.name.some((n) => !persistentOptionNames.has(n))) && o.isPersistent === true));
|
|
@@ -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 };
|
|
@@ -122,6 +122,9 @@ export class SuggestionManager {
|
|
|
122
122
|
}
|
|
123
123
|
update(keyPress) {
|
|
124
124
|
const { name, shift, ctrl } = keyPress;
|
|
125
|
+
if (name == "return") {
|
|
126
|
+
this.#term.clearCommand(); // clear the current command on enter
|
|
127
|
+
}
|
|
125
128
|
if (!this.#suggestBlob) {
|
|
126
129
|
return false;
|
|
127
130
|
}
|
package/build/ui/ui-root.js
CHANGED
|
@@ -12,8 +12,8 @@ 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,
|
|
16
|
-
const term = await isterm.spawn({ shell, rows: process.stdout.rows, cols: process.stdout.columns, underTest });
|
|
15
|
+
export const render = async (shell, underTest, login) => {
|
|
16
|
+
const term = await isterm.spawn({ shell, rows: process.stdout.rows, cols: process.stdout.columns, underTest, login });
|
|
17
17
|
const suggestionManager = new SuggestionManager(term, shell);
|
|
18
18
|
let hasActiveSuggestions = false;
|
|
19
19
|
let previousSuggestionsRows = 0;
|
|
@@ -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,8 @@ 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);
|
|
33
|
+
export const aliasSupportedShells = [Shell.Bash, Shell.Zsh];
|
|
32
34
|
export const userZdotdir = process.env?.ZDOTDIR ?? os.homedir() ?? `~`;
|
|
33
35
|
export const zdotdir = path.join(os.tmpdir(), `is-zsh`);
|
|
34
36
|
const configFolder = ".inshellisense";
|
|
@@ -99,3 +101,53 @@ export const getBackspaceSequence = (press, shell) => shell === Shell.Pwsh || sh
|
|
|
99
101
|
export const getPathSeperator = (shell) => (shell == Shell.Bash || shell == Shell.Xonsh || shell == Shell.Nushell ? "/" : path.sep);
|
|
100
102
|
// nu fully re-writes the prompt every keystroke resulting in duplicate start/end sequences on the same line
|
|
101
103
|
export const getShellPromptRewrites = (shell) => shell == Shell.Nushell;
|
|
104
|
+
export const getShellConfig = (shell) => {
|
|
105
|
+
switch (shell) {
|
|
106
|
+
case Shell.Zsh:
|
|
107
|
+
return `if [[ -z "\${ISTERM}" && $- = *i* && $- != *c* ]]; then
|
|
108
|
+
if [[ -o login ]]; then
|
|
109
|
+
is -s zsh --login ; exit
|
|
110
|
+
else
|
|
111
|
+
is -s zsh ; exit
|
|
112
|
+
fi
|
|
113
|
+
fi`;
|
|
114
|
+
case Shell.Bash:
|
|
115
|
+
return `if [[ -z "\${ISTERM}" && $- = *i* && $- != *c* ]]; then
|
|
116
|
+
shopt -q login_shell
|
|
117
|
+
login_shell=$?
|
|
118
|
+
if [ $login_shell -eq 0 ]; then
|
|
119
|
+
is -s bash --login ; exit
|
|
120
|
+
else
|
|
121
|
+
is -s bash ; exit
|
|
122
|
+
fi
|
|
123
|
+
fi`;
|
|
124
|
+
case Shell.Powershell:
|
|
125
|
+
case Shell.Pwsh:
|
|
126
|
+
return `$__IsCommandFlag = ([Environment]::GetCommandLineArgs() | ForEach-Object { $_.contains("-Command") }) -contains $true
|
|
127
|
+
$__IsNoExitFlag = ([Environment]::GetCommandLineArgs() | ForEach-Object { $_.contains("-NoExit") }) -contains $true
|
|
128
|
+
$__IsInteractive = -not $__IsCommandFlag -or ($__IsCommandFlag -and $__IsNoExitFlag)
|
|
129
|
+
if ([string]::IsNullOrEmpty($env:ISTERM) -and [Environment]::UserInteractive -and $__IsInteractive) {
|
|
130
|
+
is -s ${shell}
|
|
131
|
+
Stop-Process -Id $pid
|
|
132
|
+
}`;
|
|
133
|
+
case Shell.Fish:
|
|
134
|
+
return `if test -z "$ISTERM" && status --is-interactive
|
|
135
|
+
if status --is-login
|
|
136
|
+
is -s fish --login ; kill %self
|
|
137
|
+
else
|
|
138
|
+
is -s fish ; kill %self
|
|
139
|
+
end
|
|
140
|
+
end`;
|
|
141
|
+
case Shell.Xonsh:
|
|
142
|
+
return `if 'ISTERM' not in \${...} and $XONSH_INTERACTIVE:
|
|
143
|
+
if $XONSH_LOGIN:
|
|
144
|
+
is -s xonsh --login ; exit
|
|
145
|
+
else:
|
|
146
|
+
is -s xonsh ; exit`;
|
|
147
|
+
case Shell.Nushell:
|
|
148
|
+
return `if "ISTERM" not-in $env and $nu.is-interactive {
|
|
149
|
+
if $nu.is-login { is -s nu --login ; exit } else { is -s nu ; exit }
|
|
150
|
+
}`;
|
|
151
|
+
}
|
|
152
|
+
return "";
|
|
153
|
+
};
|
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.14",
|
|
4
4
|
"description": "IDE style command line auto complete",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"engines": {
|
|
@@ -41,6 +41,7 @@
|
|
|
41
41
|
"dependencies": {
|
|
42
42
|
"@homebridge/node-pty-prebuilt-multiarch": "^0.11.12",
|
|
43
43
|
"@withfig/autocomplete": "2.651.0",
|
|
44
|
+
"@xterm/headless": "^5.5.0",
|
|
44
45
|
"ajv": "^8.12.0",
|
|
45
46
|
"ansi-escapes": "^6.2.0",
|
|
46
47
|
"ansi-styles": "^6.2.1",
|
|
@@ -51,8 +52,7 @@
|
|
|
51
52
|
"toml": "^3.0.0",
|
|
52
53
|
"wcwidth": "^1.0.1",
|
|
53
54
|
"which": "^4.0.0",
|
|
54
|
-
"wrap-ansi": "^8.1.0"
|
|
55
|
-
"@xterm/headless": "^5.3.0"
|
|
55
|
+
"wrap-ansi": "^8.1.0"
|
|
56
56
|
},
|
|
57
57
|
"devDependencies": {
|
|
58
58
|
"@microsoft/tui-test": "^0.0.1-rc.3",
|
|
@@ -65,6 +65,7 @@
|
|
|
65
65
|
"@typescript-eslint/eslint-plugin": "^6.7.4",
|
|
66
66
|
"@typescript-eslint/parser": "^6.7.4",
|
|
67
67
|
"@withfig/autocomplete-types": "^1.28.0",
|
|
68
|
+
"@xterm/xterm": "^5.5.0",
|
|
68
69
|
"eslint": "^8.51.0",
|
|
69
70
|
"eslint-config-prettier": "^9.0.0",
|
|
70
71
|
"eslint-plugin-header": "^3.1.1",
|
|
@@ -1,5 +1,19 @@
|
|
|
1
|
-
if [ -
|
|
2
|
-
|
|
1
|
+
if [ -z "$ISTERM_LOGIN" ]; then
|
|
2
|
+
if [ -r ~/.bashrc ]; then
|
|
3
|
+
. ~/.bashrc
|
|
4
|
+
fi
|
|
5
|
+
else
|
|
6
|
+
if [ -r /etc/profile ]; then
|
|
7
|
+
. /etc/profile
|
|
8
|
+
fi
|
|
9
|
+
# execute the first that exists
|
|
10
|
+
if [ -r ~/.bash_profile ]; then
|
|
11
|
+
. ~/.bash_profile
|
|
12
|
+
elif [ -r ~/.bash_login ]; then
|
|
13
|
+
. ~/.bash_login
|
|
14
|
+
elif [ -r ~/.profile ]; then
|
|
15
|
+
. ~/.profile
|
|
16
|
+
fi
|
|
3
17
|
fi
|
|
4
18
|
|
|
5
19
|
if [ -r ~/.inshellisense/bash-preexec.sh ]; then
|