@microsoft/inshellisense 0.0.1-rc.3 → 0.0.1-rc.5
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 +4 -8
- package/build/commands/root.js +12 -31
- package/build/commands/uninstall.js +1 -1
- package/build/index.js +2 -6
- package/build/isterm/commandManager.js +212 -0
- package/build/isterm/index.js +4 -0
- package/build/isterm/pty.js +217 -0
- package/build/runtime/template.js +1 -1
- package/build/ui/input.js +7 -53
- package/build/ui/suggestionManager.js +119 -0
- package/build/ui/ui-root.js +109 -64
- package/build/ui/ui-uninstall.js +1 -3
- package/build/ui/utils.js +41 -0
- package/build/utils/ansi.js +55 -0
- package/build/utils/config.js +68 -0
- package/build/utils/log.js +22 -0
- package/build/utils/shell.js +77 -0
- package/package.json +10 -4
- package/shell/shellIntegration-env.zsh +9 -0
- package/shell/shellIntegration-login.zsh +4 -0
- package/shell/shellIntegration-profile.zsh +4 -0
- package/shell/shellIntegration-rc.zsh +19 -0
- package/shell/shellIntegration.bash +31 -0
- package/shell/shellIntegration.fish +6 -0
- package/shell/shellIntegration.ps1 +8 -0
- package/build/commands/bind.js +0 -12
- package/build/ui/suggestions.js +0 -84
- package/build/ui/ui-bind.js +0 -69
- package/build/utils/bindings.js +0 -236
- 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
package/README.md
CHANGED
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
# inshellisense
|
|
2
2
|
|
|
3
|
-
`inshellisense` provides IDE style autocomplete for shells. It's a terminal native runtime for [autocomplete](https://github.com/withfig/autocomplete) which has support for 600+ command line tools. `inshellisense` supports Windows, Linux, &
|
|
3
|
+
`inshellisense` provides IDE style autocomplete for shells. It's a terminal native runtime for [autocomplete](https://github.com/withfig/autocomplete) which has support for 600+ command line tools. `inshellisense` supports Windows, Linux, & macOS.
|
|
4
4
|
|
|
5
5
|
<p align="center"><img alt="demo of inshellisense working" src="/docs/demo.gif" height="450px"/></p>
|
|
6
6
|
|
|
7
7
|
## Getting Started
|
|
8
8
|
|
|
9
9
|
### Requirements
|
|
10
|
+
|
|
10
11
|
- `node >= 16.x`
|
|
12
|
+
- node-gyp dependencies installed for your platform (see [node-gyp](https://github.com/nodejs/node-gyp) for more details)
|
|
11
13
|
|
|
12
14
|
### Installation
|
|
13
15
|
|
|
@@ -17,13 +19,7 @@ npm install -g @microsoft/inshellisense
|
|
|
17
19
|
|
|
18
20
|
### Quickstart
|
|
19
21
|
|
|
20
|
-
After completing the installation, you can
|
|
21
|
-
|
|
22
|
-
```shell
|
|
23
|
-
inshellisense bind
|
|
24
|
-
```
|
|
25
|
-
|
|
26
|
-
Additionally, inshellisense is also aliased under `is` after install for convenience.
|
|
22
|
+
After completing the installation, you can run `is` to start the autocomplete session for your desired shell. Additionally, inshellisense is also aliased under `inshellisense` after installation.
|
|
27
23
|
|
|
28
24
|
## Integrations
|
|
29
25
|
|
package/build/commands/root.js
CHANGED
|
@@ -1,40 +1,21 @@
|
|
|
1
1
|
// Copyright (c) Microsoft Corporation.
|
|
2
2
|
// Licensed under the MIT License.
|
|
3
3
|
import { render } from "../ui/ui-root.js";
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
4
|
+
import { Shell, supportedShells as shells, setupZshDotfiles } from "../utils/shell.js";
|
|
5
|
+
import { inferShell } from "../utils/shell.js";
|
|
6
|
+
import { loadConfig } from "../utils/config.js";
|
|
7
7
|
export const supportedShells = shells.join(", ");
|
|
8
|
-
export const action = async (options) => {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
8
|
+
export const action = (program) => async (options) => {
|
|
9
|
+
await loadConfig(program);
|
|
10
|
+
const shell = options.shell ?? (await inferShell());
|
|
11
|
+
if (shell == null) {
|
|
12
|
+
program.error(`Unable to identify shell, use the -s/--shell option to provide your shell`, { exitCode: 1 });
|
|
12
13
|
}
|
|
13
|
-
const shell = options.shell ?? "";
|
|
14
14
|
if (!shells.map((s) => s.valueOf()).includes(shell)) {
|
|
15
|
-
|
|
16
|
-
process.exit(1);
|
|
15
|
+
program.error(`Unsupported shell: '${shell}', supported shells: ${supportedShells}`, { exitCode: 1 });
|
|
17
16
|
}
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
let result = { code: 0 };
|
|
21
|
-
let startingCommand = options.command;
|
|
22
|
-
while (options.duration === "session" || !executed) {
|
|
23
|
-
const commandToExecute = await render(startingCommand);
|
|
24
|
-
if (commandToExecute == null || commandToExecute.trim().toLowerCase() == "exit" || commandToExecute.trim().toLowerCase() == "logout") {
|
|
25
|
-
result = { code: 0 };
|
|
26
|
-
break;
|
|
27
|
-
}
|
|
28
|
-
commands.push(commandToExecute);
|
|
29
|
-
result = await executeShellCommandTTY(shell, commandToExecute);
|
|
30
|
-
executed = true;
|
|
31
|
-
startingCommand = undefined;
|
|
32
|
-
}
|
|
33
|
-
await saveCommand(commands);
|
|
34
|
-
if (result.code) {
|
|
35
|
-
process.exit(result.code);
|
|
36
|
-
}
|
|
37
|
-
else {
|
|
38
|
-
process.exit(0);
|
|
17
|
+
if (shell == Shell.Zsh) {
|
|
18
|
+
await setupZshDotfiles();
|
|
39
19
|
}
|
|
20
|
+
await render(shell);
|
|
40
21
|
};
|
|
@@ -6,6 +6,6 @@ const action = async () => {
|
|
|
6
6
|
await render();
|
|
7
7
|
};
|
|
8
8
|
const cmd = new Command("uninstall");
|
|
9
|
-
cmd.description(`removes all
|
|
9
|
+
cmd.description(`removes all configuration for inshellisense`);
|
|
10
10
|
cmd.action(action);
|
|
11
11
|
export default cmd;
|
package/build/index.js
CHANGED
|
@@ -3,7 +3,6 @@
|
|
|
3
3
|
// Licensed under the MIT License.
|
|
4
4
|
/* eslint-disable header/header */
|
|
5
5
|
import { Command } from "commander";
|
|
6
|
-
import bind from "./commands/bind.js";
|
|
7
6
|
import uninstall from "./commands/uninstall.js";
|
|
8
7
|
import { action, supportedShells } from "./commands/root.js";
|
|
9
8
|
import { getVersion } from "./utils/version.js";
|
|
@@ -12,11 +11,8 @@ program
|
|
|
12
11
|
.name("inshellisense")
|
|
13
12
|
.description("IDE style command line auto complete")
|
|
14
13
|
.version(await getVersion(), "-v, --version", "output the current version")
|
|
14
|
+
.action(action(program))
|
|
15
15
|
.option("-s, --shell <shell>", `shell to use for command execution, supported shells: ${supportedShells}`)
|
|
16
|
-
.
|
|
17
|
-
.option("--history", "get the last command execute")
|
|
18
|
-
.option("-d, --duration <duration>", "duration of the autocomplete session, supported durations: single, session", "session")
|
|
19
|
-
.action(action);
|
|
20
|
-
program.addCommand(bind);
|
|
16
|
+
.showHelpAfterError("(add --help for additional information)");
|
|
21
17
|
program.addCommand(uninstall);
|
|
22
18
|
program.parse();
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
// Copyright (c) Microsoft Corporation.
|
|
2
|
+
// Licensed under the MIT License.
|
|
3
|
+
import os from "node:os";
|
|
4
|
+
import { Shell } from "../utils/shell.js";
|
|
5
|
+
import log from "../utils/log.js";
|
|
6
|
+
import { getConfig } from "../utils/config.js";
|
|
7
|
+
const maxPromptPollDistance = 10;
|
|
8
|
+
export class CommandManager {
|
|
9
|
+
#activeCommand;
|
|
10
|
+
#previousCommandLines;
|
|
11
|
+
#terminal;
|
|
12
|
+
#shell;
|
|
13
|
+
#supportsProperOscPlacements = os.platform() !== "win32";
|
|
14
|
+
constructor(terminal, shell) {
|
|
15
|
+
this.#terminal = terminal;
|
|
16
|
+
this.#shell = shell;
|
|
17
|
+
this.#activeCommand = {};
|
|
18
|
+
this.#previousCommandLines = new Set();
|
|
19
|
+
if (this.#supportsProperOscPlacements) {
|
|
20
|
+
this.#terminal.parser.registerCsiHandler({ final: "J" }, (params) => {
|
|
21
|
+
if (params.at(0) == 3 || params.at(0) == 2) {
|
|
22
|
+
this.handleClear();
|
|
23
|
+
}
|
|
24
|
+
return false;
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
handlePromptStart() {
|
|
29
|
+
this.#activeCommand = { promptStartMarker: this.#terminal.registerMarker(0), hasOutput: false, cursorTerminated: false };
|
|
30
|
+
}
|
|
31
|
+
handlePromptEnd() {
|
|
32
|
+
this.#activeCommand.promptEndMarker = this.#terminal.registerMarker(0);
|
|
33
|
+
if (this.#activeCommand.promptEndMarker?.line === this.#terminal.buffer.active.cursorY) {
|
|
34
|
+
this.#activeCommand.promptEndX = this.#terminal.buffer.active.cursorX;
|
|
35
|
+
}
|
|
36
|
+
if (this.#supportsProperOscPlacements) {
|
|
37
|
+
this.#activeCommand.promptText = this.#terminal.buffer.active.getLine(this.#activeCommand.promptEndMarker?.line ?? 0)?.translateToString(true);
|
|
38
|
+
this.#previousCommandLines.add(this.#activeCommand.promptEndMarker?.line ?? -1);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
handleClear() {
|
|
42
|
+
this.handlePromptStart();
|
|
43
|
+
this.#previousCommandLines = new Set();
|
|
44
|
+
}
|
|
45
|
+
_getWindowsPrompt(y) {
|
|
46
|
+
const line = this.#terminal.buffer.active.getLine(y);
|
|
47
|
+
if (!line) {
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
const lineText = line.translateToString(true);
|
|
51
|
+
if (!lineText) {
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
// User defined prompt
|
|
55
|
+
const inshellisenseConfig = getConfig();
|
|
56
|
+
if (this.#shell == Shell.Bash) {
|
|
57
|
+
if (inshellisenseConfig.promptRegex?.bash != null) {
|
|
58
|
+
const customBashPrompt = lineText.match(new RegExp(inshellisenseConfig.promptRegex?.bash.regex))?.groups?.prompt;
|
|
59
|
+
const adjustedPrompt = this._adjustPrompt(customBashPrompt, lineText, inshellisenseConfig.promptRegex?.bash.postfix);
|
|
60
|
+
if (adjustedPrompt) {
|
|
61
|
+
return adjustedPrompt;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
const bashPrompt = lineText.match(/^(?<prompt>.*\$\s?)/)?.groups?.prompt;
|
|
65
|
+
if (bashPrompt) {
|
|
66
|
+
const adjustedPrompt = this._adjustPrompt(bashPrompt, lineText, "$");
|
|
67
|
+
if (adjustedPrompt) {
|
|
68
|
+
return adjustedPrompt;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
if (this.#shell == Shell.Powershell || this.#shell == Shell.Pwsh) {
|
|
73
|
+
if (inshellisenseConfig.promptRegex?.pwsh != null && this.#shell == Shell.Pwsh) {
|
|
74
|
+
const customPwshPrompt = lineText.match(new RegExp(inshellisenseConfig.promptRegex?.pwsh.regex))?.groups?.prompt;
|
|
75
|
+
const adjustedPrompt = this._adjustPrompt(customPwshPrompt, lineText, inshellisenseConfig.promptRegex?.pwsh.postfix);
|
|
76
|
+
if (adjustedPrompt) {
|
|
77
|
+
return adjustedPrompt;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
if (inshellisenseConfig.promptRegex?.powershell != null && this.#shell == Shell.Powershell) {
|
|
81
|
+
const customPowershellPrompt = lineText.match(new RegExp(inshellisenseConfig.promptRegex?.powershell.regex))?.groups?.prompt;
|
|
82
|
+
const adjustedPrompt = this._adjustPrompt(customPowershellPrompt, lineText, inshellisenseConfig.promptRegex?.powershell.postfix);
|
|
83
|
+
if (adjustedPrompt) {
|
|
84
|
+
return adjustedPrompt;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
const pwshPrompt = lineText.match(/(?<prompt>(\(.+\)\s)?(?:PS.+>\s?))/)?.groups?.prompt;
|
|
88
|
+
if (pwshPrompt) {
|
|
89
|
+
const adjustedPrompt = this._adjustPrompt(pwshPrompt, lineText, ">");
|
|
90
|
+
if (adjustedPrompt) {
|
|
91
|
+
return adjustedPrompt;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
if (this.#shell == Shell.Cmd) {
|
|
96
|
+
return lineText.match(/^(?<prompt>(\(.+\)\s)?(?:[A-Z]:\\.*>))/)?.groups?.prompt;
|
|
97
|
+
}
|
|
98
|
+
// Custom prompts like starship end in the common \u276f character
|
|
99
|
+
const customPrompt = lineText.match(/.*\u276f(?=[^\u276f]*$)/g)?.[0];
|
|
100
|
+
if (customPrompt) {
|
|
101
|
+
const adjustedPrompt = this._adjustPrompt(customPrompt, lineText, "\u276f");
|
|
102
|
+
if (adjustedPrompt) {
|
|
103
|
+
return adjustedPrompt;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
_adjustPrompt(prompt, lineText, char) {
|
|
108
|
+
if (!prompt) {
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
// Conpty may not 'render' the space at the end of the prompt
|
|
112
|
+
if (lineText === prompt && prompt.endsWith(char)) {
|
|
113
|
+
prompt += " ";
|
|
114
|
+
}
|
|
115
|
+
return prompt;
|
|
116
|
+
}
|
|
117
|
+
_isSuggestion(cell) {
|
|
118
|
+
const color = cell?.getFgColor();
|
|
119
|
+
const dullColor = color == 8 || (color ?? 0) > 235;
|
|
120
|
+
if (this.#shell == Shell.Powershell) {
|
|
121
|
+
return false;
|
|
122
|
+
}
|
|
123
|
+
else if (this.#shell == Shell.Pwsh) {
|
|
124
|
+
return (color ?? 0) > 235;
|
|
125
|
+
}
|
|
126
|
+
return dullColor;
|
|
127
|
+
}
|
|
128
|
+
getState() {
|
|
129
|
+
return {
|
|
130
|
+
promptText: this.#activeCommand.promptText,
|
|
131
|
+
commandText: this.#activeCommand.commandText,
|
|
132
|
+
suggestionsText: this.#activeCommand.suggestionsText,
|
|
133
|
+
hasOutput: this.#activeCommand.hasOutput,
|
|
134
|
+
cursorTerminated: this.#activeCommand.cursorTerminated,
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
termSync() {
|
|
138
|
+
if (this.#activeCommand.promptEndMarker == null || this.#activeCommand.promptStartMarker == null) {
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
const promptEndMarker = this.#activeCommand.promptEndMarker;
|
|
142
|
+
const promptStartMarker = this.#activeCommand.promptStartMarker;
|
|
143
|
+
const globalCursorPosition = this.#terminal.buffer.active.baseY + this.#terminal.buffer.active.cursorY;
|
|
144
|
+
const withinPollDistance = globalCursorPosition < this.#activeCommand.promptEndMarker.line + 5;
|
|
145
|
+
if (globalCursorPosition < promptStartMarker.line) {
|
|
146
|
+
this.handleClear();
|
|
147
|
+
}
|
|
148
|
+
// if we haven't fond the prompt yet, poll over the next 5 lines searching for it
|
|
149
|
+
if (this.#activeCommand.promptText == null && withinPollDistance) {
|
|
150
|
+
for (let i = globalCursorPosition; i < promptEndMarker.line + maxPromptPollDistance; i++) {
|
|
151
|
+
if (this.#previousCommandLines.has(i))
|
|
152
|
+
continue;
|
|
153
|
+
const promptResult = this._getWindowsPrompt(i);
|
|
154
|
+
if (promptResult != null) {
|
|
155
|
+
this.#activeCommand.promptEndMarker = this.#terminal.registerMarker(i - globalCursorPosition);
|
|
156
|
+
this.#activeCommand.promptEndX = promptResult.length;
|
|
157
|
+
this.#activeCommand.promptText = promptResult;
|
|
158
|
+
this.#previousCommandLines.add(i);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
// if the prompt is set, now parse out the values from the terminal
|
|
163
|
+
if (this.#activeCommand.promptText != null) {
|
|
164
|
+
let lineY = promptEndMarker.line;
|
|
165
|
+
let line = this.#terminal.buffer.active.getLine(promptEndMarker.line);
|
|
166
|
+
let command = "";
|
|
167
|
+
let suggestions = "";
|
|
168
|
+
for (;;) {
|
|
169
|
+
for (let i = lineY == promptEndMarker.line ? this.#activeCommand.promptText.length : 0; i < this.#terminal.cols; i++) {
|
|
170
|
+
const cell = line?.getCell(i);
|
|
171
|
+
if (cell == null)
|
|
172
|
+
continue;
|
|
173
|
+
if (!this._isSuggestion(cell) && suggestions.length == 0) {
|
|
174
|
+
command += cell.getChars();
|
|
175
|
+
}
|
|
176
|
+
else {
|
|
177
|
+
suggestions += cell.getChars();
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
lineY += 1;
|
|
181
|
+
line = this.#terminal.buffer.active.getLine(lineY);
|
|
182
|
+
if (!line?.isWrapped) {
|
|
183
|
+
break;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
const cursorAtEndOfInput = (this.#activeCommand.promptText.length + command.trim().length) % this.#terminal.cols <= this.#terminal.buffer.active.cursorX;
|
|
187
|
+
let hasOutput = false;
|
|
188
|
+
let cell = undefined;
|
|
189
|
+
for (let i = 0; i < this.#terminal.cols; i++) {
|
|
190
|
+
cell = line?.getCell(i, cell);
|
|
191
|
+
if (cell == null)
|
|
192
|
+
continue;
|
|
193
|
+
hasOutput = cell.getChars() != "";
|
|
194
|
+
if (hasOutput) {
|
|
195
|
+
break;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
const commandPostfix = this.#activeCommand.promptText.length + command.trim().length < this.#terminal.buffer.active.cursorX ? " " : "";
|
|
199
|
+
this.#activeCommand.persistentOutput = this.#activeCommand.hasOutput && hasOutput;
|
|
200
|
+
this.#activeCommand.hasOutput = hasOutput;
|
|
201
|
+
this.#activeCommand.suggestionsText = suggestions.trim();
|
|
202
|
+
this.#activeCommand.commandText = command.trim() + commandPostfix;
|
|
203
|
+
this.#activeCommand.cursorTerminated = cursorAtEndOfInput;
|
|
204
|
+
}
|
|
205
|
+
log.debug({
|
|
206
|
+
msg: "cmd manager state",
|
|
207
|
+
...this.#activeCommand,
|
|
208
|
+
promptEndMarker: this.#activeCommand.promptEndMarker?.line,
|
|
209
|
+
promptStartMarker: this.#activeCommand.promptStartMarker?.line,
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
}
|
|
@@ -0,0 +1,217 @@
|
|
|
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 pty from "node-pty";
|
|
9
|
+
import { Shell, userZdotdir, zdotdir } from "../utils/shell.js";
|
|
10
|
+
import { IsTermOscPs, IstermOscPt, IstermPromptStart, IstermPromptEnd } from "../utils/ansi.js";
|
|
11
|
+
import xterm from "xterm-headless";
|
|
12
|
+
import { CommandManager } from "./commandManager.js";
|
|
13
|
+
import log from "../utils/log.js";
|
|
14
|
+
import { gitBashPath } from "../utils/shell.js";
|
|
15
|
+
import ansi from "ansi-escapes";
|
|
16
|
+
import styles from "ansi-styles";
|
|
17
|
+
const ISTermOnDataEvent = "data";
|
|
18
|
+
export class ISTerm {
|
|
19
|
+
pid;
|
|
20
|
+
cols;
|
|
21
|
+
rows;
|
|
22
|
+
process;
|
|
23
|
+
handleFlowControl = false;
|
|
24
|
+
onData;
|
|
25
|
+
onExit;
|
|
26
|
+
shellBuffer;
|
|
27
|
+
#pty;
|
|
28
|
+
#ptyEmitter;
|
|
29
|
+
#term;
|
|
30
|
+
#commandManager;
|
|
31
|
+
constructor({ shell, cols, rows, env, shellTarget, shellArgs }) {
|
|
32
|
+
this.#pty = pty.spawn(shellTarget, shellArgs ?? [], {
|
|
33
|
+
name: "xterm-256color",
|
|
34
|
+
cols,
|
|
35
|
+
rows,
|
|
36
|
+
cwd: process.cwd(),
|
|
37
|
+
env: { ...convertToPtyEnv(shell), ...env },
|
|
38
|
+
});
|
|
39
|
+
this.pid = this.#pty.pid;
|
|
40
|
+
this.cols = this.#pty.cols;
|
|
41
|
+
this.rows = this.#pty.rows;
|
|
42
|
+
this.process = this.#pty.process;
|
|
43
|
+
this.#term = new xterm.Terminal({ allowProposedApi: true, rows, cols });
|
|
44
|
+
this.#term.parser.registerOscHandler(IsTermOscPs, (data) => this._handleIsSequence(data));
|
|
45
|
+
this.#commandManager = new CommandManager(this.#term, shell);
|
|
46
|
+
this.#ptyEmitter = new EventEmitter();
|
|
47
|
+
this.#pty.onData((data) => {
|
|
48
|
+
this.#term.write(data, () => {
|
|
49
|
+
log.debug({ msg: "parsing data", data, bytes: Uint8Array.from([...data].map((c) => c.charCodeAt(0))) });
|
|
50
|
+
this.#commandManager.termSync();
|
|
51
|
+
this.#ptyEmitter.emit(ISTermOnDataEvent, data);
|
|
52
|
+
});
|
|
53
|
+
});
|
|
54
|
+
this.onData = (listener) => {
|
|
55
|
+
this.#ptyEmitter.on(ISTermOnDataEvent, listener);
|
|
56
|
+
return {
|
|
57
|
+
dispose: () => this.#ptyEmitter.removeListener(ISTermOnDataEvent, listener),
|
|
58
|
+
};
|
|
59
|
+
};
|
|
60
|
+
this.onExit = this.#pty.onExit;
|
|
61
|
+
}
|
|
62
|
+
_handleIsSequence(data) {
|
|
63
|
+
const argsIndex = data.indexOf(";");
|
|
64
|
+
const sequence = argsIndex === -1 ? data : data.substring(0, argsIndex);
|
|
65
|
+
switch (sequence) {
|
|
66
|
+
case IstermOscPt.PromptStarted:
|
|
67
|
+
this.#commandManager.handlePromptStart();
|
|
68
|
+
break;
|
|
69
|
+
case IstermOscPt.PromptEnded:
|
|
70
|
+
this.#commandManager.handlePromptEnd();
|
|
71
|
+
break;
|
|
72
|
+
default:
|
|
73
|
+
return false;
|
|
74
|
+
}
|
|
75
|
+
return true;
|
|
76
|
+
}
|
|
77
|
+
noop() {
|
|
78
|
+
this.#ptyEmitter.emit(ISTermOnDataEvent, "");
|
|
79
|
+
}
|
|
80
|
+
resize(columns, rows) {
|
|
81
|
+
this.cols = columns;
|
|
82
|
+
this.rows = rows;
|
|
83
|
+
this.#pty.resize(columns, rows);
|
|
84
|
+
this.#term.resize(columns, rows);
|
|
85
|
+
}
|
|
86
|
+
clear() {
|
|
87
|
+
this.#term.reset();
|
|
88
|
+
this.#pty.clear();
|
|
89
|
+
}
|
|
90
|
+
kill(signal) {
|
|
91
|
+
this.#pty.kill(signal);
|
|
92
|
+
}
|
|
93
|
+
pause() {
|
|
94
|
+
this.#pty.pause();
|
|
95
|
+
}
|
|
96
|
+
resume() {
|
|
97
|
+
this.#pty.resume();
|
|
98
|
+
}
|
|
99
|
+
write(data) {
|
|
100
|
+
log.debug({ msg: "reading data", data, bytes: Uint8Array.from([...data].map((c) => c.charCodeAt(0))) });
|
|
101
|
+
this.#pty.write(data);
|
|
102
|
+
}
|
|
103
|
+
getCommandState() {
|
|
104
|
+
return this.#commandManager.getState();
|
|
105
|
+
}
|
|
106
|
+
getCursorState() {
|
|
107
|
+
return {
|
|
108
|
+
onLastLine: this.#term.buffer.active.cursorY >= this.#term.rows - 2,
|
|
109
|
+
remainingLines: Math.max(this.#term.rows - 2 - this.#term.buffer.active.cursorY, 0),
|
|
110
|
+
cursorX: this.#term.buffer.active.cursorX,
|
|
111
|
+
cursorY: this.#term.buffer.active.cursorY,
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
_sameColor(baseCell, targetCell) {
|
|
115
|
+
return (baseCell?.getBgColorMode() == targetCell?.getBgColorMode() &&
|
|
116
|
+
baseCell?.getBgColor() == targetCell?.getBgColor() &&
|
|
117
|
+
baseCell?.getFgColorMode() == targetCell?.getFgColorMode() &&
|
|
118
|
+
baseCell?.getFgColor() == targetCell?.getFgColor());
|
|
119
|
+
}
|
|
120
|
+
_getAnsiColors(cell) {
|
|
121
|
+
if (cell == null)
|
|
122
|
+
return "";
|
|
123
|
+
let bgAnsi = "";
|
|
124
|
+
cell.getBgColor;
|
|
125
|
+
cell.getFgColor;
|
|
126
|
+
if (cell.isBgDefault()) {
|
|
127
|
+
bgAnsi = "\x1b[49m";
|
|
128
|
+
}
|
|
129
|
+
else if (cell.isBgPalette()) {
|
|
130
|
+
bgAnsi = `\x1b[48;5;${cell.getBgColor()}m`;
|
|
131
|
+
}
|
|
132
|
+
else {
|
|
133
|
+
bgAnsi = `\x1b[48;5;${styles.hexToAnsi256(cell.getBgColor().toString(16))}m`;
|
|
134
|
+
}
|
|
135
|
+
let fgAnsi = "";
|
|
136
|
+
if (cell.isFgDefault()) {
|
|
137
|
+
fgAnsi = "\x1b[39m";
|
|
138
|
+
}
|
|
139
|
+
else if (cell.isFgPalette()) {
|
|
140
|
+
fgAnsi = `\x1b[38;5;${cell.getFgColor()}m`;
|
|
141
|
+
}
|
|
142
|
+
else {
|
|
143
|
+
fgAnsi = `\x1b[38;5;${styles.hexToAnsi256(cell.getFgColor().toString(16))}m`;
|
|
144
|
+
}
|
|
145
|
+
return bgAnsi + fgAnsi;
|
|
146
|
+
}
|
|
147
|
+
getCells(height, direction) {
|
|
148
|
+
const currentCursorPosition = this.#term.buffer.active.cursorY + this.#term.buffer.active.baseY;
|
|
149
|
+
const writeLine = (y) => {
|
|
150
|
+
const line = this.#term.buffer.active.getLine(y);
|
|
151
|
+
const ansiLine = ["\x1b[0m"];
|
|
152
|
+
if (line == null)
|
|
153
|
+
return "";
|
|
154
|
+
let prevCell;
|
|
155
|
+
for (let x = 0; x < line.length; x++) {
|
|
156
|
+
const cell = line.getCell(x);
|
|
157
|
+
const chars = cell?.getChars() ?? "";
|
|
158
|
+
if (!this._sameColor(prevCell, cell)) {
|
|
159
|
+
ansiLine.push(this._getAnsiColors(cell));
|
|
160
|
+
}
|
|
161
|
+
ansiLine.push(chars == "" ? " " : chars);
|
|
162
|
+
prevCell = cell;
|
|
163
|
+
}
|
|
164
|
+
return ansiLine.join("");
|
|
165
|
+
};
|
|
166
|
+
const lines = [];
|
|
167
|
+
if (direction == "above") {
|
|
168
|
+
const startCursorPosition = currentCursorPosition - 1;
|
|
169
|
+
const endCursorPosition = currentCursorPosition - 1 - height;
|
|
170
|
+
for (let y = startCursorPosition; y > endCursorPosition; y--) {
|
|
171
|
+
lines.push(writeLine(y));
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
else {
|
|
175
|
+
const startCursorPosition = currentCursorPosition + 1;
|
|
176
|
+
const endCursorPosition = currentCursorPosition + 1 + height;
|
|
177
|
+
for (let y = startCursorPosition; y < endCursorPosition; y++) {
|
|
178
|
+
lines.push(writeLine(y));
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
return lines.reverse().join(ansi.cursorNextLine);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
export const spawn = async (options) => {
|
|
185
|
+
const { shellTarget, shellArgs } = await convertToPtyTarget(options.shell);
|
|
186
|
+
return new ISTerm({ ...options, shellTarget, shellArgs });
|
|
187
|
+
};
|
|
188
|
+
const convertToPtyTarget = async (shell) => {
|
|
189
|
+
const platform = os.platform();
|
|
190
|
+
const shellTarget = shell == Shell.Bash && platform == "win32" ? await gitBashPath() : platform == "win32" ? `${shell}.exe` : shell;
|
|
191
|
+
const shellFolderPath = path.join(path.dirname(url.fileURLToPath(import.meta.url)), "..", "..", "shell");
|
|
192
|
+
let shellArgs = [];
|
|
193
|
+
switch (shell) {
|
|
194
|
+
case Shell.Bash:
|
|
195
|
+
shellArgs = ["--init-file", path.join(shellFolderPath, "shellIntegration.bash")];
|
|
196
|
+
break;
|
|
197
|
+
case (Shell.Powershell, Shell.Pwsh):
|
|
198
|
+
shellArgs = ["-noexit", "-command", `try { . "${path.join(shellFolderPath, "shellIntegration.ps1")}" } catch {}`];
|
|
199
|
+
break;
|
|
200
|
+
case Shell.Fish:
|
|
201
|
+
shellArgs = ["--init-command", `. ${path.join(shellFolderPath, "shellIntegration.fish").replace(/(\s+)/g, "\\$1")}`];
|
|
202
|
+
break;
|
|
203
|
+
}
|
|
204
|
+
return { shellTarget, shellArgs };
|
|
205
|
+
};
|
|
206
|
+
const convertToPtyEnv = (shell) => {
|
|
207
|
+
switch (shell) {
|
|
208
|
+
case Shell.Cmd: {
|
|
209
|
+
const prompt = process.env.PROMPT ? process.env.PROMPT : "$P$G";
|
|
210
|
+
return { ...process.env, PROMPT: `${IstermPromptStart}${prompt}${IstermPromptEnd}` };
|
|
211
|
+
}
|
|
212
|
+
case Shell.Zsh: {
|
|
213
|
+
return { ...process.env, ZDOTDIR: zdotdir, USER_ZDOTDIR: userZdotdir };
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
return process.env;
|
|
217
|
+
};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// Copyright (c) Microsoft Corporation.
|
|
2
2
|
// Licensed under the MIT License.
|
|
3
|
-
import fsAsync from "fs/promises";
|
|
3
|
+
import fsAsync from "node:fs/promises";
|
|
4
4
|
import process from "node:process";
|
|
5
5
|
const filepathsTemplate = async () => {
|
|
6
6
|
const files = await fsAsync.readdir(process.cwd(), { withFileTypes: true });
|
package/build/ui/input.js
CHANGED
|
@@ -1,55 +1,9 @@
|
|
|
1
1
|
// Copyright (c) Microsoft Corporation.
|
|
2
2
|
// Licensed under the MIT License.
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
const [cursorBlink, setCursorBlink] = useState(true);
|
|
11
|
-
useEffect(() => {
|
|
12
|
-
setTimeout(() => {
|
|
13
|
-
setCursorBlink(!cursorBlink);
|
|
14
|
-
}, BlinkSpeed);
|
|
15
|
-
}, [cursorBlink]);
|
|
16
|
-
// TODO: arrow key navigation shortcuts (emacs & vim modes)
|
|
17
|
-
useInput((input, key) => {
|
|
18
|
-
// TODO: handle delete better on unix systems: https://github.com/vadimdemedes/ink/issues/634
|
|
19
|
-
const windows = process.platform === "win32";
|
|
20
|
-
const backspaceKey = windows ? key.backspace : key.backspace || key.delete;
|
|
21
|
-
const deleteKey = windows ? key.delete : false;
|
|
22
|
-
if (backspaceKey) {
|
|
23
|
-
setValue([...value].slice(0, Math.max(cursorLocation - 1, 0)).join("") + [...value].slice(cursorLocation).join(""));
|
|
24
|
-
setCursorLocation(Math.max(cursorLocation - 1, 0));
|
|
25
|
-
}
|
|
26
|
-
else if (deleteKey) {
|
|
27
|
-
setValue([...value].slice(0, cursorLocation).join("") + [...value].slice(Math.min(value.length, cursorLocation + 1)).join(""));
|
|
28
|
-
}
|
|
29
|
-
else if (key.leftArrow) {
|
|
30
|
-
setCursorLocation(Math.max(cursorLocation - 1, 0));
|
|
31
|
-
}
|
|
32
|
-
else if (key.rightArrow) {
|
|
33
|
-
setCursorLocation(Math.min(cursorLocation + 1, value.length));
|
|
34
|
-
}
|
|
35
|
-
else if (key.tab) {
|
|
36
|
-
if (activeSuggestion) {
|
|
37
|
-
// TOOD: support insertValue
|
|
38
|
-
const newValue = [...value].slice(0, cursorLocation - tabCompletionDropSize).join("") + activeSuggestion.name + " ";
|
|
39
|
-
setValue(newValue);
|
|
40
|
-
setCursorLocation(newValue.length);
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
else if (input) {
|
|
44
|
-
setValue([...value].slice(0, cursorLocation).join("") + input + [...value].slice(cursorLocation).join(""));
|
|
45
|
-
setCursorLocation(cursorLocation + input.length);
|
|
46
|
-
}
|
|
47
|
-
});
|
|
48
|
-
const cursoredCommand = value + " ";
|
|
49
|
-
const cursoredText = [...cursoredCommand].slice(0, cursorLocation).join("") +
|
|
50
|
-
(cursorBlink ? chalk.bgHex(CursorColor).inverse([...cursoredCommand].at(cursorLocation)) : [...cursoredCommand].at(cursorLocation)) +
|
|
51
|
-
[...cursoredCommand].slice(cursorLocation + 1).join("");
|
|
52
|
-
return (React.createElement(Text, null,
|
|
53
|
-
prompt,
|
|
54
|
-
cursoredText));
|
|
55
|
-
}
|
|
3
|
+
export const inputModifier = (input) => {
|
|
4
|
+
switch (input.toString()) {
|
|
5
|
+
case "\b":
|
|
6
|
+
return "\u007F"; // DEL
|
|
7
|
+
}
|
|
8
|
+
return input.toString();
|
|
9
|
+
};
|