@microsoft/inshellisense 0.0.1-rc.2 → 0.0.1-rc.20

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 (53) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +80 -6
  3. package/SECURITY.md +41 -41
  4. package/build/commands/complete.js +16 -0
  5. package/build/commands/doctor.js +11 -0
  6. package/build/commands/init.js +24 -0
  7. package/build/commands/root.js +27 -30
  8. package/build/commands/specs/list.js +26 -0
  9. package/build/commands/specs/root.js +8 -0
  10. package/build/commands/uninstall.js +1 -1
  11. package/build/index.js +20 -7
  12. package/build/isterm/commandManager.js +290 -0
  13. package/build/isterm/index.js +4 -0
  14. package/build/isterm/pty.js +372 -0
  15. package/build/runtime/alias.js +61 -0
  16. package/build/runtime/generator.js +24 -11
  17. package/build/runtime/parser.js +86 -16
  18. package/build/runtime/runtime.js +103 -45
  19. package/build/runtime/suggestion.js +70 -22
  20. package/build/runtime/template.js +33 -18
  21. package/build/runtime/utils.js +111 -12
  22. package/build/ui/suggestionManager.js +162 -0
  23. package/build/ui/ui-doctor.js +69 -0
  24. package/build/ui/ui-root.js +130 -64
  25. package/build/ui/ui-uninstall.js +3 -5
  26. package/build/ui/utils.js +57 -0
  27. package/build/utils/ansi.js +37 -0
  28. package/build/utils/config.js +132 -0
  29. package/build/utils/log.js +39 -0
  30. package/build/utils/shell.js +316 -0
  31. package/package.json +39 -6
  32. package/scripts/postinstall.js +9 -0
  33. package/shell/bash-preexec.sh +380 -0
  34. package/shell/shellIntegration-env.zsh +12 -0
  35. package/shell/shellIntegration-login.zsh +9 -0
  36. package/shell/shellIntegration-profile.zsh +9 -0
  37. package/shell/shellIntegration-rc.zsh +66 -0
  38. package/shell/shellIntegration.bash +125 -0
  39. package/shell/shellIntegration.fish +28 -0
  40. package/shell/shellIntegration.nu +36 -0
  41. package/shell/shellIntegration.ps1 +27 -0
  42. package/shell/shellIntegration.xsh +37 -0
  43. package/build/commands/bind.js +0 -12
  44. package/build/ui/input.js +0 -55
  45. package/build/ui/suggestions.js +0 -84
  46. package/build/ui/ui-bind.js +0 -69
  47. package/build/utils/bindings.js +0 -216
  48. package/build/utils/cache.js +0 -21
  49. package/shell/key-bindings-powershell.ps1 +0 -27
  50. package/shell/key-bindings-pwsh.ps1 +0 -27
  51. package/shell/key-bindings.bash +0 -7
  52. package/shell/key-bindings.fish +0 -8
  53. package/shell/key-bindings.zsh +0 -10
