@microsoft/inshellisense 0.0.1-rc.1 → 0.0.1-rc.10

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.
Files changed (47) hide show
  1. package/CODE_OF_CONDUCT.md +9 -9
  2. package/LICENSE +21 -21
  3. package/README.md +114 -55
  4. package/SECURITY.md +41 -41
  5. package/SUPPORT.md +13 -13
  6. package/build/commands/complete.js +16 -0
  7. package/build/commands/root.js +23 -31
  8. package/build/commands/uninstall.js +11 -0
  9. package/build/index.js +16 -7
  10. package/build/isterm/commandManager.js +262 -0
  11. package/build/isterm/index.js +4 -0
  12. package/build/isterm/pty.js +270 -0
  13. package/build/runtime/generator.js +23 -10
  14. package/build/runtime/parser.js +2 -2
  15. package/build/runtime/runtime.js +44 -27
  16. package/build/runtime/suggestion.js +47 -21
  17. package/build/runtime/template.js +24 -18
  18. package/build/runtime/utils.js +42 -12
  19. package/build/ui/suggestionManager.js +153 -0
  20. package/build/ui/ui-root.js +132 -63
  21. package/build/ui/ui-uninstall.js +9 -0
  22. package/build/ui/utils.js +56 -0
  23. package/build/utils/ansi.js +33 -0
  24. package/build/utils/config.js +107 -0
  25. package/build/utils/log.js +30 -0
  26. package/build/utils/shell.js +98 -0
  27. package/package.json +86 -59
  28. package/shell/bash-preexec.sh +380 -0
  29. package/shell/shellIntegration-env.zsh +9 -0
  30. package/shell/shellIntegration-login.zsh +4 -0
  31. package/shell/shellIntegration-profile.zsh +4 -0
  32. package/shell/shellIntegration-rc.zsh +58 -0
  33. package/shell/shellIntegration.bash +104 -0
  34. package/shell/shellIntegration.fish +19 -0
  35. package/shell/shellIntegration.ps1 +24 -0
  36. package/shell/shellIntegration.xsh +29 -0
  37. package/build/commands/bind.js +0 -12
  38. package/build/ui/input.js +0 -55
  39. package/build/ui/suggestions.js +0 -84
  40. package/build/ui/ui-bind.js +0 -64
  41. package/build/utils/bindings.js +0 -144
  42. package/build/utils/cache.js +0 -21
  43. package/shell/key-bindings-powershell.ps1 +0 -27
  44. package/shell/key-bindings-pwsh.ps1 +0 -27
  45. package/shell/key-bindings.bash +0 -7
  46. package/shell/key-bindings.fish +0 -8
  47. package/shell/key-bindings.zsh +0 -10
