@microsoft/inshellisense 0.0.1-rc.11 → 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 +5 -1
- package/build/index.js +2 -1
- package/build/isterm/commandManager.js +17 -4
- package/build/runtime/alias.js +60 -0
- package/build/runtime/parser.js +18 -7
- package/build/runtime/runtime.js +62 -21
- 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/config.js +15 -0
- 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
|
@@ -5,6 +5,8 @@ import { Shell, supportedShells as shells, setupZshDotfiles, setupBashPreExec }
|
|
|
5
5
|
import { inferShell } from "../utils/shell.js";
|
|
6
6
|
import { loadConfig } from "../utils/config.js";
|
|
7
7
|
import log from "../utils/log.js";
|
|
8
|
+
import { loadAliases } from "../runtime/alias.js";
|
|
9
|
+
import { loadLocalSpecsSet } from "../runtime/runtime.js";
|
|
8
10
|
export const supportedShells = shells.join(", ");
|
|
9
11
|
export const action = (program) => async (options) => {
|
|
10
12
|
const inISTerm = process.env.ISTERM === "1";
|
|
@@ -15,6 +17,7 @@ export const action = (program) => async (options) => {
|
|
|
15
17
|
if (options.verbose)
|
|
16
18
|
await log.enable();
|
|
17
19
|
await loadConfig(program);
|
|
20
|
+
await loadLocalSpecsSet();
|
|
18
21
|
const shell = options.shell ?? (await inferShell());
|
|
19
22
|
if (shell == null) {
|
|
20
23
|
program.error(`Unable to identify shell, use the -s/--shell option to provide your shell`, { exitCode: 1 });
|
|
@@ -28,5 +31,6 @@ export const action = (program) => async (options) => {
|
|
|
28
31
|
else if (shell == Shell.Bash) {
|
|
29
32
|
await setupBashPreExec();
|
|
30
33
|
}
|
|
31
|
-
await
|
|
34
|
+
await loadAliases(shell);
|
|
35
|
+
await render(shell, options.test ?? false);
|
|
32
36
|
};
|
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();
|
|
@@ -32,6 +32,8 @@ export class CommandManager {
|
|
|
32
32
|
this.#activeCommand = { promptStartMarker: this.#terminal.registerMarker(0), hasOutput: false, cursorTerminated: false };
|
|
33
33
|
}
|
|
34
34
|
handlePromptEnd() {
|
|
35
|
+
if (this.#activeCommand.promptEndMarker != null)
|
|
36
|
+
return;
|
|
35
37
|
this.#activeCommand.promptEndMarker = this.#terminal.registerMarker(0);
|
|
36
38
|
if (this.#activeCommand.promptEndMarker?.line === this.#terminal.buffer.active.cursorY) {
|
|
37
39
|
this.#activeCommand.promptEndX = this.#terminal.buffer.active.cursorX;
|
|
@@ -218,8 +220,10 @@ export class CommandManager {
|
|
|
218
220
|
let lineY = this.#activeCommand.promptEndMarker.line;
|
|
219
221
|
let line = this.#terminal.buffer.active.getLine(this.#activeCommand.promptEndMarker.line);
|
|
220
222
|
let command = "";
|
|
223
|
+
let wrappedCommand = "";
|
|
221
224
|
let suggestions = "";
|
|
222
|
-
|
|
225
|
+
let isWrapped = false;
|
|
226
|
+
for (; lineY < this.#terminal.buffer.active.baseY + this.#terminal.rows;) {
|
|
223
227
|
for (let i = lineY == this.#activeCommand.promptEndMarker.line ? this.#activeCommand.promptText.length : 0; i < this.#terminal.cols; i++) {
|
|
224
228
|
if (command.endsWith(" "))
|
|
225
229
|
break; // assume that a command that ends with 4 spaces is terminated, avoids capturing right prompts
|
|
@@ -230,6 +234,7 @@ export class CommandManager {
|
|
|
230
234
|
const cleanedChars = chars == "" ? " " : chars;
|
|
231
235
|
if (!this._isSuggestion(cell) && suggestions.length == 0) {
|
|
232
236
|
command += cleanedChars;
|
|
237
|
+
wrappedCommand += cleanedChars;
|
|
233
238
|
}
|
|
234
239
|
else {
|
|
235
240
|
suggestions += cleanedChars;
|
|
@@ -237,11 +242,16 @@ export class CommandManager {
|
|
|
237
242
|
}
|
|
238
243
|
lineY += 1;
|
|
239
244
|
line = this.#terminal.buffer.active.getLine(lineY);
|
|
240
|
-
|
|
245
|
+
const wrapped = line?.isWrapped || this.#terminal.buffer.active.cursorY + this.#terminal.buffer.active.baseY != lineY - 1;
|
|
246
|
+
isWrapped = isWrapped || wrapped;
|
|
247
|
+
if (!wrapped) {
|
|
241
248
|
break;
|
|
242
249
|
}
|
|
250
|
+
wrappedCommand = "";
|
|
243
251
|
}
|
|
244
|
-
const cursorAtEndOfInput =
|
|
252
|
+
const cursorAtEndOfInput = isWrapped
|
|
253
|
+
? wrappedCommand.trim().length % this.#terminal.cols <= this.#terminal.buffer.active.cursorX
|
|
254
|
+
: (this.#activeCommand.promptText.length + command.trimEnd().length) % this.#terminal.cols <= this.#terminal.buffer.active.cursorX;
|
|
245
255
|
let hasOutput = false;
|
|
246
256
|
let cell = undefined;
|
|
247
257
|
for (let i = 0; i < this.#terminal.cols; i++) {
|
|
@@ -253,7 +263,10 @@ export class CommandManager {
|
|
|
253
263
|
break;
|
|
254
264
|
}
|
|
255
265
|
}
|
|
256
|
-
const
|
|
266
|
+
const postfixActive = isWrapped
|
|
267
|
+
? wrappedCommand.trim().length < this.#terminal.buffer.active.cursorX
|
|
268
|
+
: this.#activeCommand.promptText.length + command.trimEnd().length < this.#terminal.buffer.active.cursorX;
|
|
269
|
+
const commandPostfix = postfixActive ? " " : "";
|
|
257
270
|
this.#activeCommand.persistentOutput = this.#activeCommand.hasOutput && hasOutput;
|
|
258
271
|
this.#activeCommand.hasOutput = hasOutput;
|
|
259
272
|
this.#activeCommand.suggestionsText = suggestions.trim();
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
// Copyright (c) Microsoft Corporation.
|
|
2
|
+
// Licensed under the MIT License.
|
|
3
|
+
import log from "../utils/log.js";
|
|
4
|
+
import { gitBashPath, Shell } from "../utils/shell.js";
|
|
5
|
+
import { parseCommand } from "./parser.js";
|
|
6
|
+
import { buildExecuteShellCommand } from "./utils.js";
|
|
7
|
+
import os from "node:os";
|
|
8
|
+
const loadedAliases = {};
|
|
9
|
+
const platform = os.platform();
|
|
10
|
+
const executeShellCommand = buildExecuteShellCommand(5000);
|
|
11
|
+
const loadBashAliases = async () => {
|
|
12
|
+
const shellTarget = platform == "win32" ? await gitBashPath() : Shell.Bash;
|
|
13
|
+
const { stdout, stderr, status } = await executeShellCommand({ command: shellTarget, args: ["-i", "-c", "alias"], cwd: process.cwd() });
|
|
14
|
+
if (status !== 0) {
|
|
15
|
+
log.debug({ msg: "failed to load bash aliases", stderr, status });
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
return stdout
|
|
19
|
+
.trim()
|
|
20
|
+
.split("\n")
|
|
21
|
+
.forEach((line) => {
|
|
22
|
+
const [alias, ...commandSegments] = line.replace("alias ", "").replaceAll("'\\''", "'").split("=");
|
|
23
|
+
loadedAliases[alias] = parseCommand(commandSegments.join("=").slice(1, -1) + " ");
|
|
24
|
+
});
|
|
25
|
+
};
|
|
26
|
+
const loadZshAliases = async () => {
|
|
27
|
+
const { stdout, stderr, status } = await executeShellCommand({ command: Shell.Zsh, args: ["-i", "-c", "alias"], cwd: process.cwd() });
|
|
28
|
+
if (status !== 0) {
|
|
29
|
+
log.debug({ msg: "failed to load zsh aliases", stderr, status });
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
return stdout
|
|
33
|
+
.trim()
|
|
34
|
+
.split("\n")
|
|
35
|
+
.forEach((line) => {
|
|
36
|
+
const [alias, ...commandSegments] = line.replaceAll("'\\''", "'").split("=");
|
|
37
|
+
loadedAliases[alias] = parseCommand(commandSegments.join("=").slice(1, -1) + " ");
|
|
38
|
+
});
|
|
39
|
+
};
|
|
40
|
+
export const loadAliases = async (shell) => {
|
|
41
|
+
switch (shell) {
|
|
42
|
+
case Shell.Bash:
|
|
43
|
+
await loadBashAliases();
|
|
44
|
+
break;
|
|
45
|
+
case Shell.Zsh:
|
|
46
|
+
await loadZshAliases();
|
|
47
|
+
break;
|
|
48
|
+
}
|
|
49
|
+
return [];
|
|
50
|
+
};
|
|
51
|
+
export const aliasExpand = (command) => {
|
|
52
|
+
if (!command.at(0)?.complete)
|
|
53
|
+
return command;
|
|
54
|
+
const alias = loadedAliases[command.at(0)?.token ?? ""];
|
|
55
|
+
if (alias) {
|
|
56
|
+
log.debug({ msg: "expanding alias", alias, command: command.slice(1) });
|
|
57
|
+
return [...alias, ...command.slice(1)];
|
|
58
|
+
}
|
|
59
|
+
return command;
|
|
60
|
+
};
|
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
|
@@ -8,25 +8,31 @@ import path from "node:path";
|
|
|
8
8
|
import { parseCommand } from "./parser.js";
|
|
9
9
|
import { getArgDrivenRecommendation, getSubcommandDrivenRecommendation } from "./suggestion.js";
|
|
10
10
|
import { buildExecuteShellCommand, resolveCwd } from "./utils.js";
|
|
11
|
+
import { aliasExpand } from "./alias.js";
|
|
12
|
+
import { getConfig } from "../utils/config.js";
|
|
13
|
+
import log from "../utils/log.js";
|
|
11
14
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- recursive type, setting as any
|
|
12
15
|
const specSet = {};
|
|
13
|
-
speclist
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
16
|
+
function loadSpecsSet(speclist, versionedSpeclist, specsPath) {
|
|
17
|
+
speclist.forEach((s) => {
|
|
18
|
+
let activeSet = specSet;
|
|
19
|
+
const specRoutes = s.split("/");
|
|
20
|
+
specRoutes.forEach((route, idx) => {
|
|
21
|
+
if (typeof activeSet !== "object") {
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
if (idx === specRoutes.length - 1) {
|
|
25
|
+
const prefix = versionedSpeclist.includes(s) ? "/index.js" : `.js`;
|
|
26
|
+
activeSet[route] = `${specsPath}/${s}${prefix}`;
|
|
27
|
+
}
|
|
28
|
+
else {
|
|
29
|
+
activeSet[route] = activeSet[route] || {};
|
|
30
|
+
activeSet = activeSet[route];
|
|
31
|
+
}
|
|
32
|
+
});
|
|
28
33
|
});
|
|
29
|
-
}
|
|
34
|
+
}
|
|
35
|
+
loadSpecsSet(speclist, versionedSpeclist, `@withfig/autocomplete/build`);
|
|
30
36
|
const loadedSpecs = {};
|
|
31
37
|
const loadSpec = async (cmd) => {
|
|
32
38
|
const rootToken = cmd.at(0);
|
|
@@ -50,12 +56,32 @@ const lazyLoadSpec = async (key) => {
|
|
|
50
56
|
const lazyLoadSpecLocation = async (location) => {
|
|
51
57
|
return; //TODO: implement spec location loading
|
|
52
58
|
};
|
|
59
|
+
export const loadLocalSpecsSet = async () => {
|
|
60
|
+
const specsPath = getConfig()?.specs?.path;
|
|
61
|
+
if (!specsPath) {
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
try {
|
|
65
|
+
await Promise.allSettled(specsPath.map((specPath) => import(path.join(specPath, "index.js"))
|
|
66
|
+
.then((res) => {
|
|
67
|
+
const { default: speclist, diffVersionedCompletions: versionedSpeclist } = res;
|
|
68
|
+
loadSpecsSet(speclist, versionedSpeclist, specPath);
|
|
69
|
+
})
|
|
70
|
+
.catch((e) => {
|
|
71
|
+
log.debug({ msg: "load local spec failed", e: e.message, specPath });
|
|
72
|
+
})));
|
|
73
|
+
}
|
|
74
|
+
catch (e) {
|
|
75
|
+
log.debug({ msg: "load local specs failed", e: e.message, specsPath });
|
|
76
|
+
}
|
|
77
|
+
};
|
|
53
78
|
export const getSuggestions = async (cmd, cwd, shell) => {
|
|
54
|
-
|
|
79
|
+
let activeCmd = parseCommand(cmd);
|
|
55
80
|
const rootToken = activeCmd.at(0);
|
|
56
81
|
if (activeCmd.length === 0 || !rootToken?.complete) {
|
|
57
82
|
return;
|
|
58
83
|
}
|
|
84
|
+
activeCmd = aliasExpand(activeCmd);
|
|
59
85
|
const spec = await loadSpec(activeCmd);
|
|
60
86
|
if (spec == null)
|
|
61
87
|
return;
|
|
@@ -127,17 +153,29 @@ const genSubcommand = async (command, parentCommand) => {
|
|
|
127
153
|
return parentCommand.subcommands[subcommandIdx];
|
|
128
154
|
}
|
|
129
155
|
else {
|
|
130
|
-
parentCommand.subcommands[subcommandIdx] = {
|
|
156
|
+
parentCommand.subcommands[subcommandIdx] = {
|
|
157
|
+
...subcommand,
|
|
158
|
+
...partSpec,
|
|
159
|
+
loadSpec: undefined,
|
|
160
|
+
};
|
|
131
161
|
return parentCommand.subcommands[subcommandIdx];
|
|
132
162
|
}
|
|
133
163
|
}
|
|
134
164
|
case "string": {
|
|
135
165
|
const spec = await lazyLoadSpec(subcommand.loadSpec);
|
|
136
|
-
parentCommand.subcommands[subcommandIdx] = {
|
|
166
|
+
parentCommand.subcommands[subcommandIdx] = {
|
|
167
|
+
...subcommand,
|
|
168
|
+
...(getSubcommand(spec) ?? []),
|
|
169
|
+
loadSpec: undefined,
|
|
170
|
+
};
|
|
137
171
|
return parentCommand.subcommands[subcommandIdx];
|
|
138
172
|
}
|
|
139
173
|
case "object": {
|
|
140
|
-
parentCommand.subcommands[subcommandIdx] = {
|
|
174
|
+
parentCommand.subcommands[subcommandIdx] = {
|
|
175
|
+
...subcommand,
|
|
176
|
+
...(subcommand.loadSpec ?? {}),
|
|
177
|
+
loadSpec: undefined,
|
|
178
|
+
};
|
|
141
179
|
return parentCommand.subcommands[subcommandIdx];
|
|
142
180
|
}
|
|
143
181
|
case "undefined": {
|
|
@@ -164,7 +202,10 @@ const runOption = async (tokens, option, subcommand, cwd, persistentOptions, acc
|
|
|
164
202
|
const args = option.args instanceof Array ? option.args : [option.args];
|
|
165
203
|
return runArg(tokens.slice(1), args, subcommand, cwd, persistentOptions, acceptedTokens.concat(activeToken), true, false);
|
|
166
204
|
}
|
|
167
|
-
return runSubcommand(tokens.slice(1), subcommand, cwd, persistentOptions, acceptedTokens.concat({
|
|
205
|
+
return runSubcommand(tokens.slice(1), subcommand, cwd, persistentOptions, acceptedTokens.concat({
|
|
206
|
+
...activeToken,
|
|
207
|
+
isPersistent,
|
|
208
|
+
}));
|
|
168
209
|
};
|
|
169
210
|
const runArg = async (tokens, args, subcommand, cwd, persistentOptions, acceptedTokens, fromOption, fromVariadic) => {
|
|
170
211
|
if (args.length === 0) {
|
|
@@ -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/config.js
CHANGED
|
@@ -30,6 +30,11 @@ const promptPatternsSchema = {
|
|
|
30
30
|
required: ["regex", "postfix"],
|
|
31
31
|
},
|
|
32
32
|
};
|
|
33
|
+
const specPathsSchema = {
|
|
34
|
+
type: "array",
|
|
35
|
+
items: { type: "string" },
|
|
36
|
+
nullable: true,
|
|
37
|
+
};
|
|
33
38
|
const configSchema = {
|
|
34
39
|
type: "object",
|
|
35
40
|
nullable: true,
|
|
@@ -55,6 +60,13 @@ const configSchema = {
|
|
|
55
60
|
nu: promptPatternsSchema,
|
|
56
61
|
},
|
|
57
62
|
},
|
|
63
|
+
specs: {
|
|
64
|
+
type: "object",
|
|
65
|
+
nullable: true,
|
|
66
|
+
properties: {
|
|
67
|
+
path: specPathsSchema,
|
|
68
|
+
},
|
|
69
|
+
},
|
|
58
70
|
},
|
|
59
71
|
additionalProperties: false,
|
|
60
72
|
};
|
|
@@ -98,6 +110,9 @@ export const loadConfig = async (program) => {
|
|
|
98
110
|
pwsh: config.prompt?.pwsh,
|
|
99
111
|
nu: config.prompt?.nu,
|
|
100
112
|
},
|
|
113
|
+
specs: {
|
|
114
|
+
path: [`${os.homedir()}/.fig/autocomplete/build`, ...(config?.specs?.path ?? [])],
|
|
115
|
+
},
|
|
101
116
|
};
|
|
102
117
|
}
|
|
103
118
|
};
|
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
|
+
};
|