@@ -0,0 +1,290 @@
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 { getShellPromptRewrites, Shell } from "../utils/shell.js";
6
+ import log from "../utils/log.js";
7
+ const maxPromptPollDistance = 10;
8
+ export class CommandManager {
9
+ #activeCommand;
10
+ #terminal;
11
+ #previousCommandLines;
12
+ #maxCursorY;
13
+ #shell;
14
+ #promptRewrites;
15
+ #supportsProperOscPlacements = os.platform() !== "win32";
16
+ promptTerminator = "";
17
+ constructor(terminal, shell) {
18
+ this.#terminal = terminal;
19
+ this.#shell = shell;
20
+ this.#activeCommand = {};
21
+ this.#maxCursorY = 0;
22
+ this.#previousCommandLines = new Set();
23
+ this.#promptRewrites = getShellPromptRewrites(shell);
24
+ if (this.#supportsProperOscPlacements) {
25
+ this.#terminal.parser.registerCsiHandler({ final: "J" }, (params) => {
26
+ if (params.at(0) == 3 || params.at(0) == 2) {
27
+ this.handleClear();
28
+ }
29
+ return false;
30
+ });
31
+ }
32
+ }
33
+ handlePromptStart() {
34
+ this.#activeCommand = { promptStartMarker: this.#terminal.registerMarker(0), hasOutput: false, cursorTerminated: false };
35
+ }
36
+ handlePromptEnd() {
37
+ if (this.#activeCommand.promptEndMarker != null)
38
+ return;
39
+ this.#activeCommand.promptEndMarker = this.#terminal.registerMarker(0);
40
+ if (this.#activeCommand.promptEndMarker?.line === this.#terminal.buffer.active.cursorY) {
41
+ this.#activeCommand.promptEndX = this.#terminal.buffer.active.cursorX;
42
+ }
43
+ if (this.#supportsProperOscPlacements) {
44
+ this.#activeCommand.promptText = this.#terminal.buffer.active.getLine(this.#activeCommand.promptEndMarker?.line ?? 0)?.translateToString(true);
45
+ this.#previousCommandLines.add(this.#activeCommand.promptEndMarker?.line ?? -1);
46
+ }
47
+ }
48
+ handleClear() {
49
+ this.handlePromptStart();
50
+ this.#maxCursorY = 0;
51
+ this.#previousCommandLines = new Set();
52
+ }
53
+ _getWindowsPrompt(y) {
54
+ const line = this.#terminal.buffer.active.getLine(y);
55
+ if (!line) {
56
+ return;
57
+ }
58
+ const lineText = line.translateToString(true);
59
+ if (!lineText) {
60
+ return;
61
+ }
62
+ // dynamic prompt terminator
63
+ if (this.promptTerminator && lineText.trim().endsWith(this.promptTerminator)) {
64
+ const adjustedPrompt = this._adjustPrompt(lineText, lineText, this.promptTerminator);
65
+ if (adjustedPrompt) {
66
+ return adjustedPrompt;
67
+ }
68
+ }
69
+ // User defined prompt
70
+ if (this.#shell == Shell.Bash) {
71
+ const bashPrompt = lineText.match(/^(?<prompt>\$\s?)/)?.groups?.prompt;
72
+ if (bashPrompt) {
73
+ const adjustedPrompt = this._adjustPrompt(bashPrompt, lineText, "$");
74
+ if (adjustedPrompt) {
75
+ return adjustedPrompt;
76
+ }
77
+ }
78
+ }
79
+ if (this.#shell == Shell.Fish) {
80
+ const fishPrompt = lineText.match(/(?<prompt>.*>\s?)/)?.groups?.prompt;
81
+ if (fishPrompt) {
82
+ const adjustedPrompt = this._adjustPrompt(fishPrompt, lineText, ">");
83
+ if (adjustedPrompt) {
84
+ return adjustedPrompt;
85
+ }
86
+ }
87
+ }
88
+ if (this.#shell == Shell.Nushell) {
89
+ const nushellPrompt = lineText.match(/(?<prompt>.*>\s?)/)?.groups?.prompt;
90
+ if (nushellPrompt) {
91
+ const adjustedPrompt = this._adjustPrompt(nushellPrompt, lineText, ">");
92
+ if (adjustedPrompt) {
93
+ return adjustedPrompt;
94
+ }
95
+ }
96
+ }
97
+ if (this.#shell == Shell.Xonsh) {
98
+ let xonshPrompt = lineText.match(/(?<prompt>.*@\s?)/)?.groups?.prompt;
99
+ if (xonshPrompt) {
100
+ const adjustedPrompt = this._adjustPrompt(xonshPrompt, lineText, "@");
101
+ if (adjustedPrompt) {
102
+ return adjustedPrompt;
103
+ }
104
+ }
105
+ xonshPrompt = lineText.match(/(?<prompt>.*>\s?)/)?.groups?.prompt;
106
+ if (xonshPrompt) {
107
+ const adjustedPrompt = this._adjustPrompt(xonshPrompt, lineText, ">");
108
+ if (adjustedPrompt) {
109
+ return adjustedPrompt;
110
+ }
111
+ }
112
+ }
113
+ if (this.#shell == Shell.Powershell || this.#shell == Shell.Pwsh) {
114
+ const pwshPrompt = lineText.match(/(?<prompt>(\(.+\)\s)?(?:PS.+>\s?))/)?.groups?.prompt;
115
+ if (pwshPrompt) {
116
+ const adjustedPrompt = this._adjustPrompt(pwshPrompt, lineText, ">");
117
+ if (adjustedPrompt) {
118
+ return adjustedPrompt;
119
+ }
120
+ }
121
+ }
122
+ if (this.#shell == Shell.Cmd) {
123
+ return lineText.match(/^(?<prompt>(\(.+\)\s)?(?:[A-Z]:\\.*>)|(> ))/)?.groups?.prompt;
124
+ }
125
+ // Custom prompts like starship end in the common \u276f character
126
+ const customPrompt = lineText.match(/.*\u276f(?=[^\u276f]*$)/g)?.[0];
127
+ if (customPrompt) {
128
+ const adjustedPrompt = this._adjustPrompt(customPrompt, lineText, "\u276f");
129
+ if (adjustedPrompt) {
130
+ return adjustedPrompt;
131
+ }
132
+ }
133
+ }
134
+ _adjustPrompt(prompt, lineText, char) {
135
+ if (!prompt) {
136
+ return;
137
+ }
138
+ // Conpty may not 'render' the space at the end of the prompt
139
+ if (lineText === prompt && prompt.endsWith(char)) {
140
+ prompt += " ";
141
+ }
142
+ return prompt;
143
+ }
144
+ _getFgPaletteColor(cell) {
145
+ if (cell?.isFgDefault())
146
+ return 0;
147
+ if (cell?.isFgPalette())
148
+ return cell.getFgColor();
149
+ if (cell?.isFgRGB())
150
+ return convert.hex.ansi256(cell.getFgColor().toString(16));
151
+ }
152
+ _isSuggestion(cell) {
153
+ const color = this._getFgPaletteColor(cell);
154
+ const dim = (cell?.isDim() ?? 0) > 0;
155
+ const italic = (cell?.isItalic() ?? 0) > 0;
156
+ const dullColor = color == 8 || color == 7 || (color ?? 0) > 235 || (color == 15 && dim);
157
+ const dimItalic = dim || italic;
158
+ if (this.#shell == Shell.Pwsh || this.#shell == Shell.Powershell) {
159
+ return dimItalic;
160
+ }
161
+ return dullColor;
162
+ }
163
+ getState() {
164
+ return {
165
+ promptText: this.#activeCommand.promptText,
166
+ commandText: this.#activeCommand.commandText,
167
+ suggestionsText: this.#activeCommand.suggestionsText,
168
+ hasOutput: this.#activeCommand.hasOutput,
169
+ cursorTerminated: this.#activeCommand.cursorTerminated,
170
+ };
171
+ }
172
+ clearActiveCommand() {
173
+ this.#activeCommand = {};
174
+ }
175
+ _getCommandLines() {
176
+ const lines = [];
177
+ let lineY = this.#activeCommand.promptEndMarker.line;
178
+ let line = this.#terminal.buffer.active.getLine(this.#activeCommand.promptEndMarker.line);
179
+ const absoluteY = this.#terminal.buffer.active.baseY + this.#terminal.buffer.active.cursorY;
180
+ for (; lineY < this.#terminal.buffer.active.baseY + this.#terminal.rows;) {
181
+ if (line)
182
+ lines.push(line);
183
+ lineY += 1;
184
+ line = this.#terminal.buffer.active.getLine(lineY);
185
+ const lineWrapped = line?.isWrapped;
186
+ const cursorWrapped = absoluteY > lineY - 1;
187
+ const wrapped = lineWrapped || cursorWrapped;
188
+ if (!wrapped)
189
+ break;
190
+ }
191
+ return lines;
192
+ }
193
+ _getCommandText(commandLines) {
194
+ const absoluteY = this.#terminal.buffer.active.baseY + this.#terminal.buffer.active.cursorY;
195
+ const cursorLine = Math.max(absoluteY - this.#activeCommand.promptEndMarker.line, 0);
196
+ let preCursorCommand = "";
197
+ let postCursorCommand = "";
198
+ let suggestion = "";
199
+ for (const [y, line] of commandLines.entries()) {
200
+ const startX = y == 0 ? this.#activeCommand.promptText?.length ?? 0 : 0;
201
+ for (let x = startX; x < this.#terminal.cols; x++) {
202
+ if (postCursorCommand.endsWith(" "))
203
+ break; // assume that a command that ends with 4 spaces is terminated, avoids capturing right prompts
204
+ const cell = line.getCell(x);
205
+ if (cell == null)
206
+ continue;
207
+ const chars = cell.getChars() == "" ? " " : cell.getChars();
208
+ const beforeCursor = y < cursorLine || (y == cursorLine && x < this.#terminal.buffer.active.cursorX);
209
+ const isCommand = !this._isSuggestion(cell) && suggestion.length == 0;
210
+ if (isCommand && beforeCursor) {
211
+ preCursorCommand += chars;
212
+ }
213
+ else if (isCommand) {
214
+ postCursorCommand += chars;
215
+ }
216
+ else {
217
+ suggestion += chars;
218
+ }
219
+ }
220
+ }
221
+ log.debug({ msg: "command text", preCursorCommand, postCursorCommand, suggestion });
222
+ return { suggestion, preCursorCommand, postCursorCommand };
223
+ }
224
+ _getCommandOutputStatus(commandLines) {
225
+ const outputLineY = this.#activeCommand.promptEndMarker.line + commandLines;
226
+ const maxLineY = this.#terminal.buffer.active.baseY + this.#terminal.rows;
227
+ if (outputLineY >= maxLineY)
228
+ return false;
229
+ const line = this.#terminal.buffer.active.getLine(outputLineY);
230
+ let cell = undefined;
231
+ for (let i = 0; i < this.#terminal.cols; i++) {
232
+ cell = line?.getCell(i, cell);
233
+ if (cell?.getChars() != "") {
234
+ return true;
235
+ }
236
+ }
237
+ return false;
238
+ }
239
+ termSync() {
240
+ if (this.#activeCommand.promptEndMarker == null || this.#activeCommand.promptStartMarker == null) {
241
+ return;
242
+ }
243
+ const globalCursorPosition = this.#terminal.buffer.active.baseY + this.#terminal.buffer.active.cursorY;
244
+ const withinPollDistance = globalCursorPosition < this.#activeCommand.promptEndMarker.line + 5;
245
+ this.#maxCursorY = Math.max(this.#maxCursorY, globalCursorPosition);
246
+ if (globalCursorPosition < this.#activeCommand.promptStartMarker.line || globalCursorPosition < this.#maxCursorY) {
247
+ this.handleClear();
248
+ this.#activeCommand.promptEndMarker = this.#terminal.registerMarker(0);
249
+ return;
250
+ }
251
+ if (this.#activeCommand.promptEndMarker == null)
252
+ return;
253
+ // if we haven't fond the prompt yet, poll over the next 5 lines searching for it
254
+ if (this.#activeCommand.promptText == null && withinPollDistance) {
255
+ for (let i = globalCursorPosition; i < this.#activeCommand.promptEndMarker.line + maxPromptPollDistance; i++) {
256
+ if (this.#previousCommandLines.has(i) && !this.#promptRewrites)
257
+ continue;
258
+ const promptResult = this._getWindowsPrompt(i);
259
+ if (promptResult != null) {
260
+ this.#activeCommand.promptEndMarker = this.#terminal.registerMarker(i - globalCursorPosition);
261
+ this.#activeCommand.promptEndX = promptResult.length;
262
+ this.#activeCommand.promptText = promptResult;
263
+ this.#previousCommandLines.add(i);
264
+ break;
265
+ }
266
+ }
267
+ }
268
+ // if the prompt is set, now parse out the values from the terminal
269
+ if (this.#activeCommand.promptText != null) {
270
+ const commandLines = this._getCommandLines();
271
+ const { suggestion, preCursorCommand, postCursorCommand } = this._getCommandText(commandLines);
272
+ const command = preCursorCommand + postCursorCommand.trim();
273
+ const cursorAtEndOfInput = postCursorCommand.trim() == "";
274
+ const hasOutput = this._getCommandOutputStatus(commandLines.length);
275
+ this.#activeCommand.persistentOutput = this.#activeCommand.hasOutput && hasOutput;
276
+ this.#activeCommand.hasOutput = hasOutput;
277
+ this.#activeCommand.suggestionsText = suggestion;
278
+ this.#activeCommand.commandText = command;
279
+ this.#activeCommand.cursorTerminated = cursorAtEndOfInput;
280
+ }
281
+ log.debug({
282
+ msg: "cmd manager state",
283
+ ...this.#activeCommand,
284
+ promptEndMarker: this.#activeCommand.promptEndMarker?.line,
285
+ promptStartMarker: this.#activeCommand.promptStartMarker?.line,
286
+ cursorX: this.#terminal.buffer.active.cursorX,
287
+ cursorY: globalCursorPosition,
288
+ });
289
+ }
290
+ }
@@ -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,372 @@
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
+ });
45
+ this.pid = this.#pty.pid;
46
+ this.cols = this.#pty.cols;
47
+ this.rows = this.#pty.rows;
48
+ this.process = this.#pty.process;
49
+ const unicode11Addon = new Unicode11Addon();
50
+ this.#term = new xterm.Terminal({ allowProposedApi: true, rows, cols });
51
+ this.#term.loadAddon(unicode11Addon);
52
+ this.#term.unicode.activeVersion = "11";
53
+ this.#term.parser.registerOscHandler(IsTermOscPs, (data) => this._handleIsSequence(data));
54
+ this.#commandManager = new CommandManager(this.#term, shell);
55
+ this.#shell = shell;
56
+ this.#ptyEmitter = new EventEmitter();
57
+ this.#pty.onData((data) => {
58
+ this.#term.write(data, () => {
59
+ log.debug({ msg: "parsing data", data, bytes: Uint8Array.from([...data].map((c) => c.charCodeAt(0))) });
60
+ this.#commandManager.termSync();
61
+ this.#ptyEmitter.emit(ISTermOnDataEvent, data);
62
+ });
63
+ });
64
+ this.onData = (listener) => {
65
+ this.#ptyEmitter.on(ISTermOnDataEvent, listener);
66
+ return {
67
+ dispose: () => this.#ptyEmitter.removeListener(ISTermOnDataEvent, listener),
68
+ };
69
+ };
70
+ this.onExit = this.#pty.onExit;
71
+ }
72
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
73
+ on(_event, _listener) {
74
+ throw new Error("Method not implemented as deprecated in node-pty.");
75
+ }
76
+ _deserializeIsMessage(message) {
77
+ return message.replaceAll(/\\(\\|x([0-9a-f]{2}))/gi, (_match, op, hex) => (hex ? String.fromCharCode(parseInt(hex, 16)) : op));
78
+ }
79
+ _sanitizedCwd(cwd) {
80
+ if (cwd.match(/^['"].*['"]$/)) {
81
+ cwd = cwd.substring(1, cwd.length - 1);
82
+ }
83
+ // Convert a drive prefix to windows style when using Git Bash
84
+ if (os.platform() === "win32" && this.#shell == Shell.Bash && cwd && cwd.match(/^\/[A-z]{1}\//)) {
85
+ cwd = `${cwd[1]}:\\` + cwd.substring(3, cwd.length);
86
+ }
87
+ // Make the drive letter uppercase on Windows (see vscode #9448)
88
+ if (os.platform() === "win32" && cwd && cwd[1] === ":") {
89
+ return cwd[0].toUpperCase() + cwd.substring(1);
90
+ }
91
+ return cwd;
92
+ }
93
+ _sanitizedPrompt(prompt) {
94
+ // eslint-disable-next-line no-control-regex -- strip OSC control sequences
95
+ const oscStrippedPrompt = prompt.replace(/\x1b\][0-9]+;.*\x07/g, "");
96
+ return stripAnsi(oscStrippedPrompt);
97
+ }
98
+ _handleIsSequence(data) {
99
+ const argsIndex = data.indexOf(";");
100
+ const sequence = argsIndex === -1 ? data : data.substring(0, argsIndex);
101
+ switch (sequence) {
102
+ case IstermOscPt.PromptStarted:
103
+ this.#commandManager.handlePromptStart();
104
+ break;
105
+ case IstermOscPt.PromptEnded:
106
+ this.#commandManager.handlePromptEnd();
107
+ break;
108
+ case IstermOscPt.CurrentWorkingDirectory: {
109
+ const cwd = data.split(";").at(1);
110
+ if (cwd != null) {
111
+ this.cwd = path.resolve(this._sanitizedCwd(this._deserializeIsMessage(cwd)));
112
+ }
113
+ break;
114
+ }
115
+ case IstermOscPt.Prompt: {
116
+ const prompt = data.split(";").slice(1).join(";");
117
+ if (prompt != null) {
118
+ const sanitizedPrompt = this._sanitizedPrompt(this._deserializeIsMessage(prompt));
119
+ const lastPromptLine = sanitizedPrompt.substring(sanitizedPrompt.lastIndexOf("\n")).trim();
120
+ const promptTerminator = lastPromptLine.substring(lastPromptLine.lastIndexOf(" ")).trim();
121
+ if (promptTerminator) {
122
+ this.#commandManager.promptTerminator = promptTerminator;
123
+ log.debug({ msg: "prompt terminator", promptTerminator });
124
+ }
125
+ }
126
+ break;
127
+ }
128
+ default:
129
+ return false;
130
+ }
131
+ return true;
132
+ }
133
+ noop() {
134
+ this.#ptyEmitter.emit(ISTermOnDataEvent, "");
135
+ }
136
+ resize(columns, rows) {
137
+ this.cols = columns;
138
+ this.rows = rows;
139
+ this.#pty.resize(columns, rows);
140
+ this.#term.resize(columns, rows);
141
+ }
142
+ clear() {
143
+ this.#term.reset();
144
+ this.#pty.clear();
145
+ }
146
+ kill(signal) {
147
+ this.#pty.kill(signal);
148
+ }
149
+ pause() {
150
+ this.#pty.pause();
151
+ }
152
+ resume() {
153
+ this.#pty.resume();
154
+ }
155
+ write(data) {
156
+ log.debug({ msg: "reading data", data, bytes: Uint8Array.from([...data].map((c) => c.charCodeAt(0))) });
157
+ this.#pty.write(data);
158
+ }
159
+ getCommandState() {
160
+ return this.#commandManager.getState();
161
+ }
162
+ getCursorState() {
163
+ return {
164
+ onLastLine: this.#term.buffer.active.cursorY >= this.#term.rows - 2,
165
+ remainingLines: Math.max(this.#term.rows - 2 - this.#term.buffer.active.cursorY, 0),
166
+ cursorX: this.#term.buffer.active.cursorX,
167
+ cursorY: this.#term.buffer.active.cursorY,
168
+ };
169
+ }
170
+ _sameAccent(baseCell, targetCell) {
171
+ return (baseCell?.isBold() == targetCell?.isBold() &&
172
+ baseCell?.isItalic() == targetCell?.isItalic() &&
173
+ baseCell?.isUnderline() == targetCell?.isUnderline() &&
174
+ baseCell?.extended.underlineStyle == targetCell?.extended.underlineStyle &&
175
+ baseCell?.hasExtendedAttrs() == targetCell?.hasExtendedAttrs() &&
176
+ baseCell?.isInverse() == targetCell?.isInverse() &&
177
+ baseCell?.isBlink() == targetCell?.isBlink() &&
178
+ baseCell?.isInvisible() == targetCell?.isInvisible() &&
179
+ baseCell?.isDim() == targetCell?.isDim() &&
180
+ baseCell?.isStrikethrough() == targetCell?.isStrikethrough());
181
+ }
182
+ _getAnsiAccents(cell) {
183
+ if (cell == null)
184
+ return "";
185
+ let underlineAnsi = "";
186
+ if (cell.isUnderline()) {
187
+ if (cell.hasExtendedAttrs() && cell.extended.underlineStyle) {
188
+ underlineAnsi = `\x1b[4:${cell.extended.underlineStyle}m`;
189
+ }
190
+ else {
191
+ underlineAnsi = "\x1b[4m";
192
+ }
193
+ }
194
+ const boldAnsi = cell.isBold() ? "\x1b[1m" : "";
195
+ const dimAnsi = cell.isDim() ? "\x1b[2m" : "";
196
+ const italicAnsi = cell.isItalic() ? "\x1b[3m" : "";
197
+ const blinkAnsi = cell.isBlink() ? "\x1b[5m" : "";
198
+ const inverseAnsi = cell.isInverse() ? "\x1b[7m" : "";
199
+ const invisibleAnsi = cell.isInvisible() ? "\x1b[8m" : "";
200
+ const strikethroughAnsi = cell.isStrikethrough() ? "\x1b[9m" : "";
201
+ return boldAnsi + italicAnsi + underlineAnsi + inverseAnsi + dimAnsi + blinkAnsi + invisibleAnsi + strikethroughAnsi;
202
+ }
203
+ _sameColor(baseCell, targetCell) {
204
+ return (baseCell?.getBgColorMode() == targetCell?.getBgColorMode() &&
205
+ baseCell?.getBgColor() == targetCell?.getBgColor() &&
206
+ baseCell?.getFgColorMode() == targetCell?.getFgColorMode() &&
207
+ baseCell?.getFgColor() == targetCell?.getFgColor());
208
+ }
209
+ _getAnsiColors(cell) {
210
+ if (cell == null)
211
+ return "";
212
+ let bgAnsi = "";
213
+ cell.getBgColor;
214
+ cell.getFgColor;
215
+ if (cell.isBgDefault()) {
216
+ bgAnsi = "\x1b[49m";
217
+ }
218
+ else if (cell.isBgPalette()) {
219
+ bgAnsi = `\x1b[48;5;${cell.getBgColor()}m`;
220
+ }
221
+ else {
222
+ bgAnsi = `\x1b[48;5;${styles.hexToAnsi256(cell.getBgColor().toString(16))}m`;
223
+ }
224
+ let fgAnsi = "";
225
+ if (cell.isFgDefault()) {
226
+ fgAnsi = "\x1b[39m";
227
+ }
228
+ else if (cell.isFgPalette()) {
229
+ fgAnsi = `\x1b[38;5;${cell.getFgColor()}m`;
230
+ }
231
+ else {
232
+ fgAnsi = `\x1b[38;5;${styles.hexToAnsi256(cell.getFgColor().toString(16))}m`;
233
+ }
234
+ return bgAnsi + fgAnsi;
235
+ }
236
+ clearCommand() {
237
+ this.#commandManager.clearActiveCommand();
238
+ }
239
+ getCells(height, direction) {
240
+ const currentCursorPosition = this.#term.buffer.active.cursorY + this.#term.buffer.active.baseY;
241
+ const writeLine = (y) => {
242
+ const line = this.#term.buffer.active.getLine(y);
243
+ const ansiLine = [ansi.resetColor, ansi.resetLine];
244
+ if (line == null)
245
+ return "";
246
+ let prevCell;
247
+ for (let x = 0; x < line.length; x++) {
248
+ const cell = line.getCell(x);
249
+ const chars = cell?.getChars() ?? "";
250
+ const sameColor = this._sameColor(prevCell, cell);
251
+ const sameAccents = this._sameAccent(prevCell, cell);
252
+ if (!sameColor || !sameAccents) {
253
+ ansiLine.push(ansi.resetColor);
254
+ }
255
+ if (!sameColor) {
256
+ ansiLine.push(this._getAnsiColors(cell));
257
+ }
258
+ if (!sameAccents) {
259
+ ansiLine.push(this._getAnsiAccents(cell));
260
+ }
261
+ const isWide = prevCell?.getWidth() == 2 && cell?.getWidth() == 0;
262
+ const cursorForward = isWide ? "" : ansi.cursorForward();
263
+ ansiLine.push(chars == "" ? cursorForward : chars);
264
+ prevCell = cell;
265
+ }
266
+ return ansiLine.join("");
267
+ };
268
+ const lines = [];
269
+ if (direction == "above") {
270
+ const startCursorPosition = currentCursorPosition - 1;
271
+ const endCursorPosition = currentCursorPosition - 1 - height;
272
+ for (let y = startCursorPosition; y > endCursorPosition; y--) {
273
+ lines.push(writeLine(y));
274
+ }
275
+ }
276
+ else {
277
+ const startCursorPosition = currentCursorPosition + 1;
278
+ const endCursorPosition = currentCursorPosition + 1 + height;
279
+ for (let y = startCursorPosition; y < endCursorPosition; y++) {
280
+ lines.push(writeLine(y));
281
+ }
282
+ }
283
+ return lines.reverse().join(ansi.cursorNextLine);
284
+ }
285
+ }
286
+ export const spawn = async (program, options) => {
287
+ const { shellTarget, shellArgs } = await convertToPtyTarget(options.shell, options.underTest, options.login);
288
+ if (!(await shellExists(shellTarget))) {
289
+ program.error(`shell not found on PATH: ${shellTarget}`, { exitCode: 1 });
290
+ }
291
+ return new ISTerm({ ...options, shellTarget, shellArgs });
292
+ };
293
+ const shellExists = async (shellTarget) => {
294
+ const fileExists = fs.existsSync(shellTarget);
295
+ const fileOnPath = await which(shellTarget, { nothrow: true });
296
+ return fileExists || fileOnPath != null;
297
+ };
298
+ const convertToPtyTarget = async (shell, underTest, login) => {
299
+ const platform = os.platform();
300
+ const shellTarget = shell == Shell.Bash && platform == "win32" ? await gitBashPath() : platform == "win32" ? `${shell}.exe` : shell;
301
+ const shellFolderPath = path.join(path.dirname(url.fileURLToPath(import.meta.url)), "..", "..", "shell");
302
+ let shellArgs = [];
303
+ switch (shell) {
304
+ case Shell.Bash:
305
+ shellArgs = ["--init-file", path.join(shellFolderPath, "shellIntegration.bash")];
306
+ break;
307
+ case Shell.Powershell:
308
+ case Shell.Pwsh:
309
+ shellArgs = ["-noexit", "-command", `try { . "${path.join(shellFolderPath, "shellIntegration.ps1")}" } catch {}`];
310
+ break;
311
+ case Shell.Fish:
312
+ shellArgs =
313
+ platform == "win32"
314
+ ? ["--init-command", `. "$(cygpath -u '${path.join(shellFolderPath, "shellIntegration.fish")}')"`]
315
+ : ["--init-command", `. ${path.join(shellFolderPath, "shellIntegration.fish").replace(/(\s+)/g, "\\$1")}`];
316
+ break;
317
+ case Shell.Xonsh: {
318
+ const sharedConfig = os.platform() == "win32" ? path.join("C:\\ProgramData", "xonsh", "xonshrc") : path.join("etc", "xonsh", "xonshrc");
319
+ const userConfigs = [
320
+ path.join(os.homedir(), ".xonshrc"),
321
+ path.join(os.homedir(), ".config", "xonsh", "rc.xsh"),
322
+ path.join(os.homedir(), ".config", "xonsh", "rc.d"),
323
+ ];
324
+ const configs = [sharedConfig, ...userConfigs].filter((config) => fs.existsSync(config));
325
+ shellArgs = ["--rc", ...configs, path.join(shellFolderPath, "shellIntegration.xsh")];
326
+ break;
327
+ }
328
+ case Shell.Nushell:
329
+ shellArgs = ["-e", `source \`${path.join(shellFolderPath, "shellIntegration.nu")}\``];
330
+ if (underTest)
331
+ shellArgs.push("-n");
332
+ break;
333
+ }
334
+ if (login) {
335
+ switch (shell) {
336
+ case Shell.Powershell:
337
+ case Shell.Pwsh:
338
+ shellArgs.unshift("-login");
339
+ break;
340
+ case Shell.Zsh:
341
+ case Shell.Fish:
342
+ case Shell.Xonsh:
343
+ case Shell.Nushell:
344
+ shellArgs.unshift("--login");
345
+ break;
346
+ }
347
+ }
348
+ return { shellTarget, shellArgs };
349
+ };
350
+ const convertToPtyEnv = (shell, underTest, login) => {
351
+ const env = {
352
+ ...process.env,
353
+ ISTERM: "1",
354
+ };
355
+ if (underTest)
356
+ env.ISTERM_TESTING = "1";
357
+ if (login)
358
+ env.ISTERM_LOGIN = "1";
359
+ switch (shell) {
360
+ case Shell.Cmd: {
361
+ if (underTest) {
362
+ return { ...env, PROMPT: `${IstermPromptStart}$G ${IstermPromptEnd}` };
363
+ }
364
+ const prompt = process.env.PROMPT ? process.env.PROMPT : "$P$G";
365
+ return { ...env, PROMPT: `${IstermPromptStart}${prompt}${IstermPromptEnd}` };
366
+ }
367
+ case Shell.Zsh: {
368
+ return { ...env, ZDOTDIR: zdotdir, USER_ZDOTDIR: userZdotdir };
369
+ }
370
+ }
371
+ return env;
372
+ };