@@ -0,0 +1,262 @@
1
+ // Copyright (c) Microsoft Corporation.
2
+ // Licensed under the MIT License.
3
+ import convert from "color-convert";
4
+ import os from "node:os";
5
+ import { Shell } from "../utils/shell.js";
6
+ import log from "../utils/log.js";
7
+ import { getConfig } from "../utils/config.js";
8
+ const maxPromptPollDistance = 10;
9
+ export class CommandManager {
10
+ #activeCommand;
11
+ #previousCommandLines;
12
+ #terminal;
13
+ #shell;
14
+ #supportsProperOscPlacements = os.platform() !== "win32";
15
+ constructor(terminal, shell) {
16
+ this.#terminal = terminal;
17
+ this.#shell = shell;
18
+ this.#activeCommand = {};
19
+ this.#previousCommandLines = new Set();
20
+ if (this.#supportsProperOscPlacements) {
21
+ this.#terminal.parser.registerCsiHandler({ final: "J" }, (params) => {
22
+ if (params.at(0) == 3 || params.at(0) == 2) {
23
+ this.handleClear();
24
+ }
25
+ return false;
26
+ });
27
+ }
28
+ }
29
+ handlePromptStart() {
30
+ if (this.#activeCommand.promptStartMarker?.line == -1) {
31
+ this.#previousCommandLines = new Set();
32
+ }
33
+ this.#activeCommand = { promptStartMarker: this.#terminal.registerMarker(0), hasOutput: false, cursorTerminated: false };
34
+ }
35
+ handlePromptEnd() {
36
+ this.#activeCommand.promptEndMarker = this.#terminal.registerMarker(0);
37
+ if (this.#activeCommand.promptEndMarker?.line === this.#terminal.buffer.active.cursorY) {
38
+ this.#activeCommand.promptEndX = this.#terminal.buffer.active.cursorX;
39
+ }
40
+ if (this.#supportsProperOscPlacements) {
41
+ this.#activeCommand.promptText = this.#terminal.buffer.active.getLine(this.#activeCommand.promptEndMarker?.line ?? 0)?.translateToString(true);
42
+ this.#previousCommandLines.add(this.#activeCommand.promptEndMarker?.line ?? -1);
43
+ }
44
+ }
45
+ handleClear() {
46
+ this.handlePromptStart();
47
+ this.#previousCommandLines = new Set();
48
+ }
49
+ _getWindowsPrompt(y) {
50
+ const line = this.#terminal.buffer.active.getLine(y);
51
+ if (!line) {
52
+ return;
53
+ }
54
+ const lineText = line.translateToString(true);
55
+ if (!lineText) {
56
+ return;
57
+ }
58
+ // User defined prompt
59
+ const inshellisenseConfig = getConfig();
60
+ if (this.#shell == Shell.Bash) {
61
+ if (inshellisenseConfig?.prompt?.bash != null) {
62
+ for (const { regex, postfix } of inshellisenseConfig.prompt.bash) {
63
+ const customPrompt = lineText.match(new RegExp(regex))?.groups?.prompt;
64
+ const adjustedPrompt = this._adjustPrompt(customPrompt, lineText, postfix);
65
+ if (adjustedPrompt) {
66
+ return adjustedPrompt;
67
+ }
68
+ }
69
+ }
70
+ const bashPrompt = lineText.match(/^(?<prompt>.*\$\s?)/)?.groups?.prompt;
71
+ if (bashPrompt) {
72
+ const adjustedPrompt = this._adjustPrompt(bashPrompt, lineText, "$");
73
+ if (adjustedPrompt) {
74
+ return adjustedPrompt;
75
+ }
76
+ }
77
+ }
78
+ if (this.#shell == Shell.Xonsh) {
79
+ if (inshellisenseConfig?.prompt?.xonsh != null) {
80
+ for (const { regex, postfix } of inshellisenseConfig.prompt.xonsh) {
81
+ const customPrompt = lineText.match(new RegExp(regex))?.groups?.prompt;
82
+ const adjustedPrompt = this._adjustPrompt(customPrompt, lineText, postfix);
83
+ if (adjustedPrompt) {
84
+ return adjustedPrompt;
85
+ }
86
+ }
87
+ }
88
+ let xonshPrompt = lineText.match(/(?<prompt>.*@\s?)/)?.groups?.prompt;
89
+ if (xonshPrompt) {
90
+ const adjustedPrompt = this._adjustPrompt(xonshPrompt, lineText, "@");
91
+ if (adjustedPrompt) {
92
+ return adjustedPrompt;
93
+ }
94
+ }
95
+ xonshPrompt = lineText.match(/(?<prompt>.*>\s?)/)?.groups?.prompt;
96
+ if (xonshPrompt) {
97
+ const adjustedPrompt = this._adjustPrompt(xonshPrompt, lineText, ">");
98
+ if (adjustedPrompt) {
99
+ return adjustedPrompt;
100
+ }
101
+ }
102
+ }
103
+ if (this.#shell == Shell.Powershell || this.#shell == Shell.Pwsh) {
104
+ if (inshellisenseConfig?.prompt?.powershell != null) {
105
+ for (const { regex, postfix } of inshellisenseConfig.prompt.powershell) {
106
+ const customPrompt = lineText.match(new RegExp(regex))?.groups?.prompt;
107
+ const adjustedPrompt = this._adjustPrompt(customPrompt, lineText, postfix);
108
+ if (adjustedPrompt) {
109
+ return adjustedPrompt;
110
+ }
111
+ }
112
+ }
113
+ if (inshellisenseConfig?.prompt?.pwsh != null) {
114
+ for (const { regex, postfix } of inshellisenseConfig.prompt.pwsh) {
115
+ const customPrompt = lineText.match(new RegExp(regex))?.groups?.prompt;
116
+ const adjustedPrompt = this._adjustPrompt(customPrompt, lineText, postfix);
117
+ if (adjustedPrompt) {
118
+ return adjustedPrompt;
119
+ }
120
+ }
121
+ }
122
+ const pwshPrompt = lineText.match(/(?<prompt>(\(.+\)\s)?(?:PS.+>\s?))/)?.groups?.prompt;
123
+ if (pwshPrompt) {
124
+ const adjustedPrompt = this._adjustPrompt(pwshPrompt, lineText, ">");
125
+ if (adjustedPrompt) {
126
+ return adjustedPrompt;
127
+ }
128
+ }
129
+ }
130
+ if (this.#shell == Shell.Cmd) {
131
+ return lineText.match(/^(?<prompt>(\(.+\)\s)?(?:[A-Z]:\\.*>)|(> ))/)?.groups?.prompt;
132
+ }
133
+ // Custom prompts like starship end in the common \u276f character
134
+ const customPrompt = lineText.match(/.*\u276f(?=[^\u276f]*$)/g)?.[0];
135
+ if (customPrompt) {
136
+ const adjustedPrompt = this._adjustPrompt(customPrompt, lineText, "\u276f");
137
+ if (adjustedPrompt) {
138
+ return adjustedPrompt;
139
+ }
140
+ }
141
+ }
142
+ _adjustPrompt(prompt, lineText, char) {
143
+ if (!prompt) {
144
+ return;
145
+ }
146
+ // Conpty may not 'render' the space at the end of the prompt
147
+ if (lineText === prompt && prompt.endsWith(char)) {
148
+ prompt += " ";
149
+ }
150
+ return prompt;
151
+ }
152
+ _getFgPaletteColor(cell) {
153
+ if (cell?.isFgDefault())
154
+ return 0;
155
+ if (cell?.isFgPalette())
156
+ return cell.getFgColor();
157
+ if (cell?.isFgRGB())
158
+ return convert.hex.ansi256(cell.getFgColor().toString(16));
159
+ }
160
+ _isSuggestion(cell) {
161
+ const color = this._getFgPaletteColor(cell);
162
+ const dim = (cell?.isDim() ?? 0) > 0;
163
+ const italic = (cell?.isItalic() ?? 0) > 0;
164
+ const dullColor = color == 8 || color == 7 || (color ?? 0) > 235 || (color == 15 && dim);
165
+ const dullItalic = (color ?? 0) > 235 || (dullColor && italic);
166
+ if (this.#shell == Shell.Powershell) {
167
+ return false;
168
+ }
169
+ else if (this.#shell == Shell.Pwsh) {
170
+ return dullItalic;
171
+ }
172
+ return dullColor;
173
+ }
174
+ getState() {
175
+ return {
176
+ promptText: this.#activeCommand.promptText,
177
+ commandText: this.#activeCommand.commandText,
178
+ suggestionsText: this.#activeCommand.suggestionsText,
179
+ hasOutput: this.#activeCommand.hasOutput,
180
+ cursorTerminated: this.#activeCommand.cursorTerminated,
181
+ };
182
+ }
183
+ termSync() {
184
+ if (this.#activeCommand.promptEndMarker == null || this.#activeCommand.promptStartMarker == null) {
185
+ return;
186
+ }
187
+ const globalCursorPosition = this.#terminal.buffer.active.baseY + this.#terminal.buffer.active.cursorY;
188
+ const withinPollDistance = globalCursorPosition < this.#activeCommand.promptEndMarker.line + 5;
189
+ if (globalCursorPosition < this.#activeCommand.promptStartMarker.line) {
190
+ this.handleClear();
191
+ this.#activeCommand.promptEndMarker = this.#terminal.registerMarker(0);
192
+ }
193
+ if (this.#activeCommand.promptEndMarker == null)
194
+ return;
195
+ // if we haven't fond the prompt yet, poll over the next 5 lines searching for it
196
+ if (this.#activeCommand.promptText == null && withinPollDistance) {
197
+ for (let i = globalCursorPosition; i < this.#activeCommand.promptEndMarker.line + maxPromptPollDistance; i++) {
198
+ if (this.#previousCommandLines.has(i))
199
+ continue;
200
+ const promptResult = this._getWindowsPrompt(i);
201
+ if (promptResult != null) {
202
+ this.#activeCommand.promptEndMarker = this.#terminal.registerMarker(i - globalCursorPosition);
203
+ this.#activeCommand.promptEndX = promptResult.length;
204
+ this.#activeCommand.promptText = promptResult;
205
+ this.#previousCommandLines.add(i);
206
+ break;
207
+ }
208
+ }
209
+ }
210
+ // if the prompt is set, now parse out the values from the terminal
211
+ if (this.#activeCommand.promptText != null) {
212
+ let lineY = this.#activeCommand.promptEndMarker.line;
213
+ let line = this.#terminal.buffer.active.getLine(this.#activeCommand.promptEndMarker.line);
214
+ let command = "";
215
+ let suggestions = "";
216
+ for (;;) {
217
+ for (let i = lineY == this.#activeCommand.promptEndMarker.line ? this.#activeCommand.promptText.length : 0; i < this.#terminal.cols; i++) {
218
+ const cell = line?.getCell(i);
219
+ if (cell == null)
220
+ continue;
221
+ const chars = cell.getChars();
222
+ const cleanedChars = chars == "" ? " " : chars;
223
+ if (!this._isSuggestion(cell) && suggestions.length == 0) {
224
+ command += cleanedChars;
225
+ }
226
+ else {
227
+ suggestions += cleanedChars;
228
+ }
229
+ }
230
+ lineY += 1;
231
+ line = this.#terminal.buffer.active.getLine(lineY);
232
+ if (!line?.isWrapped) {
233
+ break;
234
+ }
235
+ }
236
+ const cursorAtEndOfInput = (this.#activeCommand.promptText.length + command.trim().length) % this.#terminal.cols <= this.#terminal.buffer.active.cursorX;
237
+ let hasOutput = false;
238
+ let cell = undefined;
239
+ for (let i = 0; i < this.#terminal.cols; i++) {
240
+ cell = line?.getCell(i, cell);
241
+ if (cell == null)
242
+ continue;
243
+ hasOutput = cell.getChars() != "";
244
+ if (hasOutput) {
245
+ break;
246
+ }
247
+ }
248
+ const commandPostfix = this.#activeCommand.promptText.length + command.trim().length < this.#terminal.buffer.active.cursorX ? " " : "";
249
+ this.#activeCommand.persistentOutput = this.#activeCommand.hasOutput && hasOutput;
250
+ this.#activeCommand.hasOutput = hasOutput;
251
+ this.#activeCommand.suggestionsText = suggestions.trim();
252
+ this.#activeCommand.commandText = command.trim() + commandPostfix;
253
+ this.#activeCommand.cursorTerminated = cursorAtEndOfInput;
254
+ }
255
+ log.debug({
256
+ msg: "cmd manager state",
257
+ ...this.#activeCommand,
258
+ promptEndMarker: this.#activeCommand.promptEndMarker?.line,
259
+ promptStartMarker: this.#activeCommand.promptStartMarker?.line,
260
+ });
261
+ }
262
+ }
@@ -0,0 +1,4 @@
1
+ // Copyright (c) Microsoft Corporation.
2
+ // Licensed under the MIT License.
3
+ import { spawn } from "./pty.js";
4
+ export default { spawn };
@@ -0,0 +1,270 @@
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 pty from "@homebridge/node-pty-prebuilt-multiarch";
10
+ import { Shell, getPythonPath, userZdotdir, zdotdir } from "../utils/shell.js";
11
+ import { IsTermOscPs, IstermOscPt, IstermPromptStart, IstermPromptEnd } from "../utils/ansi.js";
12
+ import xterm from "@xterm/headless";
13
+ import { CommandManager } from "./commandManager.js";
14
+ import log from "../utils/log.js";
15
+ import { gitBashPath } from "../utils/shell.js";
16
+ import ansi from "ansi-escapes";
17
+ import styles from "ansi-styles";
18
+ const ISTermOnDataEvent = "data";
19
+ export class ISTerm {
20
+ pid;
21
+ cols;
22
+ rows;
23
+ process;
24
+ handleFlowControl = false;
25
+ onData;
26
+ onExit;
27
+ shellBuffer;
28
+ cwd = "";
29
+ #pty;
30
+ #ptyEmitter;
31
+ #term;
32
+ #commandManager;
33
+ #shell;
34
+ constructor({ shell, cols, rows, env, shellTarget, shellArgs, underTest }) {
35
+ this.#pty = pty.spawn(shellTarget, shellArgs ?? [], {
36
+ name: "xterm-256color",
37
+ cols,
38
+ rows,
39
+ cwd: process.cwd(),
40
+ env: { ...convertToPtyEnv(shell, underTest), ...env },
41
+ });
42
+ this.pid = this.#pty.pid;
43
+ this.cols = this.#pty.cols;
44
+ this.rows = this.#pty.rows;
45
+ this.process = this.#pty.process;
46
+ this.#term = new xterm.Terminal({ allowProposedApi: true, rows, cols });
47
+ this.#term.parser.registerOscHandler(IsTermOscPs, (data) => this._handleIsSequence(data));
48
+ this.#commandManager = new CommandManager(this.#term, shell);
49
+ this.#shell = shell;
50
+ this.#ptyEmitter = new EventEmitter();
51
+ this.#pty.onData((data) => {
52
+ this.#term.write(data, () => {
53
+ log.debug({ msg: "parsing data", data, bytes: Uint8Array.from([...data].map((c) => c.charCodeAt(0))) });
54
+ this.#commandManager.termSync();
55
+ this.#ptyEmitter.emit(ISTermOnDataEvent, data);
56
+ });
57
+ });
58
+ this.onData = (listener) => {
59
+ this.#ptyEmitter.on(ISTermOnDataEvent, listener);
60
+ return {
61
+ dispose: () => this.#ptyEmitter.removeListener(ISTermOnDataEvent, listener),
62
+ };
63
+ };
64
+ this.onExit = this.#pty.onExit;
65
+ }
66
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
67
+ on(_event, _listener) {
68
+ throw new Error("Method not implemented as deprecated in node-pty.");
69
+ }
70
+ _deserializeIsMessage(message) {
71
+ return message.replaceAll(/\\(\\|x([0-9a-f]{2}))/gi, (_match, op, hex) => (hex ? String.fromCharCode(parseInt(hex, 16)) : op));
72
+ }
73
+ _sanitizedCwd(cwd) {
74
+ if (cwd.match(/^['"].*['"]$/)) {
75
+ cwd = cwd.substring(1, cwd.length - 1);
76
+ }
77
+ // Convert a drive prefix to windows style when using Git Bash
78
+ if (os.platform() === "win32" && this.#shell == Shell.Bash && cwd && cwd.match(/^\/[A-z]{1}\//)) {
79
+ cwd = `${cwd[1]}:\\` + cwd.substring(3, cwd.length);
80
+ }
81
+ // Make the drive letter uppercase on Windows (see vscode #9448)
82
+ if (os.platform() === "win32" && cwd && cwd[1] === ":") {
83
+ return cwd[0].toUpperCase() + cwd.substring(1);
84
+ }
85
+ return cwd;
86
+ }
87
+ _handleIsSequence(data) {
88
+ const argsIndex = data.indexOf(";");
89
+ const sequence = argsIndex === -1 ? data : data.substring(0, argsIndex);
90
+ switch (sequence) {
91
+ case IstermOscPt.PromptStarted:
92
+ this.#commandManager.handlePromptStart();
93
+ break;
94
+ case IstermOscPt.PromptEnded:
95
+ this.#commandManager.handlePromptEnd();
96
+ break;
97
+ case IstermOscPt.CurrentWorkingDirectory: {
98
+ const cwd = data.split(";").at(1);
99
+ if (cwd != null) {
100
+ this.cwd = path.resolve(this._sanitizedCwd(this._deserializeIsMessage(cwd)));
101
+ }
102
+ break;
103
+ }
104
+ default:
105
+ return false;
106
+ }
107
+ return true;
108
+ }
109
+ noop() {
110
+ this.#ptyEmitter.emit(ISTermOnDataEvent, "");
111
+ }
112
+ resize(columns, rows) {
113
+ this.cols = columns;
114
+ this.rows = rows;
115
+ this.#pty.resize(columns, rows);
116
+ this.#term.resize(columns, rows);
117
+ }
118
+ clear() {
119
+ this.#term.reset();
120
+ this.#pty.clear();
121
+ }
122
+ kill(signal) {
123
+ this.#pty.kill(signal);
124
+ }
125
+ pause() {
126
+ this.#pty.pause();
127
+ }
128
+ resume() {
129
+ this.#pty.resume();
130
+ }
131
+ write(data) {
132
+ log.debug({ msg: "reading data", data, bytes: Uint8Array.from([...data].map((c) => c.charCodeAt(0))) });
133
+ this.#pty.write(data);
134
+ }
135
+ getCommandState() {
136
+ return this.#commandManager.getState();
137
+ }
138
+ getCursorState() {
139
+ return {
140
+ onLastLine: this.#term.buffer.active.cursorY >= this.#term.rows - 2,
141
+ remainingLines: Math.max(this.#term.rows - 2 - this.#term.buffer.active.cursorY, 0),
142
+ cursorX: this.#term.buffer.active.cursorX,
143
+ cursorY: this.#term.buffer.active.cursorY,
144
+ };
145
+ }
146
+ _sameColor(baseCell, targetCell) {
147
+ return (baseCell?.getBgColorMode() == targetCell?.getBgColorMode() &&
148
+ baseCell?.getBgColor() == targetCell?.getBgColor() &&
149
+ baseCell?.getFgColorMode() == targetCell?.getFgColorMode() &&
150
+ baseCell?.getFgColor() == targetCell?.getFgColor());
151
+ }
152
+ _getAnsiColors(cell) {
153
+ if (cell == null)
154
+ return "";
155
+ let bgAnsi = "";
156
+ cell.getBgColor;
157
+ cell.getFgColor;
158
+ if (cell.isBgDefault()) {
159
+ bgAnsi = "\x1b[49m";
160
+ }
161
+ else if (cell.isBgPalette()) {
162
+ bgAnsi = `\x1b[48;5;${cell.getBgColor()}m`;
163
+ }
164
+ else {
165
+ bgAnsi = `\x1b[48;5;${styles.hexToAnsi256(cell.getBgColor().toString(16))}m`;
166
+ }
167
+ let fgAnsi = "";
168
+ if (cell.isFgDefault()) {
169
+ fgAnsi = "\x1b[39m";
170
+ }
171
+ else if (cell.isFgPalette()) {
172
+ fgAnsi = `\x1b[38;5;${cell.getFgColor()}m`;
173
+ }
174
+ else {
175
+ fgAnsi = `\x1b[38;5;${styles.hexToAnsi256(cell.getFgColor().toString(16))}m`;
176
+ }
177
+ return bgAnsi + fgAnsi;
178
+ }
179
+ getCells(height, direction) {
180
+ const currentCursorPosition = this.#term.buffer.active.cursorY + this.#term.buffer.active.baseY;
181
+ const writeLine = (y) => {
182
+ const line = this.#term.buffer.active.getLine(y);
183
+ const ansiLine = ["\x1b[0m"];
184
+ if (line == null)
185
+ return "";
186
+ let prevCell;
187
+ for (let x = 0; x < line.length; x++) {
188
+ const cell = line.getCell(x);
189
+ const chars = cell?.getChars() ?? "";
190
+ if (!this._sameColor(prevCell, cell)) {
191
+ ansiLine.push(this._getAnsiColors(cell));
192
+ }
193
+ ansiLine.push(chars == "" ? " " : chars);
194
+ prevCell = cell;
195
+ }
196
+ return ansiLine.join("");
197
+ };
198
+ const lines = [];
199
+ if (direction == "above") {
200
+ const startCursorPosition = currentCursorPosition - 1;
201
+ const endCursorPosition = currentCursorPosition - 1 - height;
202
+ for (let y = startCursorPosition; y > endCursorPosition; y--) {
203
+ lines.push(writeLine(y));
204
+ }
205
+ }
206
+ else {
207
+ const startCursorPosition = currentCursorPosition + 1;
208
+ const endCursorPosition = currentCursorPosition + 1 + height;
209
+ for (let y = startCursorPosition; y < endCursorPosition; y++) {
210
+ lines.push(writeLine(y));
211
+ }
212
+ }
213
+ return lines.reverse().join(ansi.cursorNextLine);
214
+ }
215
+ }
216
+ export const spawn = async (options) => {
217
+ const { shellTarget, shellArgs } = await convertToPtyTarget(options.shell);
218
+ return new ISTerm({ ...options, shellTarget, shellArgs });
219
+ };
220
+ const convertToPtyTarget = async (shell) => {
221
+ const platform = os.platform();
222
+ let shellTarget = shell == Shell.Bash && platform == "win32" ? await gitBashPath() : platform == "win32" ? `${shell}.exe` : shell;
223
+ const shellFolderPath = path.join(path.dirname(url.fileURLToPath(import.meta.url)), "..", "..", "shell");
224
+ let shellArgs = [];
225
+ switch (shell) {
226
+ case Shell.Bash:
227
+ shellArgs = ["--init-file", path.join(shellFolderPath, "shellIntegration.bash")];
228
+ break;
229
+ case Shell.Powershell:
230
+ case Shell.Pwsh:
231
+ shellArgs = ["-noexit", "-command", `try { . "${path.join(shellFolderPath, "shellIntegration.ps1")}" } catch {}`];
232
+ break;
233
+ case Shell.Fish:
234
+ shellArgs = ["--init-command", `. ${path.join(shellFolderPath, "shellIntegration.fish").replace(/(\s+)/g, "\\$1")}`];
235
+ break;
236
+ case Shell.Xonsh: {
237
+ const sharedConfig = os.platform() == "win32" ? path.join("C:\\ProgramData", "xonsh", "xonshrc") : path.join("etc", "xonsh", "xonshrc");
238
+ const userConfigs = [
239
+ path.join(os.homedir(), ".xonshrc"),
240
+ path.join(os.homedir(), ".config", "xonsh", "rc.xsh"),
241
+ path.join(os.homedir(), ".config", "xonsh", "rc.d"),
242
+ ];
243
+ const configs = [sharedConfig, ...userConfigs].filter((config) => fs.existsSync(config));
244
+ shellArgs = ["-m", "xonsh", "--rc", ...configs, path.join(shellFolderPath, "shellIntegration.xsh")];
245
+ shellTarget = await getPythonPath();
246
+ break;
247
+ }
248
+ }
249
+ return { shellTarget, shellArgs };
250
+ };
251
+ const convertToPtyEnv = (shell, underTest) => {
252
+ const env = {
253
+ ...process.env,
254
+ ISTERM: "1",
255
+ ISTERM_TESTING: underTest ? "1" : undefined,
256
+ };
257
+ switch (shell) {
258
+ case Shell.Cmd: {
259
+ if (underTest) {
260
+ return { ...env, PROMPT: `${IstermPromptStart}$G ${IstermPromptEnd}` };
261
+ }
262
+ const prompt = process.env.PROMPT ? process.env.PROMPT : "$P$G";
263
+ return { ...env, PROMPT: `${IstermPromptStart}${prompt}${IstermPromptEnd}` };
264
+ }
265
+ case Shell.Zsh: {
266
+ return { ...env, ZDOTDIR: zdotdir, USER_ZDOTDIR: userZdotdir };
267
+ }
268
+ }
269
+ return env;
270
+ };
@@ -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: process.cwd(),
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
- const { script, postProcess, scriptTimeout, splitOn, custom, template } = generator;
17
+ export const runGenerator = async (generator, tokens, cwd) => {
18
+ // TODO: support trigger
19
+ const { script, postProcess, scriptTimeout, splitOn, custom, template, filterTemplateSuggestions } = generator;
18
20
  const executeShellCommand = buildExecuteShellCommand(scriptTimeout ?? 5000);
19
21
  const suggestions = [];
20
22
  try {
21
23
  if (script) {
22
- const scriptOutput = typeof script === "function" ? script(tokens) : script != null ? await executeShellCommand(script) : "";
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(scriptOutput, tokens));
30
+ suggestions.push(...postProcess(scriptStdout, tokens));
25
31
  }
26
32
  else if (splitOn) {
27
- suggestions.push(...scriptOutput.split(splitOn).map((s) => ({ name: s })));
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
- suggestions.push(...(await runTemplates(template)));
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
- /* empty */
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
  };
@@ -1,6 +1,6 @@
1
1
  // Copyright (c) Microsoft Corporation.
2
2
  // Licensed under the MIT License.
3
- const cmdDelim = /(\|\|)|(&&)|(;)/;
3
+ const cmdDelim = /(\|\|)|(&&)|(;)|(\|)/;
4
4
  const spaceRegex = /\s/;
5
5
  export const parseCommand = (command) => {
6
6
  const lastCommand = command.split(cmdDelim).at(-1)?.trimStart();
@@ -56,7 +56,7 @@ const lex = (command) => {
56
56
  tokens.push({
57
57
  token: command.slice(readingIdx),
58
58
  complete: false,
59
- isOption: false,
59
+ isOption: readingFlag,
60
60
  });
61
61
  }
62
62
  return tokens;