@microsoft/inshellisense 0.0.1-rc.10 → 0.0.1-rc.12
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 -6
- package/build/commands/root.js +4 -0
- package/build/isterm/commandManager.js +58 -37
- package/build/isterm/pty.js +35 -7
- package/build/runtime/alias.js +60 -0
- package/build/runtime/runtime.js +62 -21
- package/build/runtime/template.js +11 -2
- package/build/runtime/utils.js +2 -2
- package/build/ui/ui-root.js +3 -3
- package/build/utils/config.js +17 -0
- package/build/utils/shell.js +6 -3
- package/package.json +1 -1
- package/shell/shellIntegration.bash +1 -11
- package/shell/shellIntegration.nu +28 -0
- package/shell/shellIntegration.xsh +1 -1
package/README.md
CHANGED
|
@@ -22,12 +22,11 @@ After completing the installation, you can run `is` to start the autocomplete se
|
|
|
22
22
|
|
|
23
23
|
### Usage
|
|
24
24
|
|
|
25
|
-
| Action | Command
|
|
26
|
-
| ------------------------------------- |
|
|
27
|
-
| Start | `is`
|
|
28
|
-
| Stop | `exit`
|
|
29
|
-
| Check If Inside Inshellisense Session | `is -c`
|
|
30
|
-
|
|
25
|
+
| Action | Command | Description |
|
|
26
|
+
| ------------------------------------- | ------- | ------------------------------------------------ |
|
|
27
|
+
| Start | `is` | Start inshellisense session on the current shell |
|
|
28
|
+
| Stop | `exit` | Stop inshellisense session on the current shell |
|
|
29
|
+
| Check If Inside Inshellisense Session | `is -c` | Check if shell inside inshellisense session |
|
|
31
30
|
|
|
32
31
|
#### Keybindings
|
|
33
32
|
|
|
@@ -51,6 +50,7 @@ inshellisense supports the following shells:
|
|
|
51
50
|
- [powershell](https://learn.microsoft.com/en-us/powershell/scripting/windows-powershell/starting-windows-powershell) (Windows Powershell)
|
|
52
51
|
- [cmd](https://learn.microsoft.com/en-us/windows-server/administration/windows-commands/cmd) _(experimental)_
|
|
53
52
|
- [xonsh](https://xon.sh/)
|
|
53
|
+
- [nushell](https://www.nushell.sh/)
|
|
54
54
|
|
|
55
55
|
## Configuration
|
|
56
56
|
|
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
|
}
|
|
34
|
+
await loadAliases(shell);
|
|
31
35
|
await render(shell, options.test ?? false, options.parentTermExit ?? false);
|
|
32
36
|
};
|
|
@@ -2,21 +2,23 @@
|
|
|
2
2
|
// Licensed under the MIT License.
|
|
3
3
|
import convert from "color-convert";
|
|
4
4
|
import os from "node:os";
|
|
5
|
-
import { Shell } from "../utils/shell.js";
|
|
5
|
+
import { getShellPromptRewrites, Shell } from "../utils/shell.js";
|
|
6
6
|
import log from "../utils/log.js";
|
|
7
7
|
import { getConfig } from "../utils/config.js";
|
|
8
8
|
const maxPromptPollDistance = 10;
|
|
9
9
|
export class CommandManager {
|
|
10
10
|
#activeCommand;
|
|
11
|
-
#previousCommandLines;
|
|
12
11
|
#terminal;
|
|
12
|
+
#previousCommandLines;
|
|
13
13
|
#shell;
|
|
14
|
+
#promptRewrites;
|
|
14
15
|
#supportsProperOscPlacements = os.platform() !== "win32";
|
|
15
16
|
constructor(terminal, shell) {
|
|
16
17
|
this.#terminal = terminal;
|
|
17
18
|
this.#shell = shell;
|
|
18
19
|
this.#activeCommand = {};
|
|
19
20
|
this.#previousCommandLines = new Set();
|
|
21
|
+
this.#promptRewrites = getShellPromptRewrites(shell);
|
|
20
22
|
if (this.#supportsProperOscPlacements) {
|
|
21
23
|
this.#terminal.parser.registerCsiHandler({ final: "J" }, (params) => {
|
|
22
24
|
if (params.at(0) == 3 || params.at(0) == 2) {
|
|
@@ -27,12 +29,11 @@ export class CommandManager {
|
|
|
27
29
|
}
|
|
28
30
|
}
|
|
29
31
|
handlePromptStart() {
|
|
30
|
-
if (this.#activeCommand.promptStartMarker?.line == -1) {
|
|
31
|
-
this.#previousCommandLines = new Set();
|
|
32
|
-
}
|
|
33
32
|
this.#activeCommand = { promptStartMarker: this.#terminal.registerMarker(0), hasOutput: false, cursorTerminated: false };
|
|
34
33
|
}
|
|
35
34
|
handlePromptEnd() {
|
|
35
|
+
if (this.#activeCommand.promptEndMarker != null)
|
|
36
|
+
return;
|
|
36
37
|
this.#activeCommand.promptEndMarker = this.#terminal.registerMarker(0);
|
|
37
38
|
if (this.#activeCommand.promptEndMarker?.line === this.#terminal.buffer.active.cursorY) {
|
|
38
39
|
this.#activeCommand.promptEndX = this.#terminal.buffer.active.cursorX;
|
|
@@ -46,6 +47,15 @@ export class CommandManager {
|
|
|
46
47
|
this.handlePromptStart();
|
|
47
48
|
this.#previousCommandLines = new Set();
|
|
48
49
|
}
|
|
50
|
+
_extractPrompt(lineText, patterns) {
|
|
51
|
+
for (const { regex, postfix } of patterns) {
|
|
52
|
+
const customPrompt = lineText.match(new RegExp(regex))?.groups?.prompt;
|
|
53
|
+
const adjustedPrompt = this._adjustPrompt(customPrompt, lineText, postfix);
|
|
54
|
+
if (adjustedPrompt) {
|
|
55
|
+
return adjustedPrompt;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
49
59
|
_getWindowsPrompt(y) {
|
|
50
60
|
const line = this.#terminal.buffer.active.getLine(y);
|
|
51
61
|
if (!line) {
|
|
@@ -59,13 +69,9 @@ export class CommandManager {
|
|
|
59
69
|
const inshellisenseConfig = getConfig();
|
|
60
70
|
if (this.#shell == Shell.Bash) {
|
|
61
71
|
if (inshellisenseConfig?.prompt?.bash != null) {
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
if (adjustedPrompt) {
|
|
66
|
-
return adjustedPrompt;
|
|
67
|
-
}
|
|
68
|
-
}
|
|
72
|
+
const extractedPrompt = this._extractPrompt(lineText, inshellisenseConfig.prompt.bash);
|
|
73
|
+
if (extractedPrompt)
|
|
74
|
+
return extractedPrompt;
|
|
69
75
|
}
|
|
70
76
|
const bashPrompt = lineText.match(/^(?<prompt>.*\$\s?)/)?.groups?.prompt;
|
|
71
77
|
if (bashPrompt) {
|
|
@@ -75,15 +81,25 @@ export class CommandManager {
|
|
|
75
81
|
}
|
|
76
82
|
}
|
|
77
83
|
}
|
|
84
|
+
if (this.#shell == Shell.Nushell) {
|
|
85
|
+
if (inshellisenseConfig?.prompt?.nu != null) {
|
|
86
|
+
const extractedPrompt = this._extractPrompt(lineText, inshellisenseConfig.prompt.nu);
|
|
87
|
+
if (extractedPrompt)
|
|
88
|
+
return extractedPrompt;
|
|
89
|
+
}
|
|
90
|
+
const nushellPrompt = lineText.match(/(?<prompt>.*>\s?)/)?.groups?.prompt;
|
|
91
|
+
if (nushellPrompt) {
|
|
92
|
+
const adjustedPrompt = this._adjustPrompt(nushellPrompt, lineText, ">");
|
|
93
|
+
if (adjustedPrompt) {
|
|
94
|
+
return adjustedPrompt;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
78
98
|
if (this.#shell == Shell.Xonsh) {
|
|
79
99
|
if (inshellisenseConfig?.prompt?.xonsh != null) {
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
if (adjustedPrompt) {
|
|
84
|
-
return adjustedPrompt;
|
|
85
|
-
}
|
|
86
|
-
}
|
|
100
|
+
const extractedPrompt = this._extractPrompt(lineText, inshellisenseConfig.prompt.xonsh);
|
|
101
|
+
if (extractedPrompt)
|
|
102
|
+
return extractedPrompt;
|
|
87
103
|
}
|
|
88
104
|
let xonshPrompt = lineText.match(/(?<prompt>.*@\s?)/)?.groups?.prompt;
|
|
89
105
|
if (xonshPrompt) {
|
|
@@ -102,22 +118,14 @@ export class CommandManager {
|
|
|
102
118
|
}
|
|
103
119
|
if (this.#shell == Shell.Powershell || this.#shell == Shell.Pwsh) {
|
|
104
120
|
if (inshellisenseConfig?.prompt?.powershell != null) {
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
if (adjustedPrompt) {
|
|
109
|
-
return adjustedPrompt;
|
|
110
|
-
}
|
|
111
|
-
}
|
|
121
|
+
const extractedPrompt = this._extractPrompt(lineText, inshellisenseConfig.prompt.powershell);
|
|
122
|
+
if (extractedPrompt)
|
|
123
|
+
return extractedPrompt;
|
|
112
124
|
}
|
|
113
125
|
if (inshellisenseConfig?.prompt?.pwsh != null) {
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
if (adjustedPrompt) {
|
|
118
|
-
return adjustedPrompt;
|
|
119
|
-
}
|
|
120
|
-
}
|
|
126
|
+
const extractedPrompt = this._extractPrompt(lineText, inshellisenseConfig.prompt.pwsh);
|
|
127
|
+
if (extractedPrompt)
|
|
128
|
+
return extractedPrompt;
|
|
121
129
|
}
|
|
122
130
|
const pwshPrompt = lineText.match(/(?<prompt>(\(.+\)\s)?(?:PS.+>\s?))/)?.groups?.prompt;
|
|
123
131
|
if (pwshPrompt) {
|
|
@@ -195,7 +203,7 @@ export class CommandManager {
|
|
|
195
203
|
// if we haven't fond the prompt yet, poll over the next 5 lines searching for it
|
|
196
204
|
if (this.#activeCommand.promptText == null && withinPollDistance) {
|
|
197
205
|
for (let i = globalCursorPosition; i < this.#activeCommand.promptEndMarker.line + maxPromptPollDistance; i++) {
|
|
198
|
-
if (this.#previousCommandLines.has(i))
|
|
206
|
+
if (this.#previousCommandLines.has(i) && !this.#promptRewrites)
|
|
199
207
|
continue;
|
|
200
208
|
const promptResult = this._getWindowsPrompt(i);
|
|
201
209
|
if (promptResult != null) {
|
|
@@ -212,9 +220,13 @@ export class CommandManager {
|
|
|
212
220
|
let lineY = this.#activeCommand.promptEndMarker.line;
|
|
213
221
|
let line = this.#terminal.buffer.active.getLine(this.#activeCommand.promptEndMarker.line);
|
|
214
222
|
let command = "";
|
|
223
|
+
let wrappedCommand = "";
|
|
215
224
|
let suggestions = "";
|
|
225
|
+
let isWrapped = false;
|
|
216
226
|
for (;;) {
|
|
217
227
|
for (let i = lineY == this.#activeCommand.promptEndMarker.line ? this.#activeCommand.promptText.length : 0; i < this.#terminal.cols; i++) {
|
|
228
|
+
if (command.endsWith(" "))
|
|
229
|
+
break; // assume that a command that ends with 4 spaces is terminated, avoids capturing right prompts
|
|
218
230
|
const cell = line?.getCell(i);
|
|
219
231
|
if (cell == null)
|
|
220
232
|
continue;
|
|
@@ -222,6 +234,7 @@ export class CommandManager {
|
|
|
222
234
|
const cleanedChars = chars == "" ? " " : chars;
|
|
223
235
|
if (!this._isSuggestion(cell) && suggestions.length == 0) {
|
|
224
236
|
command += cleanedChars;
|
|
237
|
+
wrappedCommand += cleanedChars;
|
|
225
238
|
}
|
|
226
239
|
else {
|
|
227
240
|
suggestions += cleanedChars;
|
|
@@ -229,11 +242,16 @@ export class CommandManager {
|
|
|
229
242
|
}
|
|
230
243
|
lineY += 1;
|
|
231
244
|
line = this.#terminal.buffer.active.getLine(lineY);
|
|
232
|
-
|
|
245
|
+
const wrapped = line?.isWrapped || this.#terminal.buffer.active.cursorY + this.#terminal.buffer.active.baseY != lineY - 1;
|
|
246
|
+
isWrapped = isWrapped || wrapped;
|
|
247
|
+
if (!wrapped) {
|
|
233
248
|
break;
|
|
234
249
|
}
|
|
250
|
+
wrappedCommand = "";
|
|
235
251
|
}
|
|
236
|
-
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;
|
|
237
255
|
let hasOutput = false;
|
|
238
256
|
let cell = undefined;
|
|
239
257
|
for (let i = 0; i < this.#terminal.cols; i++) {
|
|
@@ -245,7 +263,10 @@ export class CommandManager {
|
|
|
245
263
|
break;
|
|
246
264
|
}
|
|
247
265
|
}
|
|
248
|
-
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 ? " " : "";
|
|
249
270
|
this.#activeCommand.persistentOutput = this.#activeCommand.hasOutput && hasOutput;
|
|
250
271
|
this.#activeCommand.hasOutput = hasOutput;
|
|
251
272
|
this.#activeCommand.suggestionsText = suggestions.trim();
|
package/build/isterm/pty.js
CHANGED
|
@@ -7,7 +7,7 @@ import path from "node:path";
|
|
|
7
7
|
import url from "node:url";
|
|
8
8
|
import fs from "node:fs";
|
|
9
9
|
import pty from "@homebridge/node-pty-prebuilt-multiarch";
|
|
10
|
-
import { Shell,
|
|
10
|
+
import { Shell, userZdotdir, zdotdir } from "../utils/shell.js";
|
|
11
11
|
import { IsTermOscPs, IstermOscPt, IstermPromptStart, IstermPromptEnd } from "../utils/ansi.js";
|
|
12
12
|
import xterm from "@xterm/headless";
|
|
13
13
|
import { CommandManager } from "./commandManager.js";
|
|
@@ -143,6 +143,26 @@ export class ISTerm {
|
|
|
143
143
|
cursorY: this.#term.buffer.active.cursorY,
|
|
144
144
|
};
|
|
145
145
|
}
|
|
146
|
+
_sameAccent(baseCell, targetCell) {
|
|
147
|
+
return baseCell?.isBold() == targetCell?.isBold() && baseCell?.isItalic() == targetCell?.isItalic() && baseCell?.isUnderline() == targetCell?.isUnderline();
|
|
148
|
+
}
|
|
149
|
+
_getAnsiAccents(cell) {
|
|
150
|
+
if (cell == null)
|
|
151
|
+
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
|
+
let underlineAnsi = "";
|
|
161
|
+
if (cell.isUnderline()) {
|
|
162
|
+
underlineAnsi = "\x1b[4m";
|
|
163
|
+
}
|
|
164
|
+
return boldAnsi + italicAnsi + underlineAnsi;
|
|
165
|
+
}
|
|
146
166
|
_sameColor(baseCell, targetCell) {
|
|
147
167
|
return (baseCell?.getBgColorMode() == targetCell?.getBgColorMode() &&
|
|
148
168
|
baseCell?.getBgColor() == targetCell?.getBgColor() &&
|
|
@@ -190,6 +210,9 @@ export class ISTerm {
|
|
|
190
210
|
if (!this._sameColor(prevCell, cell)) {
|
|
191
211
|
ansiLine.push(this._getAnsiColors(cell));
|
|
192
212
|
}
|
|
213
|
+
if (!this._sameAccent(prevCell, cell)) {
|
|
214
|
+
ansiLine.push(this._getAnsiAccents(cell));
|
|
215
|
+
}
|
|
193
216
|
ansiLine.push(chars == "" ? " " : chars);
|
|
194
217
|
prevCell = cell;
|
|
195
218
|
}
|
|
@@ -214,12 +237,12 @@ export class ISTerm {
|
|
|
214
237
|
}
|
|
215
238
|
}
|
|
216
239
|
export const spawn = async (options) => {
|
|
217
|
-
const { shellTarget, shellArgs } = await convertToPtyTarget(options.shell);
|
|
240
|
+
const { shellTarget, shellArgs } = await convertToPtyTarget(options.shell, options.underTest);
|
|
218
241
|
return new ISTerm({ ...options, shellTarget, shellArgs });
|
|
219
242
|
};
|
|
220
|
-
const convertToPtyTarget = async (shell) => {
|
|
243
|
+
const convertToPtyTarget = async (shell, underTest) => {
|
|
221
244
|
const platform = os.platform();
|
|
222
|
-
|
|
245
|
+
const shellTarget = shell == Shell.Bash && platform == "win32" ? await gitBashPath() : platform == "win32" ? `${shell}.exe` : shell;
|
|
223
246
|
const shellFolderPath = path.join(path.dirname(url.fileURLToPath(import.meta.url)), "..", "..", "shell");
|
|
224
247
|
let shellArgs = [];
|
|
225
248
|
switch (shell) {
|
|
@@ -241,10 +264,14 @@ const convertToPtyTarget = async (shell) => {
|
|
|
241
264
|
path.join(os.homedir(), ".config", "xonsh", "rc.d"),
|
|
242
265
|
];
|
|
243
266
|
const configs = [sharedConfig, ...userConfigs].filter((config) => fs.existsSync(config));
|
|
244
|
-
shellArgs = ["
|
|
245
|
-
shellTarget = await getPythonPath();
|
|
267
|
+
shellArgs = ["--rc", ...configs, path.join(shellFolderPath, "shellIntegration.xsh")];
|
|
246
268
|
break;
|
|
247
269
|
}
|
|
270
|
+
case Shell.Nushell:
|
|
271
|
+
shellArgs = ["-e", `source \`${path.join(shellFolderPath, "shellIntegration.nu")}\``];
|
|
272
|
+
if (underTest)
|
|
273
|
+
shellArgs.push("-n");
|
|
274
|
+
break;
|
|
248
275
|
}
|
|
249
276
|
return { shellTarget, shellArgs };
|
|
250
277
|
};
|
|
@@ -252,8 +279,9 @@ const convertToPtyEnv = (shell, underTest) => {
|
|
|
252
279
|
const env = {
|
|
253
280
|
...process.env,
|
|
254
281
|
ISTERM: "1",
|
|
255
|
-
ISTERM_TESTING: underTest ? "1" : undefined,
|
|
256
282
|
};
|
|
283
|
+
if (underTest)
|
|
284
|
+
env.ISTERM_TESTING = "1";
|
|
257
285
|
switch (shell) {
|
|
258
286
|
case Shell.Cmd: {
|
|
259
287
|
if (underTest) {
|
|
@@ -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/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) {
|
|
@@ -4,11 +4,20 @@ import fsAsync from "node:fs/promises";
|
|
|
4
4
|
import log from "../utils/log.js";
|
|
5
5
|
const filepathsTemplate = async (cwd) => {
|
|
6
6
|
const files = await fsAsync.readdir(cwd, { withFileTypes: true });
|
|
7
|
-
return files
|
|
7
|
+
return files
|
|
8
|
+
.filter((f) => f.isFile() || f.isDirectory())
|
|
9
|
+
.map((f) => ({ name: f.name, priority: 55, context: { templateType: "filepaths" }, type: f.isDirectory() ? "folder" : "file" }));
|
|
8
10
|
};
|
|
9
11
|
const foldersTemplate = async (cwd) => {
|
|
10
12
|
const files = await fsAsync.readdir(cwd, { withFileTypes: true });
|
|
11
|
-
return files
|
|
13
|
+
return files
|
|
14
|
+
.filter((f) => f.isDirectory())
|
|
15
|
+
.map((f) => ({
|
|
16
|
+
name: f.name,
|
|
17
|
+
priority: 55,
|
|
18
|
+
context: { templateType: "folders" },
|
|
19
|
+
type: "folder",
|
|
20
|
+
}));
|
|
12
21
|
};
|
|
13
22
|
// TODO: implement history template
|
|
14
23
|
const historyTemplate = () => {
|
package/build/runtime/utils.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
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 { 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
9
|
const child = spawn(command, args, { cwd, env });
|
|
@@ -29,7 +29,7 @@ export const resolveCwd = async (cmdToken, cwd, shell) => {
|
|
|
29
29
|
if (cmdToken == null)
|
|
30
30
|
return { cwd, pathy: false, complete: false };
|
|
31
31
|
const { token } = cmdToken;
|
|
32
|
-
const sep = shell
|
|
32
|
+
const sep = getPathSeperator(shell);
|
|
33
33
|
if (!token.includes(sep))
|
|
34
34
|
return { cwd, pathy: false, complete: false };
|
|
35
35
|
const resolvedCwd = path.isAbsolute(token) ? token : path.join(cwd, token);
|
package/build/ui/ui-root.js
CHANGED
|
@@ -4,7 +4,7 @@ import readline from "node:readline";
|
|
|
4
4
|
import ansi from "ansi-escapes";
|
|
5
5
|
import chalk from "chalk";
|
|
6
6
|
import log from "../utils/log.js";
|
|
7
|
-
import {
|
|
7
|
+
import { getBackspaceSequence } from "../utils/shell.js";
|
|
8
8
|
import isterm from "../isterm/index.js";
|
|
9
9
|
import { eraseLinesBelow } from "../utils/ansi.js";
|
|
10
10
|
import { SuggestionManager, MAX_LINES } from "./suggestionManager.js";
|
|
@@ -120,8 +120,8 @@ export const render = async (shell, underTest, parentTermExit) => {
|
|
|
120
120
|
term.noop();
|
|
121
121
|
}
|
|
122
122
|
else if (!inputHandled) {
|
|
123
|
-
if (press.name == "backspace"
|
|
124
|
-
term.write(
|
|
123
|
+
if (press.name == "backspace") {
|
|
124
|
+
term.write(getBackspaceSequence(keyPress, shell));
|
|
125
125
|
}
|
|
126
126
|
else {
|
|
127
127
|
term.write(press.sequence);
|
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,
|
|
@@ -52,6 +57,14 @@ const configSchema = {
|
|
|
52
57
|
pwsh: promptPatternsSchema,
|
|
53
58
|
powershell: promptPatternsSchema,
|
|
54
59
|
xonsh: promptPatternsSchema,
|
|
60
|
+
nu: promptPatternsSchema,
|
|
61
|
+
},
|
|
62
|
+
},
|
|
63
|
+
specs: {
|
|
64
|
+
type: "object",
|
|
65
|
+
nullable: true,
|
|
66
|
+
properties: {
|
|
67
|
+
path: specPathsSchema,
|
|
55
68
|
},
|
|
56
69
|
},
|
|
57
70
|
},
|
|
@@ -95,6 +108,10 @@ export const loadConfig = async (program) => {
|
|
|
95
108
|
powershell: config.prompt?.powershell,
|
|
96
109
|
xonsh: config.prompt?.xonsh,
|
|
97
110
|
pwsh: config.prompt?.pwsh,
|
|
111
|
+
nu: config.prompt?.nu,
|
|
112
|
+
},
|
|
113
|
+
specs: {
|
|
114
|
+
path: [`${os.homedir()}/.fig/autocomplete/build`, ...(config?.specs?.path ?? [])],
|
|
98
115
|
},
|
|
99
116
|
};
|
|
100
117
|
}
|
package/build/utils/shell.js
CHANGED
|
@@ -17,6 +17,7 @@ export var Shell;
|
|
|
17
17
|
Shell["Fish"] = "fish";
|
|
18
18
|
Shell["Cmd"] = "cmd";
|
|
19
19
|
Shell["Xonsh"] = "xonsh";
|
|
20
|
+
Shell["Nushell"] = "nu";
|
|
20
21
|
})(Shell || (Shell = {}));
|
|
21
22
|
export const supportedShells = [
|
|
22
23
|
Shell.Bash,
|
|
@@ -26,6 +27,7 @@ export const supportedShells = [
|
|
|
26
27
|
Shell.Fish,
|
|
27
28
|
process.platform == "win32" ? Shell.Cmd : null,
|
|
28
29
|
Shell.Xonsh,
|
|
30
|
+
Shell.Nushell,
|
|
29
31
|
].filter((shell) => shell != null);
|
|
30
32
|
export const userZdotdir = process.env?.ZDOTDIR ?? os.homedir() ?? `~`;
|
|
31
33
|
export const zdotdir = path.join(os.tmpdir(), `is-zsh`);
|
|
@@ -68,9 +70,6 @@ export const gitBashPath = async () => {
|
|
|
68
70
|
}
|
|
69
71
|
throw new Error("unable to find a git bash executable installed");
|
|
70
72
|
};
|
|
71
|
-
export const getPythonPath = async () => {
|
|
72
|
-
return await which("python", { nothrow: true });
|
|
73
|
-
};
|
|
74
73
|
const getGitBashPaths = async () => {
|
|
75
74
|
const gitDirs = new Set();
|
|
76
75
|
const gitExePath = await which("git.exe", { nothrow: true });
|
|
@@ -96,3 +95,7 @@ const getGitBashPaths = async () => {
|
|
|
96
95
|
gitBashPaths.push(`${process.env["UserProfile"]}\\scoop\\apps\\git-with-openssh\\current\\bin\\bash.exe`);
|
|
97
96
|
return gitBashPaths;
|
|
98
97
|
};
|
|
98
|
+
export const getBackspaceSequence = (press, shell) => shell === Shell.Pwsh || shell === Shell.Powershell || shell === Shell.Cmd || shell === Shell.Nushell ? "\u007F" : press[1].sequence;
|
|
99
|
+
export const getPathSeperator = (shell) => (shell == Shell.Bash || shell == Shell.Xonsh || shell == Shell.Nushell ? "/" : path.sep);
|
|
100
|
+
// nu fully re-writes the prompt every keystroke resulting in duplicate start/end sequences on the same line
|
|
101
|
+
export const getShellPromptRewrites = (shell) => shell == Shell.Nushell;
|
package/package.json
CHANGED
|
@@ -1,16 +1,6 @@
|
|
|
1
1
|
if [ -r ~/.bashrc ]; then
|
|
2
2
|
. ~/.bashrc
|
|
3
3
|
fi
|
|
4
|
-
if [ -r /etc/profile ]; then
|
|
5
|
-
. /etc/profile
|
|
6
|
-
fi
|
|
7
|
-
if [ -r ~/.bash_profile ]; then
|
|
8
|
-
. ~/.bash_profile
|
|
9
|
-
elif [ -r ~/.bash_login ]; then
|
|
10
|
-
. ~/.bash_login
|
|
11
|
-
elif [ -r ~/.profile ]; then
|
|
12
|
-
. ~/.profile
|
|
13
|
-
fi
|
|
14
4
|
|
|
15
5
|
if [ -r ~/.inshellisense/bash-preexec.sh ]; then
|
|
16
6
|
. ~/.inshellisense/bash-preexec.sh
|
|
@@ -62,7 +52,7 @@ __is_precmd() {
|
|
|
62
52
|
__is_update_prompt() {
|
|
63
53
|
if [[ "$__is_custom_PS1" == "" || "$__is_custom_PS1" != "$PS1" ]]; then
|
|
64
54
|
__is_original_PS1=$PS1
|
|
65
|
-
if [ $ISTERM_TESTING == "1" ]; then
|
|
55
|
+
if [[ $ISTERM_TESTING == "1" ]]; then
|
|
66
56
|
__is_original_PS1="> "
|
|
67
57
|
fi
|
|
68
58
|
__is_custom_PS1="\[$(__is_prompt_start)\]$__is_original_PS1\[$(__is_prompt_end)\]"
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
let __is_escape_value = {|x| $x | str replace --all "\\" "\\\\" | str replace --all ";" "\\x3b" }
|
|
2
|
+
|
|
3
|
+
let __is_update_cwd = {
|
|
4
|
+
let pwd = do $__is_escape_value $env.PWD
|
|
5
|
+
$"\e]6973;CWD;($pwd)\a"
|
|
6
|
+
}
|
|
7
|
+
let __is_original_PROMPT_COMMAND = if 'PROMPT_COMMAND' in $env { $env.PROMPT_COMMAND } else { "" }
|
|
8
|
+
let __is_custom_PROMPT_COMMAND = {
|
|
9
|
+
let promptCommandType = $__is_original_PROMPT_COMMAND | describe
|
|
10
|
+
mut cmd = if $promptCommandType == "closure" { do $__is_original_PROMPT_COMMAND } else { $__is_original_PROMPT_COMMAND }
|
|
11
|
+
let pwd = do $__is_update_cwd
|
|
12
|
+
if 'ISTERM_TESTING' in $env {
|
|
13
|
+
$cmd = ""
|
|
14
|
+
}
|
|
15
|
+
$"\e]6973;PS\a($cmd)($pwd)"
|
|
16
|
+
}
|
|
17
|
+
$env.PROMPT_COMMAND = $__is_custom_PROMPT_COMMAND
|
|
18
|
+
|
|
19
|
+
let __is_original_PROMPT_INDICATOR = if 'PROMPT_INDICATOR' in $env { $env.PROMPT_INDICATOR } else { "" }
|
|
20
|
+
let __is_custom_PROMPT_INDICATOR = {
|
|
21
|
+
let indicatorCommandType = $__is_original_PROMPT_INDICATOR | describe
|
|
22
|
+
mut ind = if $indicatorCommandType == "closure" { do $__is_original_PROMPT_INDICATOR } else { $__is_original_PROMPT_INDICATOR }
|
|
23
|
+
if 'ISTERM_TESTING' in $env {
|
|
24
|
+
$ind = "> "
|
|
25
|
+
}
|
|
26
|
+
$"($ind)\e]6973;PE\a"
|
|
27
|
+
}
|
|
28
|
+
$env.PROMPT_INDICATOR = $__is_custom_PROMPT_INDICATOR
|
|
@@ -23,7 +23,7 @@ def __is_update_cwd() -> str:
|
|
|
23
23
|
$PROMPT_FIELDS['__is_prompt_start'] = __is_prompt_start
|
|
24
24
|
$PROMPT_FIELDS['__is_prompt_end'] = __is_prompt_end
|
|
25
25
|
$PROMPT_FIELDS['__is_update_cwd'] = __is_update_cwd
|
|
26
|
-
if $
|
|
26
|
+
if 'ISTERM_TESTING' in ${...}:
|
|
27
27
|
$PROMPT = "> "
|
|
28
28
|
|
|
29
29
|
$PROMPT = "{__is_prompt_start}{__is_update_cwd}" + $PROMPT + "{__is_prompt_end}"
|