@microsoft/inshellisense 0.0.1-rc.2 → 0.0.1-rc.21
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/LICENSE +21 -21
- package/README.md +80 -6
- package/SECURITY.md +41 -41
- package/build/commands/complete.js +16 -0
- package/build/commands/doctor.js +11 -0
- package/build/commands/init.js +24 -0
- package/build/commands/root.js +27 -30
- package/build/commands/specs/list.js +26 -0
- package/build/commands/specs/root.js +8 -0
- package/build/commands/uninstall.js +1 -1
- package/build/index.js +20 -7
- package/build/isterm/commandManager.js +184 -0
- package/build/isterm/index.js +4 -0
- package/build/isterm/pty.js +361 -0
- package/build/runtime/alias.js +66 -0
- package/build/runtime/generator.js +24 -11
- package/build/runtime/parser.js +86 -16
- package/build/runtime/runtime.js +103 -45
- package/build/runtime/spec.js +36 -0
- package/build/runtime/suggestion.js +70 -22
- package/build/runtime/template.js +33 -18
- package/build/runtime/utils.js +111 -12
- package/build/ui/suggestionManager.js +162 -0
- package/build/ui/ui-doctor.js +69 -0
- package/build/ui/ui-root.js +134 -64
- package/build/ui/ui-uninstall.js +3 -5
- package/build/ui/utils.js +57 -0
- package/build/utils/ansi.js +37 -0
- package/build/utils/config.js +99 -0
- package/build/utils/log.js +39 -0
- package/build/utils/shell.js +318 -0
- package/package.json +39 -6
- package/scripts/postinstall.js +9 -0
- package/shell/bash-preexec.sh +380 -0
- package/shell/shellIntegration-env.zsh +12 -0
- package/shell/shellIntegration-login.zsh +9 -0
- package/shell/shellIntegration-profile.zsh +9 -0
- package/shell/shellIntegration-rc.zsh +66 -0
- package/shell/shellIntegration.bash +114 -0
- package/shell/shellIntegration.fish +27 -0
- package/shell/shellIntegration.nu +29 -0
- package/shell/shellIntegration.ps1 +26 -0
- package/shell/shellIntegration.xsh +31 -0
- package/todo.md +17 -0
- package/build/commands/bind.js +0 -12
- package/build/ui/input.js +0 -55
- package/build/ui/suggestions.js +0 -84
- package/build/ui/ui-bind.js +0 -69
- package/build/utils/bindings.js +0 -216
- package/build/utils/cache.js +0 -21
- package/shell/key-bindings-powershell.ps1 +0 -27
- package/shell/key-bindings-pwsh.ps1 +0 -27
- package/shell/key-bindings.bash +0 -7
- package/shell/key-bindings.fish +0 -8
- package/shell/key-bindings.zsh +0 -10
|
@@ -0,0 +1,361 @@
|
|
|
1
|
+
// Copyright (c) Microsoft Corporation.
|
|
2
|
+
// Licensed under the MIT License.
|
|
3
|
+
import { EventEmitter } from "node:events";
|
|
4
|
+
import process from "node:process";
|
|
5
|
+
import os from "node:os";
|
|
6
|
+
import path from "node:path";
|
|
7
|
+
import url from "node:url";
|
|
8
|
+
import fs from "node:fs";
|
|
9
|
+
import stripAnsi from "strip-ansi";
|
|
10
|
+
import { Unicode11Addon } from "@xterm/addon-unicode11";
|
|
11
|
+
import pty from "@homebridge/node-pty-prebuilt-multiarch";
|
|
12
|
+
import { Shell, userZdotdir, zdotdir } from "../utils/shell.js";
|
|
13
|
+
import { IsTermOscPs, IstermOscPt, IstermPromptStart, IstermPromptEnd } from "../utils/ansi.js";
|
|
14
|
+
import xterm from "@xterm/headless";
|
|
15
|
+
import { CommandManager } from "./commandManager.js";
|
|
16
|
+
import log from "../utils/log.js";
|
|
17
|
+
import { gitBashPath } from "../utils/shell.js";
|
|
18
|
+
import styles from "ansi-styles";
|
|
19
|
+
import * as ansi from "../utils/ansi.js";
|
|
20
|
+
import which from "which";
|
|
21
|
+
const ISTermOnDataEvent = "data";
|
|
22
|
+
export class ISTerm {
|
|
23
|
+
pid;
|
|
24
|
+
cols;
|
|
25
|
+
rows;
|
|
26
|
+
process;
|
|
27
|
+
handleFlowControl = false;
|
|
28
|
+
onData;
|
|
29
|
+
onExit;
|
|
30
|
+
shellBuffer;
|
|
31
|
+
cwd = "";
|
|
32
|
+
#pty;
|
|
33
|
+
#ptyEmitter;
|
|
34
|
+
#term;
|
|
35
|
+
#commandManager;
|
|
36
|
+
#shell;
|
|
37
|
+
constructor({ shell, cols, rows, env, shellTarget, shellArgs, underTest, login }) {
|
|
38
|
+
this.#pty = pty.spawn(shellTarget, shellArgs ?? [], {
|
|
39
|
+
name: "xterm-256color",
|
|
40
|
+
cols,
|
|
41
|
+
rows,
|
|
42
|
+
cwd: process.cwd(),
|
|
43
|
+
env: { ...convertToPtyEnv(shell, underTest, login), ...env },
|
|
44
|
+
useConpty: true,
|
|
45
|
+
useConptyDll: true,
|
|
46
|
+
});
|
|
47
|
+
this.pid = this.#pty.pid;
|
|
48
|
+
this.cols = this.#pty.cols;
|
|
49
|
+
this.rows = this.#pty.rows;
|
|
50
|
+
this.process = this.#pty.process;
|
|
51
|
+
const unicode11Addon = new Unicode11Addon();
|
|
52
|
+
this.#term = new xterm.Terminal({ allowProposedApi: true, rows, cols });
|
|
53
|
+
this.#term.loadAddon(unicode11Addon);
|
|
54
|
+
this.#term.unicode.activeVersion = "11";
|
|
55
|
+
this.#term.parser.registerOscHandler(IsTermOscPs, (data) => this._handleIsSequence(data));
|
|
56
|
+
this.#commandManager = new CommandManager(this.#term, shell);
|
|
57
|
+
this.#shell = shell;
|
|
58
|
+
this.#ptyEmitter = new EventEmitter();
|
|
59
|
+
this.#pty.onData((data) => {
|
|
60
|
+
this.#term.write(data, () => {
|
|
61
|
+
log.debug({ msg: "parsing data", data, bytes: Uint8Array.from([...data].map((c) => c.charCodeAt(0))) });
|
|
62
|
+
this.#commandManager.termSync();
|
|
63
|
+
this.#ptyEmitter.emit(ISTermOnDataEvent, data);
|
|
64
|
+
});
|
|
65
|
+
});
|
|
66
|
+
this.onData = (listener) => {
|
|
67
|
+
this.#ptyEmitter.on(ISTermOnDataEvent, listener);
|
|
68
|
+
return {
|
|
69
|
+
dispose: () => this.#ptyEmitter.removeListener(ISTermOnDataEvent, listener),
|
|
70
|
+
};
|
|
71
|
+
};
|
|
72
|
+
this.onExit = this.#pty.onExit;
|
|
73
|
+
}
|
|
74
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
75
|
+
on(_event, _listener) {
|
|
76
|
+
throw new Error("Method not implemented as deprecated in node-pty.");
|
|
77
|
+
}
|
|
78
|
+
_deserializeIsMessage(message) {
|
|
79
|
+
return message.replaceAll(/\\(\\|x([0-9a-f]{2}))/gi, (_match, op, hex) => (hex ? String.fromCharCode(parseInt(hex, 16)) : op));
|
|
80
|
+
}
|
|
81
|
+
_sanitizedCwd(cwd) {
|
|
82
|
+
if (cwd.match(/^['"].*['"]$/)) {
|
|
83
|
+
cwd = cwd.substring(1, cwd.length - 1);
|
|
84
|
+
}
|
|
85
|
+
// Convert a drive prefix to windows style when using Git Bash
|
|
86
|
+
if (os.platform() === "win32" && this.#shell == Shell.Bash && cwd && cwd.match(/^\/[A-z]{1}\//)) {
|
|
87
|
+
cwd = `${cwd[1]}:\\` + cwd.substring(3, cwd.length);
|
|
88
|
+
}
|
|
89
|
+
// Make the drive letter uppercase on Windows (see vscode #9448)
|
|
90
|
+
if (os.platform() === "win32" && cwd && cwd[1] === ":") {
|
|
91
|
+
return cwd[0].toUpperCase() + cwd.substring(1);
|
|
92
|
+
}
|
|
93
|
+
return cwd;
|
|
94
|
+
}
|
|
95
|
+
_sanitizedPrompt(prompt) {
|
|
96
|
+
// eslint-disable-next-line no-control-regex -- strip OSC control sequences
|
|
97
|
+
const oscStrippedPrompt = prompt.replace(/\x1b\][0-9]+;.*\x07/g, "");
|
|
98
|
+
return stripAnsi(oscStrippedPrompt);
|
|
99
|
+
}
|
|
100
|
+
_handleIsSequence(data) {
|
|
101
|
+
const argsIndex = data.indexOf(";");
|
|
102
|
+
const sequence = argsIndex === -1 ? data : data.substring(0, argsIndex);
|
|
103
|
+
switch (sequence) {
|
|
104
|
+
case IstermOscPt.PromptStarted:
|
|
105
|
+
this.#commandManager.handlePromptStart();
|
|
106
|
+
break;
|
|
107
|
+
case IstermOscPt.PromptEnded:
|
|
108
|
+
this.#commandManager.handlePromptEnd();
|
|
109
|
+
break;
|
|
110
|
+
case IstermOscPt.CurrentWorkingDirectory: {
|
|
111
|
+
const cwd = data.split(";").at(1);
|
|
112
|
+
if (cwd != null) {
|
|
113
|
+
this.cwd = path.resolve(this._sanitizedCwd(this._deserializeIsMessage(cwd)));
|
|
114
|
+
}
|
|
115
|
+
break;
|
|
116
|
+
}
|
|
117
|
+
default:
|
|
118
|
+
return false;
|
|
119
|
+
}
|
|
120
|
+
return true;
|
|
121
|
+
}
|
|
122
|
+
noop() {
|
|
123
|
+
this.#ptyEmitter.emit(ISTermOnDataEvent, "");
|
|
124
|
+
}
|
|
125
|
+
resize(columns, rows) {
|
|
126
|
+
this.cols = columns;
|
|
127
|
+
this.rows = rows;
|
|
128
|
+
this.#pty.resize(columns, rows);
|
|
129
|
+
this.#term.resize(columns, rows);
|
|
130
|
+
}
|
|
131
|
+
clear() {
|
|
132
|
+
this.#term.reset();
|
|
133
|
+
this.#pty.clear();
|
|
134
|
+
}
|
|
135
|
+
kill(signal) {
|
|
136
|
+
this.#pty.kill(signal);
|
|
137
|
+
}
|
|
138
|
+
pause() {
|
|
139
|
+
this.#pty.pause();
|
|
140
|
+
}
|
|
141
|
+
resume() {
|
|
142
|
+
this.#pty.resume();
|
|
143
|
+
}
|
|
144
|
+
write(data) {
|
|
145
|
+
log.debug({ msg: "reading data", data, bytes: Uint8Array.from([...data].map((c) => c.charCodeAt(0))) });
|
|
146
|
+
this.#pty.write(data);
|
|
147
|
+
}
|
|
148
|
+
getCommandState() {
|
|
149
|
+
return this.#commandManager.getState();
|
|
150
|
+
}
|
|
151
|
+
getCursorState() {
|
|
152
|
+
return {
|
|
153
|
+
onLastLine: this.#term.buffer.active.cursorY >= this.#term.rows - 2,
|
|
154
|
+
remainingLines: Math.max(this.#term.rows - 2 - this.#term.buffer.active.cursorY, 0),
|
|
155
|
+
cursorX: this.#term.buffer.active.cursorX,
|
|
156
|
+
cursorY: this.#term.buffer.active.cursorY,
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
_sameAccent(baseCell, targetCell) {
|
|
160
|
+
return (baseCell?.isBold() == targetCell?.isBold() &&
|
|
161
|
+
baseCell?.isItalic() == targetCell?.isItalic() &&
|
|
162
|
+
baseCell?.isUnderline() == targetCell?.isUnderline() &&
|
|
163
|
+
baseCell?.extended.underlineStyle == targetCell?.extended.underlineStyle &&
|
|
164
|
+
baseCell?.hasExtendedAttrs() == targetCell?.hasExtendedAttrs() &&
|
|
165
|
+
baseCell?.isInverse() == targetCell?.isInverse() &&
|
|
166
|
+
baseCell?.isBlink() == targetCell?.isBlink() &&
|
|
167
|
+
baseCell?.isInvisible() == targetCell?.isInvisible() &&
|
|
168
|
+
baseCell?.isDim() == targetCell?.isDim() &&
|
|
169
|
+
baseCell?.isStrikethrough() == targetCell?.isStrikethrough());
|
|
170
|
+
}
|
|
171
|
+
_getAnsiAccents(cell) {
|
|
172
|
+
if (cell == null)
|
|
173
|
+
return "";
|
|
174
|
+
let underlineAnsi = "";
|
|
175
|
+
if (cell.isUnderline()) {
|
|
176
|
+
if (cell.hasExtendedAttrs() && cell.extended.underlineStyle) {
|
|
177
|
+
underlineAnsi = `\x1b[4:${cell.extended.underlineStyle}m`;
|
|
178
|
+
}
|
|
179
|
+
else {
|
|
180
|
+
underlineAnsi = "\x1b[4m";
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
const boldAnsi = cell.isBold() ? "\x1b[1m" : "";
|
|
184
|
+
const dimAnsi = cell.isDim() ? "\x1b[2m" : "";
|
|
185
|
+
const italicAnsi = cell.isItalic() ? "\x1b[3m" : "";
|
|
186
|
+
const blinkAnsi = cell.isBlink() ? "\x1b[5m" : "";
|
|
187
|
+
const inverseAnsi = cell.isInverse() ? "\x1b[7m" : "";
|
|
188
|
+
const invisibleAnsi = cell.isInvisible() ? "\x1b[8m" : "";
|
|
189
|
+
const strikethroughAnsi = cell.isStrikethrough() ? "\x1b[9m" : "";
|
|
190
|
+
return boldAnsi + italicAnsi + underlineAnsi + inverseAnsi + dimAnsi + blinkAnsi + invisibleAnsi + strikethroughAnsi;
|
|
191
|
+
}
|
|
192
|
+
_sameColor(baseCell, targetCell) {
|
|
193
|
+
return (baseCell?.getBgColorMode() == targetCell?.getBgColorMode() &&
|
|
194
|
+
baseCell?.getBgColor() == targetCell?.getBgColor() &&
|
|
195
|
+
baseCell?.getFgColorMode() == targetCell?.getFgColorMode() &&
|
|
196
|
+
baseCell?.getFgColor() == targetCell?.getFgColor());
|
|
197
|
+
}
|
|
198
|
+
_getAnsiColors(cell) {
|
|
199
|
+
if (cell == null)
|
|
200
|
+
return "";
|
|
201
|
+
let bgAnsi = "";
|
|
202
|
+
cell.getBgColor;
|
|
203
|
+
cell.getFgColor;
|
|
204
|
+
if (cell.isBgDefault()) {
|
|
205
|
+
bgAnsi = "\x1b[49m";
|
|
206
|
+
}
|
|
207
|
+
else if (cell.isBgPalette()) {
|
|
208
|
+
bgAnsi = `\x1b[48;5;${cell.getBgColor()}m`;
|
|
209
|
+
}
|
|
210
|
+
else {
|
|
211
|
+
bgAnsi = `\x1b[48;5;${styles.hexToAnsi256(cell.getBgColor().toString(16))}m`;
|
|
212
|
+
}
|
|
213
|
+
let fgAnsi = "";
|
|
214
|
+
if (cell.isFgDefault()) {
|
|
215
|
+
fgAnsi = "\x1b[39m";
|
|
216
|
+
}
|
|
217
|
+
else if (cell.isFgPalette()) {
|
|
218
|
+
fgAnsi = `\x1b[38;5;${cell.getFgColor()}m`;
|
|
219
|
+
}
|
|
220
|
+
else {
|
|
221
|
+
fgAnsi = `\x1b[38;5;${styles.hexToAnsi256(cell.getFgColor().toString(16))}m`;
|
|
222
|
+
}
|
|
223
|
+
return bgAnsi + fgAnsi;
|
|
224
|
+
}
|
|
225
|
+
clearCommand() {
|
|
226
|
+
this.#commandManager.clearActiveCommand();
|
|
227
|
+
}
|
|
228
|
+
getCells(height, direction) {
|
|
229
|
+
const currentCursorPosition = this.#term.buffer.active.cursorY + this.#term.buffer.active.baseY;
|
|
230
|
+
const writeLine = (y) => {
|
|
231
|
+
const line = this.#term.buffer.active.getLine(y);
|
|
232
|
+
const ansiLine = [ansi.resetColor, ansi.resetLine];
|
|
233
|
+
if (line == null)
|
|
234
|
+
return "";
|
|
235
|
+
let prevCell;
|
|
236
|
+
for (let x = 0; x < line.length; x++) {
|
|
237
|
+
const cell = line.getCell(x);
|
|
238
|
+
const chars = cell?.getChars() ?? "";
|
|
239
|
+
const sameColor = this._sameColor(prevCell, cell);
|
|
240
|
+
const sameAccents = this._sameAccent(prevCell, cell);
|
|
241
|
+
if (!sameColor || !sameAccents) {
|
|
242
|
+
ansiLine.push(ansi.resetColor);
|
|
243
|
+
}
|
|
244
|
+
if (!sameColor) {
|
|
245
|
+
ansiLine.push(this._getAnsiColors(cell));
|
|
246
|
+
}
|
|
247
|
+
if (!sameAccents) {
|
|
248
|
+
ansiLine.push(this._getAnsiAccents(cell));
|
|
249
|
+
}
|
|
250
|
+
const isWide = prevCell?.getWidth() == 2 && cell?.getWidth() == 0;
|
|
251
|
+
const cursorForward = isWide ? "" : ansi.cursorForward();
|
|
252
|
+
ansiLine.push(chars == "" ? cursorForward : chars);
|
|
253
|
+
prevCell = cell;
|
|
254
|
+
}
|
|
255
|
+
return ansiLine.join("");
|
|
256
|
+
};
|
|
257
|
+
const lines = [];
|
|
258
|
+
if (direction == "above") {
|
|
259
|
+
const startCursorPosition = currentCursorPosition - 1;
|
|
260
|
+
const endCursorPosition = currentCursorPosition - 1 - height;
|
|
261
|
+
for (let y = startCursorPosition; y > endCursorPosition; y--) {
|
|
262
|
+
lines.push(writeLine(y));
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
else {
|
|
266
|
+
const startCursorPosition = currentCursorPosition + 1;
|
|
267
|
+
const endCursorPosition = currentCursorPosition + 1 + height;
|
|
268
|
+
for (let y = startCursorPosition; y < endCursorPosition; y++) {
|
|
269
|
+
lines.push(writeLine(y));
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
return lines.reverse().join(ansi.cursorNextLine);
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
export const spawn = async (program, options) => {
|
|
276
|
+
const { shellTarget, shellArgs } = await convertToPtyTarget(options.shell, options.underTest, options.login);
|
|
277
|
+
if (!(await shellExists(shellTarget))) {
|
|
278
|
+
program.error(`shell not found on PATH: ${shellTarget}`, { exitCode: 1 });
|
|
279
|
+
}
|
|
280
|
+
return new ISTerm({ ...options, shellTarget, shellArgs });
|
|
281
|
+
};
|
|
282
|
+
const shellExists = async (shellTarget) => {
|
|
283
|
+
const fileExists = fs.existsSync(shellTarget);
|
|
284
|
+
const fileOnPath = await which(shellTarget, { nothrow: true });
|
|
285
|
+
return fileExists || fileOnPath != null;
|
|
286
|
+
};
|
|
287
|
+
const convertToPtyTarget = async (shell, underTest, login) => {
|
|
288
|
+
const platform = os.platform();
|
|
289
|
+
const shellTarget = shell == Shell.Bash && platform == "win32" ? await gitBashPath() : platform == "win32" ? `${shell}.exe` : shell;
|
|
290
|
+
const shellFolderPath = path.join(path.dirname(url.fileURLToPath(import.meta.url)), "..", "..", "shell");
|
|
291
|
+
let shellArgs = [];
|
|
292
|
+
switch (shell) {
|
|
293
|
+
case Shell.Bash:
|
|
294
|
+
shellArgs = ["--init-file", path.join(shellFolderPath, "shellIntegration.bash")];
|
|
295
|
+
break;
|
|
296
|
+
case Shell.Powershell:
|
|
297
|
+
case Shell.Pwsh:
|
|
298
|
+
shellArgs = ["-noexit", "-command", `try { . "${path.join(shellFolderPath, "shellIntegration.ps1")}" } catch {}`];
|
|
299
|
+
break;
|
|
300
|
+
case Shell.Fish:
|
|
301
|
+
shellArgs =
|
|
302
|
+
platform == "win32"
|
|
303
|
+
? ["--init-command", `. "$(cygpath -u '${path.join(shellFolderPath, "shellIntegration.fish")}')"`]
|
|
304
|
+
: ["--init-command", `. ${path.join(shellFolderPath, "shellIntegration.fish").replace(/(\s+)/g, "\\$1")}`];
|
|
305
|
+
break;
|
|
306
|
+
case Shell.Xonsh: {
|
|
307
|
+
const sharedConfig = os.platform() == "win32" ? path.join("C:\\ProgramData", "xonsh", "xonshrc") : path.join("etc", "xonsh", "xonshrc");
|
|
308
|
+
const userConfigs = [
|
|
309
|
+
path.join(os.homedir(), ".xonshrc"),
|
|
310
|
+
path.join(os.homedir(), ".config", "xonsh", "rc.xsh"),
|
|
311
|
+
path.join(os.homedir(), ".config", "xonsh", "rc.d"),
|
|
312
|
+
];
|
|
313
|
+
const configs = [sharedConfig, ...userConfigs].filter((config) => fs.existsSync(config));
|
|
314
|
+
shellArgs = ["--rc", ...configs, path.join(shellFolderPath, "shellIntegration.xsh")];
|
|
315
|
+
break;
|
|
316
|
+
}
|
|
317
|
+
case Shell.Nushell:
|
|
318
|
+
shellArgs = ["-e", `source \`${path.join(shellFolderPath, "shellIntegration.nu")}\``];
|
|
319
|
+
if (underTest)
|
|
320
|
+
shellArgs.push("-n");
|
|
321
|
+
break;
|
|
322
|
+
}
|
|
323
|
+
if (login) {
|
|
324
|
+
switch (shell) {
|
|
325
|
+
case Shell.Powershell:
|
|
326
|
+
case Shell.Pwsh:
|
|
327
|
+
shellArgs.unshift("-login");
|
|
328
|
+
break;
|
|
329
|
+
case Shell.Zsh:
|
|
330
|
+
case Shell.Fish:
|
|
331
|
+
case Shell.Xonsh:
|
|
332
|
+
case Shell.Nushell:
|
|
333
|
+
shellArgs.unshift("--login");
|
|
334
|
+
break;
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
return { shellTarget, shellArgs };
|
|
338
|
+
};
|
|
339
|
+
const convertToPtyEnv = (shell, underTest, login) => {
|
|
340
|
+
const env = {
|
|
341
|
+
...process.env,
|
|
342
|
+
ISTERM: "1",
|
|
343
|
+
};
|
|
344
|
+
if (underTest)
|
|
345
|
+
env.ISTERM_TESTING = "1";
|
|
346
|
+
if (login)
|
|
347
|
+
env.ISTERM_LOGIN = "1";
|
|
348
|
+
switch (shell) {
|
|
349
|
+
case Shell.Cmd: {
|
|
350
|
+
if (underTest) {
|
|
351
|
+
return { ...env, PROMPT: `${IstermPromptStart}$G ${IstermPromptEnd}` };
|
|
352
|
+
}
|
|
353
|
+
const prompt = process.env.PROMPT ? process.env.PROMPT : "$P$G";
|
|
354
|
+
return { ...env, PROMPT: `${IstermPromptStart}${prompt}${IstermPromptEnd}` };
|
|
355
|
+
}
|
|
356
|
+
case Shell.Zsh: {
|
|
357
|
+
return { ...env, ZDOTDIR: zdotdir, USER_ZDOTDIR: userZdotdir };
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
return env;
|
|
361
|
+
};
|
|
@@ -0,0 +1,66 @@
|
|
|
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 = await buildExecuteShellCommand(5000);
|
|
11
|
+
const loadBashAliases = async () => {
|
|
12
|
+
const shellTarget = platform == "win32" ? await gitBashPath() : Shell.Bash;
|
|
13
|
+
const { stdout, stderr, status } = await executeShellCommand({
|
|
14
|
+
command: `'${shellTarget}'`,
|
|
15
|
+
args: ["-i", "-c", "alias"],
|
|
16
|
+
cwd: process.cwd(),
|
|
17
|
+
env: { ISTERM: "1" },
|
|
18
|
+
});
|
|
19
|
+
if (status !== 0) {
|
|
20
|
+
log.debug({ msg: "failed to load bash aliases", stderr, status });
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
return stdout
|
|
24
|
+
.trim()
|
|
25
|
+
.split("\n")
|
|
26
|
+
.forEach((line) => {
|
|
27
|
+
const [alias, ...commandSegments] = line.replace("alias ", "").replaceAll("'\\''", "'").split("=");
|
|
28
|
+
loadedAliases[alias] = parseCommand(commandSegments.join("=").slice(1, -1) + " ", Shell.Bash);
|
|
29
|
+
});
|
|
30
|
+
};
|
|
31
|
+
const loadZshAliases = async () => {
|
|
32
|
+
const { stdout, stderr, status } = await executeShellCommand({ command: Shell.Zsh, args: ["-i", "-c", "alias"], cwd: process.cwd(), env: { ISTERM: "1" } });
|
|
33
|
+
if (status !== 0) {
|
|
34
|
+
log.debug({ msg: "failed to load zsh aliases", stderr, status });
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
return stdout
|
|
38
|
+
.trim()
|
|
39
|
+
.split("\n")
|
|
40
|
+
.forEach((line) => {
|
|
41
|
+
const [alias, ...commandSegments] = line.replaceAll("'\\''", "'").split("=");
|
|
42
|
+
loadedAliases[alias] = parseCommand(commandSegments.join("=").slice(1, -1) + " ", Shell.Zsh);
|
|
43
|
+
});
|
|
44
|
+
};
|
|
45
|
+
export const loadAliases = async (shell) => {
|
|
46
|
+
switch (shell) {
|
|
47
|
+
case Shell.Bash:
|
|
48
|
+
await loadBashAliases();
|
|
49
|
+
break;
|
|
50
|
+
case Shell.Zsh:
|
|
51
|
+
await loadZshAliases();
|
|
52
|
+
break;
|
|
53
|
+
}
|
|
54
|
+
return [];
|
|
55
|
+
};
|
|
56
|
+
export const getAliasNames = () => Object.keys(loadedAliases);
|
|
57
|
+
export const aliasExpand = (command) => {
|
|
58
|
+
if (!command.at(0)?.complete)
|
|
59
|
+
return command;
|
|
60
|
+
const alias = loadedAliases[command.at(0)?.token ?? ""];
|
|
61
|
+
if (alias) {
|
|
62
|
+
log.debug({ msg: "expanding alias", alias, command: command.slice(1) });
|
|
63
|
+
return [...alias, ...command.slice(1)];
|
|
64
|
+
}
|
|
65
|
+
return command;
|
|
66
|
+
};
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
// Copyright (c) Microsoft Corporation.
|
|
2
2
|
// Licensed under the MIT License.
|
|
3
|
+
import log from "../utils/log.js";
|
|
3
4
|
import { runTemplates } from "./template.js";
|
|
4
5
|
import { buildExecuteShellCommand } from "./utils.js";
|
|
5
|
-
const getGeneratorContext = () => {
|
|
6
|
+
const getGeneratorContext = (cwd) => {
|
|
6
7
|
return {
|
|
7
8
|
environmentVariables: Object.fromEntries(Object.entries(process.env).filter((entry) => entry[1] != null)),
|
|
8
|
-
currentWorkingDirectory:
|
|
9
|
+
currentWorkingDirectory: cwd,
|
|
9
10
|
currentProcess: "",
|
|
10
11
|
sshPrefix: "",
|
|
11
12
|
isDangerous: false,
|
|
@@ -13,30 +14,42 @@ const getGeneratorContext = () => {
|
|
|
13
14
|
};
|
|
14
15
|
};
|
|
15
16
|
// TODO: add support for caching, trigger, & getQueryTerm
|
|
16
|
-
export const runGenerator = async (generator, tokens) => {
|
|
17
|
-
|
|
18
|
-
const
|
|
17
|
+
export const runGenerator = async (generator, tokens, cwd) => {
|
|
18
|
+
// TODO: support trigger
|
|
19
|
+
const { script, postProcess, scriptTimeout, splitOn, custom, template, filterTemplateSuggestions } = generator;
|
|
20
|
+
const executeShellCommand = await buildExecuteShellCommand(scriptTimeout ?? 5000);
|
|
19
21
|
const suggestions = [];
|
|
20
22
|
try {
|
|
21
23
|
if (script) {
|
|
22
|
-
const
|
|
24
|
+
const shellInput = typeof script === "function" ? script(tokens) : script;
|
|
25
|
+
const scriptOutput = Array.isArray(shellInput)
|
|
26
|
+
? await executeShellCommand({ command: shellInput.at(0) ?? "", args: shellInput.slice(1), cwd })
|
|
27
|
+
: await executeShellCommand({ ...shellInput, cwd });
|
|
28
|
+
const scriptStdout = scriptOutput.stdout.trim();
|
|
23
29
|
if (postProcess) {
|
|
24
|
-
suggestions.push(...postProcess(
|
|
30
|
+
suggestions.push(...postProcess(scriptStdout, tokens));
|
|
25
31
|
}
|
|
26
32
|
else if (splitOn) {
|
|
27
|
-
suggestions.push(...
|
|
33
|
+
suggestions.push(...scriptStdout.split(splitOn).map((s) => ({ name: s })));
|
|
28
34
|
}
|
|
29
35
|
}
|
|
30
36
|
if (custom) {
|
|
31
|
-
suggestions.push(...(await custom(tokens, executeShellCommand, getGeneratorContext())));
|
|
37
|
+
suggestions.push(...(await custom(tokens, executeShellCommand, getGeneratorContext(cwd))));
|
|
32
38
|
}
|
|
33
39
|
if (template != null) {
|
|
34
|
-
|
|
40
|
+
const templateSuggestions = await runTemplates(template, cwd);
|
|
41
|
+
if (filterTemplateSuggestions) {
|
|
42
|
+
suggestions.push(...filterTemplateSuggestions(templateSuggestions));
|
|
43
|
+
}
|
|
44
|
+
else {
|
|
45
|
+
suggestions.push(...templateSuggestions);
|
|
46
|
+
}
|
|
35
47
|
}
|
|
36
48
|
return suggestions;
|
|
37
49
|
}
|
|
38
50
|
catch (e) {
|
|
39
|
-
|
|
51
|
+
const err = typeof e === "string" ? e : e instanceof Error ? e.message : e;
|
|
52
|
+
log.debug({ msg: "generator failed", err, script, splitOn, template });
|
|
40
53
|
}
|
|
41
54
|
return suggestions;
|
|
42
55
|
};
|
package/build/runtime/parser.js
CHANGED
|
@@ -1,19 +1,48 @@
|
|
|
1
1
|
// Copyright (c) Microsoft Corporation.
|
|
2
2
|
// Licensed under the MIT License.
|
|
3
|
-
|
|
3
|
+
import wcwidth from "wcwidth";
|
|
4
|
+
import { getShellWhitespaceEscapeChar } from "./utils.js";
|
|
5
|
+
const cmdDelim = /(\|\|)|(&&)|(;)|(\|)/;
|
|
4
6
|
const spaceRegex = /\s/;
|
|
5
|
-
export const parseCommand = (command) => {
|
|
7
|
+
export const parseCommand = (command, shell) => {
|
|
6
8
|
const lastCommand = command.split(cmdDelim).at(-1)?.trimStart();
|
|
7
|
-
|
|
9
|
+
const tokens = lastCommand ? lex(lastCommand, shell) : [];
|
|
10
|
+
return sanitizeTokens(tokens, shell);
|
|
8
11
|
};
|
|
9
|
-
const
|
|
12
|
+
const sanitizeTokens = (cmdTokens, shell) => unwrapQuotedTokens(unescapeSpaceTokens(cmdTokens, shell), shell);
|
|
13
|
+
// remove escapes around spaces
|
|
14
|
+
const unescapeSpaceTokens = (cmdTokens, shell) => {
|
|
15
|
+
const escapeChar = getShellWhitespaceEscapeChar(shell);
|
|
16
|
+
return cmdTokens.map((cmdToken) => {
|
|
17
|
+
const { token, isQuoted } = cmdToken;
|
|
18
|
+
if (!isQuoted && token.includes(`${escapeChar} `)) {
|
|
19
|
+
return { ...cmdToken, token: token.replaceAll(`${escapeChar} `, " ") };
|
|
20
|
+
}
|
|
21
|
+
return cmdToken;
|
|
22
|
+
});
|
|
23
|
+
};
|
|
24
|
+
// need to unwrap tokens that are quoted with content after the quotes like `"hello"world`
|
|
25
|
+
const unwrapQuotedTokens = (cmdTokens, shell) => {
|
|
26
|
+
const escapeChar = getShellWhitespaceEscapeChar(shell);
|
|
27
|
+
return cmdTokens.map((cmdToken) => {
|
|
28
|
+
const { token, isQuoteContinued } = cmdToken;
|
|
29
|
+
if (isQuoteContinued) {
|
|
30
|
+
const quoteChar = token[0];
|
|
31
|
+
const unquotedToken = token.replaceAll(`${escapeChar}${quoteChar}`, "\u001B").replaceAll(quoteChar, "").replaceAll("\u001B", quoteChar);
|
|
32
|
+
return { ...cmdToken, token: unquotedToken };
|
|
33
|
+
}
|
|
34
|
+
return cmdToken;
|
|
35
|
+
});
|
|
36
|
+
};
|
|
37
|
+
const lex = (command, shell) => {
|
|
10
38
|
const tokens = [];
|
|
11
|
-
|
|
39
|
+
const escapeChar = getShellWhitespaceEscapeChar(shell);
|
|
40
|
+
let [readingQuotedString, readingQuoteContinuedString, readingFlag, readingCmd] = [false, false, false, false];
|
|
12
41
|
let readingIdx = 0;
|
|
13
42
|
let readingQuoteChar = "";
|
|
14
43
|
[...command].forEach((char, idx) => {
|
|
15
|
-
const reading = readingQuotedString || readingFlag || readingCmd;
|
|
16
|
-
if (!reading && (char === `'` || char === `"`)) {
|
|
44
|
+
const reading = readingQuotedString || readingQuoteContinuedString || readingFlag || readingCmd;
|
|
45
|
+
if (!reading && (char === `'` || char === `"` || char == "`")) {
|
|
17
46
|
[readingQuotedString, readingIdx, readingQuoteChar] = [true, idx, char];
|
|
18
47
|
return;
|
|
19
48
|
}
|
|
@@ -25,39 +54,80 @@ const lex = (command) => {
|
|
|
25
54
|
[readingCmd, readingIdx] = [true, idx];
|
|
26
55
|
return;
|
|
27
56
|
}
|
|
28
|
-
if (readingQuotedString && char === readingQuoteChar && command.at(idx - 1) !== "
|
|
57
|
+
if (readingQuotedString && char === readingQuoteChar && command.at(idx - 1) !== escapeChar && !spaceRegex.test(command.at(idx + 1) ?? " ")) {
|
|
58
|
+
readingQuotedString = false;
|
|
59
|
+
readingQuoteContinuedString = true;
|
|
60
|
+
}
|
|
61
|
+
else if (readingQuotedString && char === readingQuoteChar && command.at(idx - 1) !== escapeChar) {
|
|
29
62
|
readingQuotedString = false;
|
|
30
63
|
const complete = idx + 1 < command.length && spaceRegex.test(command[idx + 1]);
|
|
31
64
|
tokens.push({
|
|
32
|
-
token: command.slice(readingIdx
|
|
65
|
+
token: command.slice(readingIdx + 1, idx),
|
|
66
|
+
tokenLength: wcwidth(command.slice(readingIdx + 1, idx)) + 2,
|
|
33
67
|
complete,
|
|
34
68
|
isOption: false,
|
|
69
|
+
isQuoted: true,
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
else if (readingQuoteContinuedString && spaceRegex.test(char) && command.at(idx - 1) !== escapeChar) {
|
|
73
|
+
readingQuoteContinuedString = false;
|
|
74
|
+
tokens.push({
|
|
75
|
+
token: command.slice(readingIdx, idx),
|
|
76
|
+
tokenLength: wcwidth(command.slice(readingIdx, idx)),
|
|
77
|
+
complete: true,
|
|
78
|
+
isOption: false,
|
|
79
|
+
isQuoted: true,
|
|
80
|
+
isQuoteContinued: true,
|
|
35
81
|
});
|
|
36
82
|
}
|
|
37
83
|
else if ((readingFlag && spaceRegex.test(char)) || char === "=") {
|
|
38
84
|
readingFlag = false;
|
|
39
85
|
tokens.push({
|
|
40
86
|
token: command.slice(readingIdx, idx),
|
|
87
|
+
tokenLength: wcwidth(command.slice(readingIdx, idx)),
|
|
41
88
|
complete: true,
|
|
42
89
|
isOption: true,
|
|
43
90
|
});
|
|
44
91
|
}
|
|
45
|
-
else if (readingCmd && spaceRegex.test(char)) {
|
|
92
|
+
else if (readingCmd && spaceRegex.test(char) && command.at(idx - 1) !== escapeChar) {
|
|
46
93
|
readingCmd = false;
|
|
47
94
|
tokens.push({
|
|
48
95
|
token: command.slice(readingIdx, idx),
|
|
96
|
+
tokenLength: wcwidth(command.slice(readingIdx, idx)),
|
|
49
97
|
complete: true,
|
|
50
98
|
isOption: false,
|
|
51
99
|
});
|
|
52
100
|
}
|
|
53
101
|
});
|
|
54
|
-
const reading = readingQuotedString || readingFlag || readingCmd;
|
|
102
|
+
const reading = readingQuotedString || readingQuoteContinuedString || readingFlag || readingCmd;
|
|
55
103
|
if (reading) {
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
104
|
+
if (readingQuotedString) {
|
|
105
|
+
tokens.push({
|
|
106
|
+
token: command.slice(readingIdx + 1),
|
|
107
|
+
tokenLength: wcwidth(command.slice(readingIdx + 1)) + 1,
|
|
108
|
+
complete: false,
|
|
109
|
+
isOption: false,
|
|
110
|
+
isQuoted: true,
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
else if (readingQuoteContinuedString) {
|
|
114
|
+
tokens.push({
|
|
115
|
+
token: command.slice(readingIdx),
|
|
116
|
+
tokenLength: wcwidth(command.slice(readingIdx)),
|
|
117
|
+
complete: false,
|
|
118
|
+
isOption: false,
|
|
119
|
+
isQuoted: true,
|
|
120
|
+
isQuoteContinued: true,
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
else {
|
|
124
|
+
tokens.push({
|
|
125
|
+
token: command.slice(readingIdx),
|
|
126
|
+
tokenLength: wcwidth(command.slice(readingIdx)),
|
|
127
|
+
complete: false,
|
|
128
|
+
isOption: readingFlag,
|
|
129
|
+
});
|
|
130
|
+
}
|
|
61
131
|
}
|
|
62
132
|
return tokens;
|
|
63
133
|
};
|