@microsoft/inshellisense 0.0.1-rc.13 → 0.0.1-rc.15
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 +1 -13
- package/build/commands/root.js +2 -2
- package/build/commands/specs/list.js +26 -0
- package/build/commands/specs/root.js +8 -0
- package/build/index.js +5 -1
- package/build/isterm/commandManager.js +100 -90
- package/build/isterm/pty.js +79 -22
- package/build/runtime/alias.js +1 -0
- package/build/runtime/runtime.js +3 -0
- package/build/ui/suggestionManager.js +10 -1
- package/build/ui/ui-root.js +2 -2
- package/build/ui/utils.js +2 -1
- package/build/utils/ansi.js +4 -0
- package/build/utils/config.js +42 -33
- package/build/utils/shell.js +43 -4
- package/package.json +5 -3
- package/shell/shellIntegration.bash +31 -2
- package/shell/shellIntegration.fish +8 -5
- package/shell/shellIntegration.nu +11 -3
- package/shell/shellIntegration.ps1 +5 -2
- package/shell/shellIntegration.xsh +12 -4
package/README.md
CHANGED
|
@@ -81,7 +81,7 @@ inshellisense supports the following shells:
|
|
|
81
81
|
|
|
82
82
|
## Configuration
|
|
83
83
|
|
|
84
|
-
All configuration is done through a [toml](https://toml.io/) file
|
|
84
|
+
All configuration is done through a [toml](https://toml.io/) file. You can create this file at `~/.inshellisenserc` or, for XDG compliance, at `~/.config/inshellisense/rc.toml`. The [JSON schema](https://json-schema.org/) for the configuration file can be found [here](https://github.com/microsoft/inshellisense/blob/main/src/utils/config.ts).
|
|
85
85
|
|
|
86
86
|
### Keybindings
|
|
87
87
|
|
|
@@ -106,18 +106,6 @@ key = "escape"
|
|
|
106
106
|
|
|
107
107
|
Key names are matched against the Node.js [keypress](https://nodejs.org/api/readline.html#readlineemitkeypresseventsstream-interface) events.
|
|
108
108
|
|
|
109
|
-
### Custom Prompts (Windows)
|
|
110
|
-
|
|
111
|
-
If you are using a custom prompt in your shell (anything that is not the default PS1), you will need to set up a custom prompt in the inshellisense config file. This is because Windows strips details from your prompt which are required for inshellisense to work. To do this, update your config file in your home directory and add the following configuration:
|
|
112
|
-
|
|
113
|
-
```toml
|
|
114
|
-
[[prompt.bash]]
|
|
115
|
-
regex = "(?<prompt>^>\\s*)" # the prompt match group will be used to detect the prompt
|
|
116
|
-
postfix = ">" # the postfix is the last expected character in your prompt
|
|
117
|
-
```
|
|
118
|
-
|
|
119
|
-
This example adds custom prompt detection for bash where the prompt is expected to be only `> `. You can add similar configurations for other shells as well as well as multiple configurations for each shell.
|
|
120
|
-
|
|
121
109
|
## Contributing
|
|
122
110
|
|
|
123
111
|
This project welcomes contributions and suggestions. Most contributions require you to agree to a
|
package/build/commands/root.js
CHANGED
|
@@ -12,7 +12,7 @@ export const action = (program) => async (options) => {
|
|
|
12
12
|
const inISTerm = process.env.ISTERM === "1";
|
|
13
13
|
if (options.check || inISTerm) {
|
|
14
14
|
process.stdout.write(renderConfirmation(inISTerm));
|
|
15
|
-
|
|
15
|
+
process.exit(0);
|
|
16
16
|
}
|
|
17
17
|
if (options.verbose)
|
|
18
18
|
await log.enable();
|
|
@@ -32,5 +32,5 @@ export const action = (program) => async (options) => {
|
|
|
32
32
|
await setupBashPreExec();
|
|
33
33
|
}
|
|
34
34
|
await loadAliases(shell);
|
|
35
|
-
await render(shell, options.test ?? false);
|
|
35
|
+
await render(shell, options.test ?? false, options.login ?? false);
|
|
36
36
|
};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
// Copyright (c) Microsoft Corporation.
|
|
2
|
+
// Licensed under the MIT License.
|
|
3
|
+
import { Command } from "commander";
|
|
4
|
+
import { loadConfig } from "../../utils/config.js";
|
|
5
|
+
import { getSpecNames, loadLocalSpecsSet } from "../../runtime/runtime.js";
|
|
6
|
+
import { getAliasNames, loadAliases } from "../../runtime/alias.js";
|
|
7
|
+
import { aliasSupportedShells } from "../../utils/shell.js";
|
|
8
|
+
const supportedShells = aliasSupportedShells.join(", ");
|
|
9
|
+
const action = (program) => async (options) => {
|
|
10
|
+
await loadConfig(program);
|
|
11
|
+
await loadLocalSpecsSet();
|
|
12
|
+
const { shell } = options;
|
|
13
|
+
if (shell && !aliasSupportedShells.map((s) => s.valueOf()).includes(shell)) {
|
|
14
|
+
program.error(`Unsupported shell: '${shell}', supported shells: ${supportedShells}`, { exitCode: 1 });
|
|
15
|
+
}
|
|
16
|
+
if (shell) {
|
|
17
|
+
await loadAliases(shell);
|
|
18
|
+
}
|
|
19
|
+
process.stdout.write(JSON.stringify([...getAliasNames(), ...getSpecNames()]));
|
|
20
|
+
process.exit(0);
|
|
21
|
+
};
|
|
22
|
+
const cmd = new Command("list");
|
|
23
|
+
cmd.description(`list the names of all available specs`);
|
|
24
|
+
cmd.option("-s, --shell <shell>", `shell to use alias specs, supported shells: ${supportedShells}`);
|
|
25
|
+
cmd.action(action(cmd));
|
|
26
|
+
export default cmd;
|
package/build/index.js
CHANGED
|
@@ -6,6 +6,7 @@ import { Command, Option } from "commander";
|
|
|
6
6
|
import complete from "./commands/complete.js";
|
|
7
7
|
import uninstall from "./commands/uninstall.js";
|
|
8
8
|
import init from "./commands/init.js";
|
|
9
|
+
import specs from "./commands/specs/root.js";
|
|
9
10
|
import { action, supportedShells } from "./commands/root.js";
|
|
10
11
|
import { getVersion } from "./utils/version.js";
|
|
11
12
|
const program = new Command();
|
|
@@ -19,12 +20,15 @@ program
|
|
|
19
20
|
.description("IDE style command line auto complete")
|
|
20
21
|
.version(await getVersion(), "-v, --version", "output the current version")
|
|
21
22
|
.action(action(program))
|
|
23
|
+
.option("-l, --login", `start shell as a login shell`)
|
|
22
24
|
.option("-s, --shell <shell>", `shell to use for command execution, supported shells: ${supportedShells}`)
|
|
23
25
|
.option("-c, --check", `check if shell is in an inshellisense session`)
|
|
24
26
|
.addOption(hiddenOption("-T, --test", "used to make e2e tests reproducible across machines"))
|
|
25
27
|
.option("-V, --verbose", `enable verbose logging`)
|
|
26
|
-
.showHelpAfterError("(add --help for additional information)")
|
|
28
|
+
.showHelpAfterError("(add --help for additional information)")
|
|
29
|
+
.passThroughOptions();
|
|
27
30
|
program.addCommand(complete);
|
|
28
31
|
program.addCommand(uninstall);
|
|
29
32
|
program.addCommand(init);
|
|
33
|
+
program.addCommand(specs);
|
|
30
34
|
program.parse();
|
|
@@ -4,19 +4,21 @@ import convert from "color-convert";
|
|
|
4
4
|
import os from "node:os";
|
|
5
5
|
import { getShellPromptRewrites, Shell } from "../utils/shell.js";
|
|
6
6
|
import log from "../utils/log.js";
|
|
7
|
-
import { getConfig } from "../utils/config.js";
|
|
8
7
|
const maxPromptPollDistance = 10;
|
|
9
8
|
export class CommandManager {
|
|
10
9
|
#activeCommand;
|
|
11
10
|
#terminal;
|
|
12
11
|
#previousCommandLines;
|
|
12
|
+
#maxCursorY;
|
|
13
13
|
#shell;
|
|
14
14
|
#promptRewrites;
|
|
15
15
|
#supportsProperOscPlacements = os.platform() !== "win32";
|
|
16
|
+
promptTerminator = "";
|
|
16
17
|
constructor(terminal, shell) {
|
|
17
18
|
this.#terminal = terminal;
|
|
18
19
|
this.#shell = shell;
|
|
19
20
|
this.#activeCommand = {};
|
|
21
|
+
this.#maxCursorY = 0;
|
|
20
22
|
this.#previousCommandLines = new Set();
|
|
21
23
|
this.#promptRewrites = getShellPromptRewrites(shell);
|
|
22
24
|
if (this.#supportsProperOscPlacements) {
|
|
@@ -45,17 +47,9 @@ export class CommandManager {
|
|
|
45
47
|
}
|
|
46
48
|
handleClear() {
|
|
47
49
|
this.handlePromptStart();
|
|
50
|
+
this.#maxCursorY = 0;
|
|
48
51
|
this.#previousCommandLines = new Set();
|
|
49
52
|
}
|
|
50
|
-
_extractPrompt(lineText, patterns) {
|
|
51
|
-
for (const { regex, postfix } of patterns) {
|
|
52
|
-
const customPrompt = lineText.match(new RegExp(regex))?.groups?.prompt;
|
|
53
|
-
const adjustedPrompt = this._adjustPrompt(customPrompt, lineText, postfix);
|
|
54
|
-
if (adjustedPrompt) {
|
|
55
|
-
return adjustedPrompt;
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
53
|
_getWindowsPrompt(y) {
|
|
60
54
|
const line = this.#terminal.buffer.active.getLine(y);
|
|
61
55
|
if (!line) {
|
|
@@ -65,15 +59,16 @@ export class CommandManager {
|
|
|
65
59
|
if (!lineText) {
|
|
66
60
|
return;
|
|
67
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
|
+
}
|
|
68
69
|
// User defined prompt
|
|
69
|
-
const inshellisenseConfig = getConfig();
|
|
70
70
|
if (this.#shell == Shell.Bash) {
|
|
71
|
-
|
|
72
|
-
const extractedPrompt = this._extractPrompt(lineText, inshellisenseConfig.prompt.bash);
|
|
73
|
-
if (extractedPrompt)
|
|
74
|
-
return extractedPrompt;
|
|
75
|
-
}
|
|
76
|
-
const bashPrompt = lineText.match(/^(?<prompt>.*\$\s?)/)?.groups?.prompt;
|
|
71
|
+
const bashPrompt = lineText.match(/^(?<prompt>\$\s?)/)?.groups?.prompt;
|
|
77
72
|
if (bashPrompt) {
|
|
78
73
|
const adjustedPrompt = this._adjustPrompt(bashPrompt, lineText, "$");
|
|
79
74
|
if (adjustedPrompt) {
|
|
@@ -81,12 +76,16 @@ export class CommandManager {
|
|
|
81
76
|
}
|
|
82
77
|
}
|
|
83
78
|
}
|
|
84
|
-
if (this.#shell == Shell.
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
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
|
+
}
|
|
89
86
|
}
|
|
87
|
+
}
|
|
88
|
+
if (this.#shell == Shell.Nushell) {
|
|
90
89
|
const nushellPrompt = lineText.match(/(?<prompt>.*>\s?)/)?.groups?.prompt;
|
|
91
90
|
if (nushellPrompt) {
|
|
92
91
|
const adjustedPrompt = this._adjustPrompt(nushellPrompt, lineText, ">");
|
|
@@ -96,11 +95,6 @@ export class CommandManager {
|
|
|
96
95
|
}
|
|
97
96
|
}
|
|
98
97
|
if (this.#shell == Shell.Xonsh) {
|
|
99
|
-
if (inshellisenseConfig?.prompt?.xonsh != null) {
|
|
100
|
-
const extractedPrompt = this._extractPrompt(lineText, inshellisenseConfig.prompt.xonsh);
|
|
101
|
-
if (extractedPrompt)
|
|
102
|
-
return extractedPrompt;
|
|
103
|
-
}
|
|
104
98
|
let xonshPrompt = lineText.match(/(?<prompt>.*@\s?)/)?.groups?.prompt;
|
|
105
99
|
if (xonshPrompt) {
|
|
106
100
|
const adjustedPrompt = this._adjustPrompt(xonshPrompt, lineText, "@");
|
|
@@ -117,16 +111,6 @@ export class CommandManager {
|
|
|
117
111
|
}
|
|
118
112
|
}
|
|
119
113
|
if (this.#shell == Shell.Powershell || this.#shell == Shell.Pwsh) {
|
|
120
|
-
if (inshellisenseConfig?.prompt?.powershell != null) {
|
|
121
|
-
const extractedPrompt = this._extractPrompt(lineText, inshellisenseConfig.prompt.powershell);
|
|
122
|
-
if (extractedPrompt)
|
|
123
|
-
return extractedPrompt;
|
|
124
|
-
}
|
|
125
|
-
if (inshellisenseConfig?.prompt?.pwsh != null) {
|
|
126
|
-
const extractedPrompt = this._extractPrompt(lineText, inshellisenseConfig.prompt.pwsh);
|
|
127
|
-
if (extractedPrompt)
|
|
128
|
-
return extractedPrompt;
|
|
129
|
-
}
|
|
130
114
|
const pwshPrompt = lineText.match(/(?<prompt>(\(.+\)\s)?(?:PS.+>\s?))/)?.groups?.prompt;
|
|
131
115
|
if (pwshPrompt) {
|
|
132
116
|
const adjustedPrompt = this._adjustPrompt(pwshPrompt, lineText, ">");
|
|
@@ -188,15 +172,84 @@ export class CommandManager {
|
|
|
188
172
|
cursorTerminated: this.#activeCommand.cursorTerminated,
|
|
189
173
|
};
|
|
190
174
|
}
|
|
175
|
+
clearActiveCommand() {
|
|
176
|
+
this.#activeCommand = {};
|
|
177
|
+
}
|
|
178
|
+
_getCommandLines() {
|
|
179
|
+
const lines = [];
|
|
180
|
+
let lineY = this.#activeCommand.promptEndMarker.line;
|
|
181
|
+
let line = this.#terminal.buffer.active.getLine(this.#activeCommand.promptEndMarker.line);
|
|
182
|
+
const absoluteY = this.#terminal.buffer.active.baseY + this.#terminal.buffer.active.cursorY;
|
|
183
|
+
for (; lineY < this.#terminal.buffer.active.baseY + this.#terminal.rows;) {
|
|
184
|
+
if (line)
|
|
185
|
+
lines.push(line);
|
|
186
|
+
lineY += 1;
|
|
187
|
+
line = this.#terminal.buffer.active.getLine(lineY);
|
|
188
|
+
const lineWrapped = line?.isWrapped;
|
|
189
|
+
const cursorWrapped = absoluteY > lineY - 1;
|
|
190
|
+
const wrapped = lineWrapped || cursorWrapped;
|
|
191
|
+
if (!wrapped)
|
|
192
|
+
break;
|
|
193
|
+
}
|
|
194
|
+
return lines;
|
|
195
|
+
}
|
|
196
|
+
_getCommandText(commandLines) {
|
|
197
|
+
const absoluteY = this.#terminal.buffer.active.baseY + this.#terminal.buffer.active.cursorY;
|
|
198
|
+
const cursorLine = Math.max(absoluteY - this.#activeCommand.promptEndMarker.line, 0);
|
|
199
|
+
let preCursorCommand = "";
|
|
200
|
+
let postCursorCommand = "";
|
|
201
|
+
let suggestion = "";
|
|
202
|
+
for (const [y, line] of commandLines.entries()) {
|
|
203
|
+
const startX = y == 0 ? this.#activeCommand.promptText?.length ?? 0 : 0;
|
|
204
|
+
for (let x = startX; x < this.#terminal.cols; x++) {
|
|
205
|
+
if (postCursorCommand.endsWith(" "))
|
|
206
|
+
break; // assume that a command that ends with 4 spaces is terminated, avoids capturing right prompts
|
|
207
|
+
const cell = line.getCell(x);
|
|
208
|
+
if (cell == null)
|
|
209
|
+
continue;
|
|
210
|
+
const chars = cell.getChars() == "" ? " " : cell.getChars();
|
|
211
|
+
const beforeCursor = y < cursorLine || (y == cursorLine && x < this.#terminal.buffer.active.cursorX);
|
|
212
|
+
const isCommand = !this._isSuggestion(cell) && suggestion.length == 0;
|
|
213
|
+
if (isCommand && beforeCursor) {
|
|
214
|
+
preCursorCommand += chars;
|
|
215
|
+
}
|
|
216
|
+
else if (isCommand) {
|
|
217
|
+
postCursorCommand += chars;
|
|
218
|
+
}
|
|
219
|
+
else {
|
|
220
|
+
suggestion += chars;
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
log.debug({ msg: "command text", preCursorCommand, postCursorCommand, suggestion });
|
|
225
|
+
return { suggestion, preCursorCommand, postCursorCommand };
|
|
226
|
+
}
|
|
227
|
+
_getCommandOutputStatus(commandLines) {
|
|
228
|
+
const outputLineY = this.#activeCommand.promptEndMarker.line + commandLines;
|
|
229
|
+
const maxLineY = this.#terminal.buffer.active.baseY + this.#terminal.rows;
|
|
230
|
+
if (outputLineY >= maxLineY)
|
|
231
|
+
return false;
|
|
232
|
+
const line = this.#terminal.buffer.active.getLine(outputLineY);
|
|
233
|
+
let cell = undefined;
|
|
234
|
+
for (let i = 0; i < this.#terminal.cols; i++) {
|
|
235
|
+
cell = line?.getCell(i, cell);
|
|
236
|
+
if (cell?.getChars() != "") {
|
|
237
|
+
return true;
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
return false;
|
|
241
|
+
}
|
|
191
242
|
termSync() {
|
|
192
243
|
if (this.#activeCommand.promptEndMarker == null || this.#activeCommand.promptStartMarker == null) {
|
|
193
244
|
return;
|
|
194
245
|
}
|
|
195
246
|
const globalCursorPosition = this.#terminal.buffer.active.baseY + this.#terminal.buffer.active.cursorY;
|
|
196
247
|
const withinPollDistance = globalCursorPosition < this.#activeCommand.promptEndMarker.line + 5;
|
|
197
|
-
|
|
248
|
+
this.#maxCursorY = Math.max(this.#maxCursorY, globalCursorPosition);
|
|
249
|
+
if (globalCursorPosition < this.#activeCommand.promptStartMarker.line || globalCursorPosition < this.#maxCursorY) {
|
|
198
250
|
this.handleClear();
|
|
199
251
|
this.#activeCommand.promptEndMarker = this.#terminal.registerMarker(0);
|
|
252
|
+
return;
|
|
200
253
|
}
|
|
201
254
|
if (this.#activeCommand.promptEndMarker == null)
|
|
202
255
|
return;
|
|
@@ -217,60 +270,15 @@ export class CommandManager {
|
|
|
217
270
|
}
|
|
218
271
|
// if the prompt is set, now parse out the values from the terminal
|
|
219
272
|
if (this.#activeCommand.promptText != null) {
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
let isWrapped = false;
|
|
226
|
-
for (; lineY < this.#terminal.buffer.active.baseY + this.#terminal.rows;) {
|
|
227
|
-
for (let i = lineY == this.#activeCommand.promptEndMarker.line ? this.#activeCommand.promptText.length : 0; i < this.#terminal.cols; i++) {
|
|
228
|
-
if (command.endsWith(" "))
|
|
229
|
-
break; // assume that a command that ends with 4 spaces is terminated, avoids capturing right prompts
|
|
230
|
-
const cell = line?.getCell(i);
|
|
231
|
-
if (cell == null)
|
|
232
|
-
continue;
|
|
233
|
-
const chars = cell.getChars();
|
|
234
|
-
const cleanedChars = chars == "" ? " " : chars;
|
|
235
|
-
if (!this._isSuggestion(cell) && suggestions.length == 0) {
|
|
236
|
-
command += cleanedChars;
|
|
237
|
-
wrappedCommand += cleanedChars;
|
|
238
|
-
}
|
|
239
|
-
else {
|
|
240
|
-
suggestions += cleanedChars;
|
|
241
|
-
}
|
|
242
|
-
}
|
|
243
|
-
lineY += 1;
|
|
244
|
-
line = this.#terminal.buffer.active.getLine(lineY);
|
|
245
|
-
const wrapped = line?.isWrapped || this.#terminal.buffer.active.cursorY + this.#terminal.buffer.active.baseY != lineY - 1;
|
|
246
|
-
isWrapped = isWrapped || wrapped;
|
|
247
|
-
if (!wrapped) {
|
|
248
|
-
break;
|
|
249
|
-
}
|
|
250
|
-
wrappedCommand = "";
|
|
251
|
-
}
|
|
252
|
-
const cursorAtEndOfInput = isWrapped
|
|
253
|
-
? wrappedCommand.trim().length % this.#terminal.cols <= this.#terminal.buffer.active.cursorX
|
|
254
|
-
: (this.#activeCommand.promptText.length + command.trimEnd().length) % this.#terminal.cols <= this.#terminal.buffer.active.cursorX;
|
|
255
|
-
let hasOutput = false;
|
|
256
|
-
let cell = undefined;
|
|
257
|
-
for (let i = 0; i < this.#terminal.cols; i++) {
|
|
258
|
-
cell = line?.getCell(i, cell);
|
|
259
|
-
if (cell == null)
|
|
260
|
-
continue;
|
|
261
|
-
hasOutput = cell.getChars() != "";
|
|
262
|
-
if (hasOutput) {
|
|
263
|
-
break;
|
|
264
|
-
}
|
|
265
|
-
}
|
|
266
|
-
const postfixActive = isWrapped
|
|
267
|
-
? wrappedCommand.trim().length < this.#terminal.buffer.active.cursorX
|
|
268
|
-
: this.#activeCommand.promptText.length + command.trimEnd().length < this.#terminal.buffer.active.cursorX;
|
|
269
|
-
const commandPostfix = postfixActive ? " " : "";
|
|
273
|
+
const commandLines = this._getCommandLines();
|
|
274
|
+
const { suggestion, preCursorCommand, postCursorCommand } = this._getCommandText(commandLines);
|
|
275
|
+
const command = preCursorCommand + postCursorCommand.trim();
|
|
276
|
+
const cursorAtEndOfInput = postCursorCommand.trim() == "";
|
|
277
|
+
const hasOutput = this._getCommandOutputStatus(commandLines.length);
|
|
270
278
|
this.#activeCommand.persistentOutput = this.#activeCommand.hasOutput && hasOutput;
|
|
271
279
|
this.#activeCommand.hasOutput = hasOutput;
|
|
272
|
-
this.#activeCommand.suggestionsText =
|
|
273
|
-
this.#activeCommand.commandText = command
|
|
280
|
+
this.#activeCommand.suggestionsText = suggestion;
|
|
281
|
+
this.#activeCommand.commandText = command;
|
|
274
282
|
this.#activeCommand.cursorTerminated = cursorAtEndOfInput;
|
|
275
283
|
}
|
|
276
284
|
log.debug({
|
|
@@ -278,6 +286,8 @@ export class CommandManager {
|
|
|
278
286
|
...this.#activeCommand,
|
|
279
287
|
promptEndMarker: this.#activeCommand.promptEndMarker?.line,
|
|
280
288
|
promptStartMarker: this.#activeCommand.promptStartMarker?.line,
|
|
289
|
+
cursorX: this.#terminal.buffer.active.cursorX,
|
|
290
|
+
cursorY: globalCursorPosition,
|
|
281
291
|
});
|
|
282
292
|
}
|
|
283
293
|
}
|
package/build/isterm/pty.js
CHANGED
|
@@ -6,6 +6,7 @@ import os from "node:os";
|
|
|
6
6
|
import path from "node:path";
|
|
7
7
|
import url from "node:url";
|
|
8
8
|
import fs from "node:fs";
|
|
9
|
+
import stripAnsi from "strip-ansi";
|
|
9
10
|
import pty from "@homebridge/node-pty-prebuilt-multiarch";
|
|
10
11
|
import { Shell, userZdotdir, zdotdir } from "../utils/shell.js";
|
|
11
12
|
import { IsTermOscPs, IstermOscPt, IstermPromptStart, IstermPromptEnd } from "../utils/ansi.js";
|
|
@@ -13,8 +14,8 @@ import xterm from "@xterm/headless";
|
|
|
13
14
|
import { CommandManager } from "./commandManager.js";
|
|
14
15
|
import log from "../utils/log.js";
|
|
15
16
|
import { gitBashPath } from "../utils/shell.js";
|
|
16
|
-
import ansi from "ansi-escapes";
|
|
17
17
|
import styles from "ansi-styles";
|
|
18
|
+
import * as ansi from "../utils/ansi.js";
|
|
18
19
|
const ISTermOnDataEvent = "data";
|
|
19
20
|
export class ISTerm {
|
|
20
21
|
pid;
|
|
@@ -31,13 +32,13 @@ export class ISTerm {
|
|
|
31
32
|
#term;
|
|
32
33
|
#commandManager;
|
|
33
34
|
#shell;
|
|
34
|
-
constructor({ shell, cols, rows, env, shellTarget, shellArgs, underTest }) {
|
|
35
|
+
constructor({ shell, cols, rows, env, shellTarget, shellArgs, underTest, login }) {
|
|
35
36
|
this.#pty = pty.spawn(shellTarget, shellArgs ?? [], {
|
|
36
37
|
name: "xterm-256color",
|
|
37
38
|
cols,
|
|
38
39
|
rows,
|
|
39
40
|
cwd: process.cwd(),
|
|
40
|
-
env: { ...convertToPtyEnv(shell, underTest), ...env },
|
|
41
|
+
env: { ...convertToPtyEnv(shell, underTest, login), ...env },
|
|
41
42
|
});
|
|
42
43
|
this.pid = this.#pty.pid;
|
|
43
44
|
this.cols = this.#pty.cols;
|
|
@@ -84,6 +85,9 @@ export class ISTerm {
|
|
|
84
85
|
}
|
|
85
86
|
return cwd;
|
|
86
87
|
}
|
|
88
|
+
_sanitizedPrompt(prompt) {
|
|
89
|
+
return stripAnsi(prompt);
|
|
90
|
+
}
|
|
87
91
|
_handleIsSequence(data) {
|
|
88
92
|
const argsIndex = data.indexOf(";");
|
|
89
93
|
const sequence = argsIndex === -1 ? data : data.substring(0, argsIndex);
|
|
@@ -101,6 +105,19 @@ export class ISTerm {
|
|
|
101
105
|
}
|
|
102
106
|
break;
|
|
103
107
|
}
|
|
108
|
+
case IstermOscPt.Prompt: {
|
|
109
|
+
const prompt = data.split(";").slice(1).join(";");
|
|
110
|
+
if (prompt != null) {
|
|
111
|
+
const sanitizedPrompt = this._sanitizedPrompt(this._deserializeIsMessage(prompt));
|
|
112
|
+
const lastPromptLine = sanitizedPrompt.substring(sanitizedPrompt.lastIndexOf("\n")).trim();
|
|
113
|
+
const promptTerminator = lastPromptLine.substring(lastPromptLine.lastIndexOf(" ")).trim();
|
|
114
|
+
if (promptTerminator) {
|
|
115
|
+
this.#commandManager.promptTerminator = promptTerminator;
|
|
116
|
+
log.debug({ msg: "prompt terminator", promptTerminator });
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
break;
|
|
120
|
+
}
|
|
104
121
|
default:
|
|
105
122
|
return false;
|
|
106
123
|
}
|
|
@@ -144,24 +161,37 @@ export class ISTerm {
|
|
|
144
161
|
};
|
|
145
162
|
}
|
|
146
163
|
_sameAccent(baseCell, targetCell) {
|
|
147
|
-
return baseCell?.isBold() == targetCell?.isBold() &&
|
|
164
|
+
return (baseCell?.isBold() == targetCell?.isBold() &&
|
|
165
|
+
baseCell?.isItalic() == targetCell?.isItalic() &&
|
|
166
|
+
baseCell?.isUnderline() == targetCell?.isUnderline() &&
|
|
167
|
+
baseCell?.extended.underlineStyle == targetCell?.extended.underlineStyle &&
|
|
168
|
+
baseCell?.hasExtendedAttrs() == targetCell?.hasExtendedAttrs() &&
|
|
169
|
+
baseCell?.isInverse() == targetCell?.isInverse() &&
|
|
170
|
+
baseCell?.isBlink() == targetCell?.isBlink() &&
|
|
171
|
+
baseCell?.isInvisible() == targetCell?.isInvisible() &&
|
|
172
|
+
baseCell?.isDim() == targetCell?.isDim() &&
|
|
173
|
+
baseCell?.isStrikethrough() == targetCell?.isStrikethrough());
|
|
148
174
|
}
|
|
149
175
|
_getAnsiAccents(cell) {
|
|
150
176
|
if (cell == null)
|
|
151
177
|
return "";
|
|
152
|
-
let boldAnsi = "";
|
|
153
|
-
if (cell.isBold()) {
|
|
154
|
-
boldAnsi = "\x1b[1m";
|
|
155
|
-
}
|
|
156
|
-
let italicAnsi = "";
|
|
157
|
-
if (cell.isItalic()) {
|
|
158
|
-
italicAnsi = "\x1b[3m";
|
|
159
|
-
}
|
|
160
178
|
let underlineAnsi = "";
|
|
161
179
|
if (cell.isUnderline()) {
|
|
162
|
-
|
|
180
|
+
if (cell.hasExtendedAttrs() && cell.extended.underlineStyle) {
|
|
181
|
+
underlineAnsi = `\x1b[4:${cell.extended.underlineStyle}m`;
|
|
182
|
+
}
|
|
183
|
+
else {
|
|
184
|
+
underlineAnsi = "\x1b[4m";
|
|
185
|
+
}
|
|
163
186
|
}
|
|
164
|
-
|
|
187
|
+
const boldAnsi = cell.isBold() ? "\x1b[1m" : "";
|
|
188
|
+
const dimAnsi = cell.isDim() ? "\x1b[2m" : "";
|
|
189
|
+
const italicAnsi = cell.isItalic() ? "\x1b[3m" : "";
|
|
190
|
+
const blinkAnsi = cell.isBlink() ? "\x1b[5m" : "";
|
|
191
|
+
const inverseAnsi = cell.isInverse() ? "\x1b[7m" : "";
|
|
192
|
+
const invisibleAnsi = cell.isInvisible() ? "\x1b[8m" : "";
|
|
193
|
+
const strikethroughAnsi = cell.isStrikethrough() ? "\x1b[9m" : "";
|
|
194
|
+
return boldAnsi + italicAnsi + underlineAnsi + inverseAnsi + dimAnsi + blinkAnsi + invisibleAnsi + strikethroughAnsi;
|
|
165
195
|
}
|
|
166
196
|
_sameColor(baseCell, targetCell) {
|
|
167
197
|
return (baseCell?.getBgColorMode() == targetCell?.getBgColorMode() &&
|
|
@@ -196,24 +226,32 @@ export class ISTerm {
|
|
|
196
226
|
}
|
|
197
227
|
return bgAnsi + fgAnsi;
|
|
198
228
|
}
|
|
229
|
+
clearCommand() {
|
|
230
|
+
this.#commandManager.clearActiveCommand();
|
|
231
|
+
}
|
|
199
232
|
getCells(height, direction) {
|
|
200
233
|
const currentCursorPosition = this.#term.buffer.active.cursorY + this.#term.buffer.active.baseY;
|
|
201
234
|
const writeLine = (y) => {
|
|
202
235
|
const line = this.#term.buffer.active.getLine(y);
|
|
203
|
-
const ansiLine = [
|
|
236
|
+
const ansiLine = [ansi.resetColor, ansi.resetLine];
|
|
204
237
|
if (line == null)
|
|
205
238
|
return "";
|
|
206
239
|
let prevCell;
|
|
207
240
|
for (let x = 0; x < line.length; x++) {
|
|
208
241
|
const cell = line.getCell(x);
|
|
209
242
|
const chars = cell?.getChars() ?? "";
|
|
210
|
-
|
|
243
|
+
const sameColor = this._sameColor(prevCell, cell);
|
|
244
|
+
const sameAccents = this._sameAccent(prevCell, cell);
|
|
245
|
+
if (!sameColor || !sameAccents) {
|
|
246
|
+
ansiLine.push(ansi.resetColor);
|
|
247
|
+
}
|
|
248
|
+
if (!sameColor) {
|
|
211
249
|
ansiLine.push(this._getAnsiColors(cell));
|
|
212
250
|
}
|
|
213
|
-
if (!
|
|
251
|
+
if (!sameAccents) {
|
|
214
252
|
ansiLine.push(this._getAnsiAccents(cell));
|
|
215
253
|
}
|
|
216
|
-
ansiLine.push(chars == "" ?
|
|
254
|
+
ansiLine.push(chars == "" ? ansi.cursorForward() : chars);
|
|
217
255
|
prevCell = cell;
|
|
218
256
|
}
|
|
219
257
|
return ansiLine.join("");
|
|
@@ -237,10 +275,10 @@ export class ISTerm {
|
|
|
237
275
|
}
|
|
238
276
|
}
|
|
239
277
|
export const spawn = async (options) => {
|
|
240
|
-
const { shellTarget, shellArgs } = await convertToPtyTarget(options.shell, options.underTest);
|
|
278
|
+
const { shellTarget, shellArgs } = await convertToPtyTarget(options.shell, options.underTest, options.login);
|
|
241
279
|
return new ISTerm({ ...options, shellTarget, shellArgs });
|
|
242
280
|
};
|
|
243
|
-
const convertToPtyTarget = async (shell, underTest) => {
|
|
281
|
+
const convertToPtyTarget = async (shell, underTest, login) => {
|
|
244
282
|
const platform = os.platform();
|
|
245
283
|
const shellTarget = shell == Shell.Bash && platform == "win32" ? await gitBashPath() : platform == "win32" ? `${shell}.exe` : shell;
|
|
246
284
|
const shellFolderPath = path.join(path.dirname(url.fileURLToPath(import.meta.url)), "..", "..", "shell");
|
|
@@ -254,7 +292,10 @@ const convertToPtyTarget = async (shell, underTest) => {
|
|
|
254
292
|
shellArgs = ["-noexit", "-command", `try { . "${path.join(shellFolderPath, "shellIntegration.ps1")}" } catch {}`];
|
|
255
293
|
break;
|
|
256
294
|
case Shell.Fish:
|
|
257
|
-
shellArgs =
|
|
295
|
+
shellArgs =
|
|
296
|
+
platform == "win32"
|
|
297
|
+
? ["--init-command", `. "$(cygpath -u '${path.join(shellFolderPath, "shellIntegration.fish")}')"`]
|
|
298
|
+
: ["--init-command", `. ${path.join(shellFolderPath, "shellIntegration.fish").replace(/(\s+)/g, "\\$1")}`];
|
|
258
299
|
break;
|
|
259
300
|
case Shell.Xonsh: {
|
|
260
301
|
const sharedConfig = os.platform() == "win32" ? path.join("C:\\ProgramData", "xonsh", "xonshrc") : path.join("etc", "xonsh", "xonshrc");
|
|
@@ -273,15 +314,31 @@ const convertToPtyTarget = async (shell, underTest) => {
|
|
|
273
314
|
shellArgs.push("-n");
|
|
274
315
|
break;
|
|
275
316
|
}
|
|
317
|
+
if (login) {
|
|
318
|
+
switch (shell) {
|
|
319
|
+
case Shell.Powershell:
|
|
320
|
+
case Shell.Pwsh:
|
|
321
|
+
shellArgs.unshift("-login");
|
|
322
|
+
break;
|
|
323
|
+
case Shell.Zsh:
|
|
324
|
+
case Shell.Fish:
|
|
325
|
+
case Shell.Xonsh:
|
|
326
|
+
case Shell.Nushell:
|
|
327
|
+
shellArgs.unshift("--login");
|
|
328
|
+
break;
|
|
329
|
+
}
|
|
330
|
+
}
|
|
276
331
|
return { shellTarget, shellArgs };
|
|
277
332
|
};
|
|
278
|
-
const convertToPtyEnv = (shell, underTest) => {
|
|
333
|
+
const convertToPtyEnv = (shell, underTest, login) => {
|
|
279
334
|
const env = {
|
|
280
335
|
...process.env,
|
|
281
336
|
ISTERM: "1",
|
|
282
337
|
};
|
|
283
338
|
if (underTest)
|
|
284
339
|
env.ISTERM_TESTING = "1";
|
|
340
|
+
if (login)
|
|
341
|
+
env.ISTERM_LOGIN = "1";
|
|
285
342
|
switch (shell) {
|
|
286
343
|
case Shell.Cmd: {
|
|
287
344
|
if (underTest) {
|
package/build/runtime/alias.js
CHANGED
package/build/runtime/runtime.js
CHANGED
|
@@ -103,6 +103,9 @@ export const getSuggestions = async (cmd, cwd, shell) => {
|
|
|
103
103
|
}
|
|
104
104
|
return { ...result, charactersToDrop };
|
|
105
105
|
};
|
|
106
|
+
export const getSpecNames = () => {
|
|
107
|
+
return Object.keys(specSet).filter((spec) => !spec.startsWith("@") && spec != "-");
|
|
108
|
+
};
|
|
106
109
|
const getPersistentOptions = (persistentOptions, options) => {
|
|
107
110
|
const persistentOptionNames = new Set(persistentOptions.map((o) => (typeof o.name === "string" ? [o.name] : o.name)).flat());
|
|
108
111
|
return persistentOptions.concat((options ?? []).filter((o) => (typeof o.name == "string" ? !persistentOptionNames.has(o.name) : o.name.some((n) => !persistentOptionNames.has(n))) && o.isPersistent === true));
|
|
@@ -19,6 +19,7 @@ export class SuggestionManager {
|
|
|
19
19
|
#activeSuggestionIdx;
|
|
20
20
|
#suggestBlob;
|
|
21
21
|
#shell;
|
|
22
|
+
#hideSuggestions = false;
|
|
22
23
|
constructor(terminal, shell) {
|
|
23
24
|
this.#term = terminal;
|
|
24
25
|
this.#suggestBlob = { suggestions: [] };
|
|
@@ -28,7 +29,7 @@ export class SuggestionManager {
|
|
|
28
29
|
}
|
|
29
30
|
async _loadSuggestions() {
|
|
30
31
|
const commandText = this.#term.getCommandState().commandText;
|
|
31
|
-
if (!commandText) {
|
|
32
|
+
if (!commandText || this.#hideSuggestions) {
|
|
32
33
|
this.#suggestBlob = undefined;
|
|
33
34
|
this.#activeSuggestionIdx = 0;
|
|
34
35
|
return;
|
|
@@ -122,12 +123,20 @@ export class SuggestionManager {
|
|
|
122
123
|
}
|
|
123
124
|
update(keyPress) {
|
|
124
125
|
const { name, shift, ctrl } = keyPress;
|
|
126
|
+
if (name == "return") {
|
|
127
|
+
this.#term.clearCommand(); // clear the current command on enter
|
|
128
|
+
}
|
|
129
|
+
// if suggestions are hidden, keep them hidden until during command navigation
|
|
130
|
+
if (this.#hideSuggestions) {
|
|
131
|
+
this.#hideSuggestions = name == "up" || name == "down";
|
|
132
|
+
}
|
|
125
133
|
if (!this.#suggestBlob) {
|
|
126
134
|
return false;
|
|
127
135
|
}
|
|
128
136
|
const { dismissSuggestions: { key: dismissKey, shift: dismissShift, control: dismissCtrl }, acceptSuggestion: { key: acceptKey, shift: acceptShift, control: acceptCtrl }, nextSuggestion: { key: nextKey, shift: nextShift, control: nextCtrl }, previousSuggestion: { key: prevKey, shift: prevShift, control: prevCtrl }, } = getConfig().bindings;
|
|
129
137
|
if (name == dismissKey && shift == !!dismissShift && ctrl == !!dismissCtrl) {
|
|
130
138
|
this.#suggestBlob = undefined;
|
|
139
|
+
this.#hideSuggestions = true;
|
|
131
140
|
}
|
|
132
141
|
else if (name == prevKey && shift == !!prevShift && ctrl == !!prevCtrl) {
|
|
133
142
|
this.#activeSuggestionIdx = Math.max(0, this.#activeSuggestionIdx - 1);
|
package/build/ui/ui-root.js
CHANGED
|
@@ -12,8 +12,8 @@ export const renderConfirmation = (live) => {
|
|
|
12
12
|
const statusMessage = live ? chalk.green("live") : chalk.red("not found");
|
|
13
13
|
return `inshellisense session [${statusMessage}]\n`;
|
|
14
14
|
};
|
|
15
|
-
export const render = async (shell, underTest) => {
|
|
16
|
-
const term = await isterm.spawn({ shell, rows: process.stdout.rows, cols: process.stdout.columns, underTest });
|
|
15
|
+
export const render = async (shell, underTest, login) => {
|
|
16
|
+
const term = await isterm.spawn({ shell, rows: process.stdout.rows, cols: process.stdout.columns, underTest, login });
|
|
17
17
|
const suggestionManager = new SuggestionManager(term, shell);
|
|
18
18
|
let hasActiveSuggestions = false;
|
|
19
19
|
let previousSuggestionsRows = 0;
|
package/build/ui/utils.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// Copyright (c) Microsoft Corporation.
|
|
2
2
|
// Licensed under the MIT License.
|
|
3
3
|
import ansi from "ansi-escapes";
|
|
4
|
+
import { resetColor } from "../utils/ansi.js";
|
|
4
5
|
import wrapAnsi from "wrap-ansi";
|
|
5
6
|
import chalk from "chalk";
|
|
6
7
|
import wcwidth from "wcwidth";
|
|
@@ -12,7 +13,7 @@ import wcwidth from "wcwidth";
|
|
|
12
13
|
*/
|
|
13
14
|
export const renderBox = (rows, width, x, borderColor) => {
|
|
14
15
|
const result = [];
|
|
15
|
-
const setColor = (text) => (borderColor ? chalk.hex(borderColor).apply(text) : text);
|
|
16
|
+
const setColor = (text) => resetColor + (borderColor ? chalk.hex(borderColor).apply(text) : text);
|
|
16
17
|
result.push(ansi.cursorTo(x) + setColor("┌" + "─".repeat(width - 2) + "┐") + ansi.cursorTo(x));
|
|
17
18
|
rows.forEach((row) => {
|
|
18
19
|
result.push(ansi.cursorDown() + setColor("│") + row + setColor("│") + ansi.cursorTo(x));
|
package/build/utils/ansi.js
CHANGED
|
@@ -11,6 +11,7 @@ export var IstermOscPt;
|
|
|
11
11
|
IstermOscPt["PromptStarted"] = "PS";
|
|
12
12
|
IstermOscPt["PromptEnded"] = "PE";
|
|
13
13
|
IstermOscPt["CurrentWorkingDirectory"] = "CWD";
|
|
14
|
+
IstermOscPt["Prompt"] = "PROMPT";
|
|
14
15
|
})(IstermOscPt || (IstermOscPt = {}));
|
|
15
16
|
export const IstermPromptStart = IS_OSC + IstermOscPt.PromptStarted + BEL;
|
|
16
17
|
export const IstermPromptEnd = IS_OSC + IstermOscPt.PromptEnded + BEL;
|
|
@@ -18,7 +19,10 @@ export const cursorHide = CSI + "?25l";
|
|
|
18
19
|
export const cursorShow = CSI + "?25h";
|
|
19
20
|
export const cursorNextLine = CSI + "E";
|
|
20
21
|
export const eraseLine = CSI + "2K";
|
|
22
|
+
export const resetColor = CSI + "0m";
|
|
23
|
+
export const resetLine = CSI + "2K";
|
|
21
24
|
export const cursorBackward = (count = 1) => CSI + count + "D";
|
|
25
|
+
export const cursorForward = (count = 1) => CSI + count + "C";
|
|
22
26
|
export const cursorTo = ({ x, y }) => {
|
|
23
27
|
return CSI + (y ?? "") + ";" + (x ?? "") + "H";
|
|
24
28
|
};
|
package/build/utils/config.js
CHANGED
|
@@ -49,6 +49,7 @@ const configSchema = {
|
|
|
49
49
|
acceptSuggestion: bindingSchema,
|
|
50
50
|
},
|
|
51
51
|
},
|
|
52
|
+
// DEPRECATED: prompt patterns are no longer used
|
|
52
53
|
prompt: {
|
|
53
54
|
type: "object",
|
|
54
55
|
nullable: true,
|
|
@@ -58,6 +59,7 @@ const configSchema = {
|
|
|
58
59
|
powershell: promptPatternsSchema,
|
|
59
60
|
xonsh: promptPatternsSchema,
|
|
60
61
|
nu: promptPatternsSchema,
|
|
62
|
+
fish: promptPatternsSchema,
|
|
61
63
|
},
|
|
62
64
|
},
|
|
63
65
|
specs: {
|
|
@@ -70,9 +72,12 @@ const configSchema = {
|
|
|
70
72
|
},
|
|
71
73
|
additionalProperties: false,
|
|
72
74
|
};
|
|
73
|
-
const
|
|
75
|
+
const rcFile = ".inshellisenserc";
|
|
76
|
+
const xdgFile = "rc.toml";
|
|
74
77
|
const cachePath = path.join(os.homedir(), ".inshellisense");
|
|
75
|
-
const
|
|
78
|
+
const rcPath = path.join(os.homedir(), rcFile);
|
|
79
|
+
const xdgPath = path.join(os.homedir(), ".config", "inshellisense", xdgFile);
|
|
80
|
+
const configPaths = [rcPath, xdgPath];
|
|
76
81
|
let globalConfig = {
|
|
77
82
|
bindings: {
|
|
78
83
|
nextSuggestion: { key: "down" },
|
|
@@ -83,38 +88,42 @@ let globalConfig = {
|
|
|
83
88
|
};
|
|
84
89
|
export const getConfig = () => globalConfig;
|
|
85
90
|
export const loadConfig = async (program) => {
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
+
configPaths.forEach(async (configPath) => {
|
|
92
|
+
if (fs.existsSync(configPath)) {
|
|
93
|
+
let config;
|
|
94
|
+
try {
|
|
95
|
+
config = toml.parse((await fsAsync.readFile(configPath)).toString());
|
|
96
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
97
|
+
}
|
|
98
|
+
catch (e) {
|
|
99
|
+
program.error(`${configPath} is invalid toml. Parsing error on line ${e.line}, column ${e.column}: ${e.message}`);
|
|
100
|
+
}
|
|
101
|
+
const isValid = ajv.validate(configSchema, config);
|
|
102
|
+
if (!isValid) {
|
|
103
|
+
program.error(`${configPath} is invalid: ${ajv.errorsText()}`);
|
|
104
|
+
}
|
|
105
|
+
globalConfig = {
|
|
106
|
+
bindings: {
|
|
107
|
+
nextSuggestion: config?.bindings?.nextSuggestion ?? globalConfig.bindings.nextSuggestion,
|
|
108
|
+
previousSuggestion: config?.bindings?.previousSuggestion ?? globalConfig.bindings.previousSuggestion,
|
|
109
|
+
acceptSuggestion: config?.bindings?.acceptSuggestion ?? globalConfig.bindings.acceptSuggestion,
|
|
110
|
+
dismissSuggestions: config?.bindings?.dismissSuggestions ?? globalConfig.bindings.dismissSuggestions,
|
|
111
|
+
},
|
|
112
|
+
prompt: {
|
|
113
|
+
bash: config.prompt?.bash ?? globalConfig?.prompt?.bash,
|
|
114
|
+
powershell: config.prompt?.powershell ?? globalConfig?.prompt?.powershell,
|
|
115
|
+
xonsh: config.prompt?.xonsh ?? globalConfig?.prompt?.xonsh,
|
|
116
|
+
pwsh: config.prompt?.pwsh ?? globalConfig?.prompt?.pwsh,
|
|
117
|
+
nu: config.prompt?.nu ?? globalConfig?.prompt?.nu,
|
|
118
|
+
fish: config.prompt?.fish ?? globalConfig?.prompt?.fish,
|
|
119
|
+
},
|
|
120
|
+
specs: {
|
|
121
|
+
path: [...(config?.specs?.path ?? []), ...(config?.specs?.path ?? [])],
|
|
122
|
+
},
|
|
123
|
+
};
|
|
91
124
|
}
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
}
|
|
95
|
-
const isValid = ajv.validate(configSchema, config);
|
|
96
|
-
if (!isValid) {
|
|
97
|
-
program.error(`${configFile} is invalid: ${ajv.errorsText()}`);
|
|
98
|
-
}
|
|
99
|
-
globalConfig = {
|
|
100
|
-
bindings: {
|
|
101
|
-
nextSuggestion: config?.bindings?.nextSuggestion ?? globalConfig.bindings.nextSuggestion,
|
|
102
|
-
previousSuggestion: config?.bindings?.previousSuggestion ?? globalConfig.bindings.previousSuggestion,
|
|
103
|
-
acceptSuggestion: config?.bindings?.acceptSuggestion ?? globalConfig.bindings.acceptSuggestion,
|
|
104
|
-
dismissSuggestions: config?.bindings?.dismissSuggestions ?? globalConfig.bindings.dismissSuggestions,
|
|
105
|
-
},
|
|
106
|
-
prompt: {
|
|
107
|
-
bash: config.prompt?.bash,
|
|
108
|
-
powershell: config.prompt?.powershell,
|
|
109
|
-
xonsh: config.prompt?.xonsh,
|
|
110
|
-
pwsh: config.prompt?.pwsh,
|
|
111
|
-
nu: config.prompt?.nu,
|
|
112
|
-
},
|
|
113
|
-
specs: {
|
|
114
|
-
path: [`${os.homedir()}/.fig/autocomplete/build`, ...(config?.specs?.path ?? [])],
|
|
115
|
-
},
|
|
116
|
-
};
|
|
117
|
-
}
|
|
125
|
+
});
|
|
126
|
+
globalConfig.specs = { path: [`${os.homedir()}/.fig/autocomplete/build`, ...(globalConfig.specs?.path ?? [])] };
|
|
118
127
|
};
|
|
119
128
|
export const deleteCacheFolder = async () => {
|
|
120
129
|
const cliConfigPath = path.join(os.homedir(), cachePath);
|
package/build/utils/shell.js
CHANGED
|
@@ -30,6 +30,7 @@ export const supportedShells = [
|
|
|
30
30
|
Shell.Nushell,
|
|
31
31
|
].filter((shell) => shell != null);
|
|
32
32
|
export const initSupportedShells = supportedShells.filter((shell) => shell != Shell.Cmd);
|
|
33
|
+
export const aliasSupportedShells = [Shell.Bash, Shell.Zsh];
|
|
33
34
|
export const userZdotdir = process.env?.ZDOTDIR ?? os.homedir() ?? `~`;
|
|
34
35
|
export const zdotdir = path.join(os.tmpdir(), `is-zsh`);
|
|
35
36
|
const configFolder = ".inshellisense";
|
|
@@ -49,6 +50,23 @@ export const setupZshDotfiles = async () => {
|
|
|
49
50
|
await fsAsync.cp(path.join(shellFolderPath, "shellIntegration-login.zsh"), path.join(zdotdir, ".zlogin"));
|
|
50
51
|
};
|
|
51
52
|
export const inferShell = async () => {
|
|
53
|
+
// try getting shell from shell specific env variables
|
|
54
|
+
if (process.env.NU_VERSION != null) {
|
|
55
|
+
return Shell.Nushell;
|
|
56
|
+
}
|
|
57
|
+
else if (process.env.XONSHRC != null) {
|
|
58
|
+
return Shell.Xonsh;
|
|
59
|
+
}
|
|
60
|
+
else if (process.env.FISH_VERSION != null) {
|
|
61
|
+
return Shell.Fish;
|
|
62
|
+
}
|
|
63
|
+
else if (process.env.ZSH_VERSION != null) {
|
|
64
|
+
return Shell.Zsh;
|
|
65
|
+
}
|
|
66
|
+
else if (process.env.BASH_VERSION != null) {
|
|
67
|
+
return Shell.Bash;
|
|
68
|
+
}
|
|
69
|
+
// try getting shell from env
|
|
52
70
|
try {
|
|
53
71
|
const name = path.parse(process.env.SHELL ?? "").name;
|
|
54
72
|
const shellName = supportedShells.find((shell) => name.includes(shell));
|
|
@@ -58,6 +76,7 @@ export const inferShell = async () => {
|
|
|
58
76
|
catch {
|
|
59
77
|
/* empty */
|
|
60
78
|
}
|
|
79
|
+
// try getting shell from parent process
|
|
61
80
|
const processResult = (await find("pid", process.ppid)).at(0);
|
|
62
81
|
const name = processResult?.name;
|
|
63
82
|
return name != null ? supportedShells.find((shell) => name.includes(shell)) : undefined;
|
|
@@ -103,9 +122,22 @@ export const getShellPromptRewrites = (shell) => shell == Shell.Nushell;
|
|
|
103
122
|
export const getShellConfig = (shell) => {
|
|
104
123
|
switch (shell) {
|
|
105
124
|
case Shell.Zsh:
|
|
125
|
+
return `if [[ -z "\${ISTERM}" && $- = *i* && $- != *c* ]]; then
|
|
126
|
+
if [[ -o login ]]; then
|
|
127
|
+
is -s zsh --login ; exit
|
|
128
|
+
else
|
|
129
|
+
is -s zsh ; exit
|
|
130
|
+
fi
|
|
131
|
+
fi`;
|
|
106
132
|
case Shell.Bash:
|
|
107
133
|
return `if [[ -z "\${ISTERM}" && $- = *i* && $- != *c* ]]; then
|
|
108
|
-
|
|
134
|
+
shopt -q login_shell
|
|
135
|
+
login_shell=$?
|
|
136
|
+
if [ $login_shell -eq 0 ]; then
|
|
137
|
+
is -s bash --login ; exit
|
|
138
|
+
else
|
|
139
|
+
is -s bash ; exit
|
|
140
|
+
fi
|
|
109
141
|
fi`;
|
|
110
142
|
case Shell.Powershell:
|
|
111
143
|
case Shell.Pwsh:
|
|
@@ -118,14 +150,21 @@ if ([string]::IsNullOrEmpty($env:ISTERM) -and [Environment]::UserInteractive -an
|
|
|
118
150
|
}`;
|
|
119
151
|
case Shell.Fish:
|
|
120
152
|
return `if test -z "$ISTERM" && status --is-interactive
|
|
121
|
-
is
|
|
153
|
+
if status --is-login
|
|
154
|
+
is -s fish --login ; kill %self
|
|
155
|
+
else
|
|
156
|
+
is -s fish ; kill %self
|
|
157
|
+
end
|
|
122
158
|
end`;
|
|
123
159
|
case Shell.Xonsh:
|
|
124
160
|
return `if 'ISTERM' not in \${...} and $XONSH_INTERACTIVE:
|
|
125
|
-
|
|
161
|
+
if $XONSH_LOGIN:
|
|
162
|
+
is -s xonsh --login ; exit
|
|
163
|
+
else:
|
|
164
|
+
is -s xonsh ; exit`;
|
|
126
165
|
case Shell.Nushell:
|
|
127
166
|
return `if "ISTERM" not-in $env and $nu.is-interactive {
|
|
128
|
-
is -s nu ; exit
|
|
167
|
+
if $nu.is-login { is -s nu --login ; exit } else { is -s nu ; exit }
|
|
129
168
|
}`;
|
|
130
169
|
}
|
|
131
170
|
return "";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@microsoft/inshellisense",
|
|
3
|
-
"version": "0.0.1-rc.
|
|
3
|
+
"version": "0.0.1-rc.15",
|
|
4
4
|
"description": "IDE style command line auto complete",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"engines": {
|
|
@@ -41,6 +41,7 @@
|
|
|
41
41
|
"dependencies": {
|
|
42
42
|
"@homebridge/node-pty-prebuilt-multiarch": "^0.11.12",
|
|
43
43
|
"@withfig/autocomplete": "2.651.0",
|
|
44
|
+
"@xterm/headless": "^5.5.0",
|
|
44
45
|
"ajv": "^8.12.0",
|
|
45
46
|
"ansi-escapes": "^6.2.0",
|
|
46
47
|
"ansi-styles": "^6.2.1",
|
|
@@ -48,11 +49,11 @@
|
|
|
48
49
|
"color-convert": "^2.0.1",
|
|
49
50
|
"commander": "^11.0.0",
|
|
50
51
|
"find-process": "^1.4.7",
|
|
52
|
+
"strip-ansi": "^7.1.0",
|
|
51
53
|
"toml": "^3.0.0",
|
|
52
54
|
"wcwidth": "^1.0.1",
|
|
53
55
|
"which": "^4.0.0",
|
|
54
|
-
"wrap-ansi": "^8.1.0"
|
|
55
|
-
"@xterm/headless": "^5.3.0"
|
|
56
|
+
"wrap-ansi": "^8.1.0"
|
|
56
57
|
},
|
|
57
58
|
"devDependencies": {
|
|
58
59
|
"@microsoft/tui-test": "^0.0.1-rc.3",
|
|
@@ -65,6 +66,7 @@
|
|
|
65
66
|
"@typescript-eslint/eslint-plugin": "^6.7.4",
|
|
66
67
|
"@typescript-eslint/parser": "^6.7.4",
|
|
67
68
|
"@withfig/autocomplete-types": "^1.28.0",
|
|
69
|
+
"@xterm/xterm": "^5.5.0",
|
|
68
70
|
"eslint": "^8.51.0",
|
|
69
71
|
"eslint-config-prettier": "^9.0.0",
|
|
70
72
|
"eslint-plugin-header": "^3.1.1",
|
|
@@ -1,5 +1,19 @@
|
|
|
1
|
-
if [ -
|
|
2
|
-
|
|
1
|
+
if [ -z "$ISTERM_LOGIN" ]; then
|
|
2
|
+
if [ -r ~/.bashrc ]; then
|
|
3
|
+
. ~/.bashrc
|
|
4
|
+
fi
|
|
5
|
+
else
|
|
6
|
+
if [ -r /etc/profile ]; then
|
|
7
|
+
. /etc/profile
|
|
8
|
+
fi
|
|
9
|
+
# execute the first that exists
|
|
10
|
+
if [ -r ~/.bash_profile ]; then
|
|
11
|
+
. ~/.bash_profile
|
|
12
|
+
elif [ -r ~/.bash_login ]; then
|
|
13
|
+
. ~/.bash_login
|
|
14
|
+
elif [ -r ~/.profile ]; then
|
|
15
|
+
. ~/.profile
|
|
16
|
+
fi
|
|
3
17
|
fi
|
|
4
18
|
|
|
5
19
|
if [ -r ~/.inshellisense/bash-preexec.sh ]; then
|
|
@@ -26,6 +40,10 @@ __is_escape_value() {
|
|
|
26
40
|
token="\\\\"
|
|
27
41
|
elif [ "$byte" = ";" ]; then
|
|
28
42
|
token="\\x3b"
|
|
43
|
+
elif [ "$byte" = $'\n' ]; then
|
|
44
|
+
token="\x0a"
|
|
45
|
+
elif [ "$byte" = $'\e' ]; then
|
|
46
|
+
token="\\x1b"
|
|
29
47
|
else
|
|
30
48
|
token="$byte"
|
|
31
49
|
fi
|
|
@@ -40,6 +58,16 @@ __is_update_cwd() {
|
|
|
40
58
|
builtin printf '\e]6973;CWD;%s\a' "$(__is_escape_value "$PWD")"
|
|
41
59
|
}
|
|
42
60
|
|
|
61
|
+
__is_report_prompt() {
|
|
62
|
+
if ((BASH_VERSINFO[0] >= 4)); then
|
|
63
|
+
__is_prompt=${__is_original_PS1@P}
|
|
64
|
+
else
|
|
65
|
+
__is_prompt=${__is_original_PS1}
|
|
66
|
+
fi
|
|
67
|
+
__is_prompt="$(builtin printf "%s" "${__is_prompt//[$'\001'$'\002']}")"
|
|
68
|
+
builtin printf "\e]6973;PROMPT;%s\a" "$(__is_escape_value "${__is_prompt}")"
|
|
69
|
+
}
|
|
70
|
+
|
|
43
71
|
if [[ -n "${bash_preexec_imported:-}" ]]; then
|
|
44
72
|
precmd_functions+=(__is_precmd)
|
|
45
73
|
fi
|
|
@@ -47,6 +75,7 @@ fi
|
|
|
47
75
|
__is_precmd() {
|
|
48
76
|
__is_update_cwd
|
|
49
77
|
__is_update_prompt
|
|
78
|
+
__is_report_prompt
|
|
50
79
|
}
|
|
51
80
|
|
|
52
81
|
__is_update_prompt() {
|
|
@@ -2,15 +2,18 @@ function __is_copy_function; functions $argv[1] | sed "s/^function $argv[1]/func
|
|
|
2
2
|
function __is_prompt_start; printf '\e]6973;PS\a'; end
|
|
3
3
|
function __is_prompt_end; printf '\e]6973;PE\a'; end
|
|
4
4
|
|
|
5
|
+
__is_copy_function fish_prompt is_user_prompt
|
|
6
|
+
|
|
5
7
|
function __is_escape_value
|
|
6
8
|
echo $argv \
|
|
7
|
-
| string replace
|
|
8
|
-
| string replace
|
|
9
|
+
| string replace -a '\\' '\\\\' \
|
|
10
|
+
| string replace -a ';' '\\x3b' \
|
|
11
|
+
| string replace -a \e '\\x1b' \
|
|
12
|
+
| string split \n | string join '\x0a' \
|
|
9
13
|
;
|
|
10
14
|
end
|
|
11
|
-
function __is_update_cwd --on-event fish_prompt; set __is_cwd (__is_escape_value "$PWD"); printf "\e]6973;CWD
|
|
12
|
-
|
|
13
|
-
__is_copy_function fish_prompt is_user_prompt
|
|
15
|
+
function __is_update_cwd --on-event fish_prompt; set __is_cwd (__is_escape_value "$PWD"); printf "\e]6973;CWD;%s\a" $__is_cwd; end
|
|
16
|
+
function __is_report_prompt --on-event fish_prompt; set __is_prompt (__is_escape_value (is_user_prompt)); printf "\e]6973;PROMPT;%s\a" $__is_prompt; end
|
|
14
17
|
|
|
15
18
|
if [ "$ISTERM_TESTING" = "1" ]
|
|
16
19
|
function is_user_prompt; printf '> '; end
|
|
@@ -1,18 +1,26 @@
|
|
|
1
|
-
let __is_escape_value = {|x| $x | str replace --all "\\" "\\\\" | str replace --all ";" "\\x3b" }
|
|
1
|
+
let __is_escape_value = {|x| $x | str replace --all "\\" "\\\\" | str replace --all ";" "\\x3b" | str replace --all "\n" '\x0a' | str replace --all "\e" "\\x1b" }
|
|
2
|
+
let __is_original_PROMPT_COMMAND = if 'PROMPT_COMMAND' in $env { $env.PROMPT_COMMAND } else { "" }
|
|
3
|
+
let __is_original_PROMPT_INDICATOR = if 'PROMPT_INDICATOR' in $env { $env.PROMPT_INDICATOR } else { "" }
|
|
2
4
|
|
|
3
5
|
let __is_update_cwd = {
|
|
4
6
|
let pwd = do $__is_escape_value $env.PWD
|
|
5
7
|
$"\e]6973;CWD;($pwd)\a"
|
|
6
8
|
}
|
|
7
|
-
let
|
|
9
|
+
let __is_report_prompt = {
|
|
10
|
+
let __is_indicatorCommandType = $__is_original_PROMPT_INDICATOR | describe
|
|
11
|
+
mut __is_prompt_ind = if $__is_indicatorCommandType == "closure" { do $__is_original_PROMPT_INDICATOR } else { $__is_original_PROMPT_INDICATOR }
|
|
12
|
+
let __is_esc_prompt_ind = do $__is_escape_value $__is_prompt_ind
|
|
13
|
+
$"\e]6973;PROMPT;($__is_esc_prompt_ind)\a"
|
|
14
|
+
}
|
|
8
15
|
let __is_custom_PROMPT_COMMAND = {
|
|
9
16
|
let promptCommandType = $__is_original_PROMPT_COMMAND | describe
|
|
10
17
|
mut cmd = if $promptCommandType == "closure" { do $__is_original_PROMPT_COMMAND } else { $__is_original_PROMPT_COMMAND }
|
|
11
18
|
let pwd = do $__is_update_cwd
|
|
19
|
+
let prompt = do $__is_report_prompt
|
|
12
20
|
if 'ISTERM_TESTING' in $env {
|
|
13
21
|
$cmd = ""
|
|
14
22
|
}
|
|
15
|
-
$"\e]6973;PS\a($cmd)($pwd)"
|
|
23
|
+
$"\e]6973;PS\a($cmd)($pwd)($prompt)"
|
|
16
24
|
}
|
|
17
25
|
$env.PROMPT_COMMAND = $__is_custom_PROMPT_COMMAND
|
|
18
26
|
|
|
@@ -8,7 +8,7 @@ if ($env:ISTERM_TESTING -eq "1") {
|
|
|
8
8
|
}
|
|
9
9
|
|
|
10
10
|
function Global:__IS-Escape-Value([string]$value) {
|
|
11
|
-
[regex]::Replace($value,
|
|
11
|
+
[regex]::Replace($value, "[$([char]0x1b)\\\n;]", { param($match)
|
|
12
12
|
-Join (
|
|
13
13
|
[System.Text.Encoding]::UTF8.GetBytes($match.Value) | ForEach-Object { '\x{0:x2}' -f $_ }
|
|
14
14
|
)
|
|
@@ -17,8 +17,11 @@ function Global:__IS-Escape-Value([string]$value) {
|
|
|
17
17
|
|
|
18
18
|
function Global:Prompt() {
|
|
19
19
|
$Result = "$([char]0x1b)]6973;PS`a"
|
|
20
|
-
$
|
|
20
|
+
$OriginalPrompt += $Global:__IsOriginalPrompt.Invoke()
|
|
21
|
+
$Result += $OriginalPrompt
|
|
21
22
|
$Result += "$([char]0x1b)]6973;PE`a"
|
|
23
|
+
|
|
24
|
+
$Result += "$([char]0x1b)]6973;PROMPT;$(__IS-Escape-Value $OriginalPrompt)`a"
|
|
22
25
|
$Result += if ($pwd.Provider.Name -eq 'FileSystem') { "$([char]0x1b)]6973;CWD;$(__IS-Escape-Value $pwd.ProviderPath)`a" }
|
|
23
26
|
return $Result
|
|
24
27
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import os
|
|
2
|
+
from xonsh.main import XSH
|
|
2
3
|
|
|
3
4
|
def __is_prompt_start() -> str:
|
|
4
5
|
return "\001" + "\x1b]6973;PS\x07"
|
|
@@ -7,23 +8,30 @@ def __is_prompt_start() -> str:
|
|
|
7
8
|
def __is_prompt_end() -> str:
|
|
8
9
|
return "\001" + "\x1b]6973;PE\x07" + "\002"
|
|
9
10
|
|
|
10
|
-
|
|
11
11
|
def __is_escape_value(value: str) -> str:
|
|
12
12
|
byte_list = [bytes([byte]).decode("utf-8") for byte in list(value.encode("utf-8"))]
|
|
13
13
|
return "".join(
|
|
14
14
|
[
|
|
15
|
-
"\\x3b" if byte == ";" else "\\\\" if byte == "\\" else byte
|
|
15
|
+
"\\x3b" if byte == ";" else "\\\\" if byte == "\\" else "\\x1b" if byte == "\x1b" else "\x0a" if byte == "\n" else byte
|
|
16
16
|
for byte in byte_list
|
|
17
17
|
]
|
|
18
18
|
)
|
|
19
19
|
|
|
20
20
|
def __is_update_cwd() -> str:
|
|
21
|
-
return f"\x1b]6973;CWD;{__is_escape_value(os.getcwd())}\x07"
|
|
21
|
+
return f"\x1b]6973;CWD;{__is_escape_value(os.getcwd())}\x07"
|
|
22
|
+
|
|
23
|
+
__is_original_prompt = $PROMPT
|
|
24
|
+
def __is_report_prompt() -> str:
|
|
25
|
+
prompt = ""
|
|
26
|
+
formatted_prompt = XSH.shell.prompt_formatter(__is_original_prompt)
|
|
27
|
+
prompt = "".join([text for _, text in XSH.shell.format_color(formatted_prompt)])
|
|
28
|
+
return f"\x1b]6973;PROMPT;{__is_escape_value(prompt)}\x07" + "\002"
|
|
22
29
|
|
|
23
30
|
$PROMPT_FIELDS['__is_prompt_start'] = __is_prompt_start
|
|
24
31
|
$PROMPT_FIELDS['__is_prompt_end'] = __is_prompt_end
|
|
25
32
|
$PROMPT_FIELDS['__is_update_cwd'] = __is_update_cwd
|
|
33
|
+
$PROMPT_FIELDS['__is_report_prompt'] = __is_report_prompt
|
|
26
34
|
if 'ISTERM_TESTING' in ${...}:
|
|
27
35
|
$PROMPT = "> "
|
|
28
36
|
|
|
29
|
-
$PROMPT = "{__is_prompt_start}{__is_update_cwd}" + $PROMPT + "{__is_prompt_end}"
|
|
37
|
+
$PROMPT = "{__is_prompt_start}{__is_update_cwd}{__is_report_prompt}" + $PROMPT + "{__is_prompt_end}"
|