@microsoft/inshellisense 0.0.1-rc.14 → 0.0.1-rc.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -21
- package/README.md +3 -13
- package/SECURITY.md +41 -41
- package/build/commands/shellenv.js +18 -0
- package/build/isterm/commandManager.js +99 -96
- package/build/isterm/pty.js +31 -5
- package/build/runtime/suggestion.js +4 -4
- package/build/runtime/utils.js +2 -2
- package/build/ui/suggestionManager.js +7 -1
- 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 +28 -1
- package/package.json +3 -1
- package/shell/shellIntegration-env.zsh +5 -2
- package/shell/shellIntegration-rc.zsh +8 -0
- package/shell/shellIntegration.bash +17 -0
- package/shell/shellIntegration.fish +9 -5
- package/shell/shellIntegration.nu +11 -3
- package/shell/shellIntegration.ps1 +5 -2
- package/shell/shellIntegration.xsh +12 -4
- package/todo.md +4 -0
package/LICENSE
CHANGED
|
@@ -1,21 +1,21 @@
|
|
|
1
|
-
MIT License
|
|
2
|
-
|
|
3
|
-
Copyright (c) Microsoft Corporation.
|
|
4
|
-
|
|
5
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
-
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
-
in the Software without restriction, including without limitation the rights
|
|
8
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
-
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
-
furnished to do so, subject to the following conditions:
|
|
11
|
-
|
|
12
|
-
The above copyright notice and this permission notice shall be included in all
|
|
13
|
-
copies or substantial portions of the Software.
|
|
14
|
-
|
|
15
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
-
SOFTWARE
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) Microsoft Corporation.
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE
|
package/README.md
CHANGED
|
@@ -47,6 +47,8 @@ is init xonsh >> ~/.xonshrc
|
|
|
47
47
|
is init nu >> $nu.env-path
|
|
48
48
|
```
|
|
49
49
|
|
|
50
|
+
When updating your shell configuration in the future, make sure the inshellisense plugin is the last command in the file.
|
|
51
|
+
|
|
50
52
|
### Usage
|
|
51
53
|
|
|
52
54
|
| Action | Command | Description |
|
|
@@ -81,7 +83,7 @@ inshellisense supports the following shells:
|
|
|
81
83
|
|
|
82
84
|
## Configuration
|
|
83
85
|
|
|
84
|
-
All configuration is done through a [toml](https://toml.io/) file
|
|
86
|
+
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
87
|
|
|
86
88
|
### Keybindings
|
|
87
89
|
|
|
@@ -106,18 +108,6 @@ key = "escape"
|
|
|
106
108
|
|
|
107
109
|
Key names are matched against the Node.js [keypress](https://nodejs.org/api/readline.html#readlineemitkeypresseventsstream-interface) events.
|
|
108
110
|
|
|
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
111
|
## Contributing
|
|
122
112
|
|
|
123
113
|
This project welcomes contributions and suggestions. Most contributions require you to agree to a
|
package/SECURITY.md
CHANGED
|
@@ -1,41 +1,41 @@
|
|
|
1
|
-
<!-- BEGIN MICROSOFT SECURITY.MD V0.0.9 BLOCK -->
|
|
2
|
-
|
|
3
|
-
## Security
|
|
4
|
-
|
|
5
|
-
Microsoft takes the security of our software products and services seriously, which includes all source code repositories managed through our GitHub organizations, which include [Microsoft](https://github.com/Microsoft), [Azure](https://github.com/Azure), [DotNet](https://github.com/dotnet), [AspNet](https://github.com/aspnet) and [Xamarin](https://github.com/xamarin).
|
|
6
|
-
|
|
7
|
-
If you believe you have found a security vulnerability in any Microsoft-owned repository that meets [Microsoft's definition of a security vulnerability](https://aka.ms/security.md/definition), please report it to us as described below.
|
|
8
|
-
|
|
9
|
-
## Reporting Security Issues
|
|
10
|
-
|
|
11
|
-
**Please do not report security vulnerabilities through public GitHub issues.**
|
|
12
|
-
|
|
13
|
-
Instead, please report them to the Microsoft Security Response Center (MSRC) at [https://msrc.microsoft.com/create-report](https://aka.ms/security.md/msrc/create-report).
|
|
14
|
-
|
|
15
|
-
If you prefer to submit without logging in, send email to [secure@microsoft.com](mailto:secure@microsoft.com). If possible, encrypt your message with our PGP key; please download it from the [Microsoft Security Response Center PGP Key page](https://aka.ms/security.md/msrc/pgp).
|
|
16
|
-
|
|
17
|
-
You should receive a response within 24 hours. If for some reason you do not, please follow up via email to ensure we received your original message. Additional information can be found at [microsoft.com/msrc](https://www.microsoft.com/msrc).
|
|
18
|
-
|
|
19
|
-
Please include the requested information listed below (as much as you can provide) to help us better understand the nature and scope of the possible issue:
|
|
20
|
-
|
|
21
|
-
* Type of issue (e.g. buffer overflow, SQL injection, cross-site scripting, etc.)
|
|
22
|
-
* Full paths of source file(s) related to the manifestation of the issue
|
|
23
|
-
* The location of the affected source code (tag/branch/commit or direct URL)
|
|
24
|
-
* Any special configuration required to reproduce the issue
|
|
25
|
-
* Step-by-step instructions to reproduce the issue
|
|
26
|
-
* Proof-of-concept or exploit code (if possible)
|
|
27
|
-
* Impact of the issue, including how an attacker might exploit the issue
|
|
28
|
-
|
|
29
|
-
This information will help us triage your report more quickly.
|
|
30
|
-
|
|
31
|
-
If you are reporting for a bug bounty, more complete reports can contribute to a higher bounty award. Please visit our [Microsoft Bug Bounty Program](https://aka.ms/security.md/msrc/bounty) page for more details about our active programs.
|
|
32
|
-
|
|
33
|
-
## Preferred Languages
|
|
34
|
-
|
|
35
|
-
We prefer all communications to be in English.
|
|
36
|
-
|
|
37
|
-
## Policy
|
|
38
|
-
|
|
39
|
-
Microsoft follows the principle of [Coordinated Vulnerability Disclosure](https://aka.ms/security.md/cvd).
|
|
40
|
-
|
|
41
|
-
<!-- END MICROSOFT SECURITY.MD BLOCK -->
|
|
1
|
+
<!-- BEGIN MICROSOFT SECURITY.MD V0.0.9 BLOCK -->
|
|
2
|
+
|
|
3
|
+
## Security
|
|
4
|
+
|
|
5
|
+
Microsoft takes the security of our software products and services seriously, which includes all source code repositories managed through our GitHub organizations, which include [Microsoft](https://github.com/Microsoft), [Azure](https://github.com/Azure), [DotNet](https://github.com/dotnet), [AspNet](https://github.com/aspnet) and [Xamarin](https://github.com/xamarin).
|
|
6
|
+
|
|
7
|
+
If you believe you have found a security vulnerability in any Microsoft-owned repository that meets [Microsoft's definition of a security vulnerability](https://aka.ms/security.md/definition), please report it to us as described below.
|
|
8
|
+
|
|
9
|
+
## Reporting Security Issues
|
|
10
|
+
|
|
11
|
+
**Please do not report security vulnerabilities through public GitHub issues.**
|
|
12
|
+
|
|
13
|
+
Instead, please report them to the Microsoft Security Response Center (MSRC) at [https://msrc.microsoft.com/create-report](https://aka.ms/security.md/msrc/create-report).
|
|
14
|
+
|
|
15
|
+
If you prefer to submit without logging in, send email to [secure@microsoft.com](mailto:secure@microsoft.com). If possible, encrypt your message with our PGP key; please download it from the [Microsoft Security Response Center PGP Key page](https://aka.ms/security.md/msrc/pgp).
|
|
16
|
+
|
|
17
|
+
You should receive a response within 24 hours. If for some reason you do not, please follow up via email to ensure we received your original message. Additional information can be found at [microsoft.com/msrc](https://www.microsoft.com/msrc).
|
|
18
|
+
|
|
19
|
+
Please include the requested information listed below (as much as you can provide) to help us better understand the nature and scope of the possible issue:
|
|
20
|
+
|
|
21
|
+
* Type of issue (e.g. buffer overflow, SQL injection, cross-site scripting, etc.)
|
|
22
|
+
* Full paths of source file(s) related to the manifestation of the issue
|
|
23
|
+
* The location of the affected source code (tag/branch/commit or direct URL)
|
|
24
|
+
* Any special configuration required to reproduce the issue
|
|
25
|
+
* Step-by-step instructions to reproduce the issue
|
|
26
|
+
* Proof-of-concept or exploit code (if possible)
|
|
27
|
+
* Impact of the issue, including how an attacker might exploit the issue
|
|
28
|
+
|
|
29
|
+
This information will help us triage your report more quickly.
|
|
30
|
+
|
|
31
|
+
If you are reporting for a bug bounty, more complete reports can contribute to a higher bounty award. Please visit our [Microsoft Bug Bounty Program](https://aka.ms/security.md/msrc/bounty) page for more details about our active programs.
|
|
32
|
+
|
|
33
|
+
## Preferred Languages
|
|
34
|
+
|
|
35
|
+
We prefer all communications to be in English.
|
|
36
|
+
|
|
37
|
+
## Policy
|
|
38
|
+
|
|
39
|
+
Microsoft follows the principle of [Coordinated Vulnerability Disclosure](https://aka.ms/security.md/cvd).
|
|
40
|
+
|
|
41
|
+
<!-- END MICROSOFT SECURITY.MD BLOCK -->
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
// Copyright (c) Microsoft Corporation.
|
|
2
|
+
// Licensed under the MIT License.
|
|
3
|
+
import { Command } from "commander";
|
|
4
|
+
import { shellEnvSupportedShells as shells, getShellConfig } from "../utils/shell.js";
|
|
5
|
+
const supportedShells = shells.join(", ");
|
|
6
|
+
const action = (program) => async (shell) => {
|
|
7
|
+
if (!shells.map((s) => s.valueOf()).includes(shell)) {
|
|
8
|
+
program.error(`Unsupported shell: '${shell}', supported shells: ${supportedShells}`, { exitCode: 1 });
|
|
9
|
+
}
|
|
10
|
+
const config = getShellConfig(shell);
|
|
11
|
+
process.stdout.write(`\n\n# ---------------- inshellisense shell plugin ----------------\n${config}`);
|
|
12
|
+
process.exit(0);
|
|
13
|
+
};
|
|
14
|
+
const cmd = new Command("shellenv");
|
|
15
|
+
cmd.description(`generates shell configurations for the provided shell`);
|
|
16
|
+
cmd.argument("<shell>", `shell to generate configuration for, supported shells: ${supportedShells}`);
|
|
17
|
+
cmd.action(action(cmd));
|
|
18
|
+
export default cmd;
|
|
@@ -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, ">");
|
|
@@ -170,12 +154,9 @@ export class CommandManager {
|
|
|
170
154
|
const dim = (cell?.isDim() ?? 0) > 0;
|
|
171
155
|
const italic = (cell?.isItalic() ?? 0) > 0;
|
|
172
156
|
const dullColor = color == 8 || color == 7 || (color ?? 0) > 235 || (color == 15 && dim);
|
|
173
|
-
const
|
|
174
|
-
if (this.#shell == Shell.Powershell) {
|
|
175
|
-
return
|
|
176
|
-
}
|
|
177
|
-
else if (this.#shell == Shell.Pwsh) {
|
|
178
|
-
return dullItalic;
|
|
157
|
+
const dimItalic = dim || italic;
|
|
158
|
+
if (this.#shell == Shell.Pwsh || this.#shell == Shell.Powershell) {
|
|
159
|
+
return dimItalic;
|
|
179
160
|
}
|
|
180
161
|
return dullColor;
|
|
181
162
|
}
|
|
@@ -191,13 +172,78 @@ export class CommandManager {
|
|
|
191
172
|
clearActiveCommand() {
|
|
192
173
|
this.#activeCommand = {};
|
|
193
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
|
+
}
|
|
194
239
|
termSync() {
|
|
195
240
|
if (this.#activeCommand.promptEndMarker == null || this.#activeCommand.promptStartMarker == null) {
|
|
196
241
|
return;
|
|
197
242
|
}
|
|
198
243
|
const globalCursorPosition = this.#terminal.buffer.active.baseY + this.#terminal.buffer.active.cursorY;
|
|
199
244
|
const withinPollDistance = globalCursorPosition < this.#activeCommand.promptEndMarker.line + 5;
|
|
200
|
-
|
|
245
|
+
this.#maxCursorY = Math.max(this.#maxCursorY, globalCursorPosition);
|
|
246
|
+
if (globalCursorPosition < this.#activeCommand.promptStartMarker.line || globalCursorPosition < this.#maxCursorY) {
|
|
201
247
|
this.handleClear();
|
|
202
248
|
this.#activeCommand.promptEndMarker = this.#terminal.registerMarker(0);
|
|
203
249
|
return;
|
|
@@ -221,60 +267,15 @@ export class CommandManager {
|
|
|
221
267
|
}
|
|
222
268
|
// if the prompt is set, now parse out the values from the terminal
|
|
223
269
|
if (this.#activeCommand.promptText != null) {
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
let isWrapped = false;
|
|
230
|
-
for (; lineY < this.#terminal.buffer.active.baseY + this.#terminal.rows;) {
|
|
231
|
-
for (let i = lineY == this.#activeCommand.promptEndMarker.line ? this.#activeCommand.promptText.length : 0; i < this.#terminal.cols; i++) {
|
|
232
|
-
if (command.endsWith(" "))
|
|
233
|
-
break; // assume that a command that ends with 4 spaces is terminated, avoids capturing right prompts
|
|
234
|
-
const cell = line?.getCell(i);
|
|
235
|
-
if (cell == null)
|
|
236
|
-
continue;
|
|
237
|
-
const chars = cell.getChars();
|
|
238
|
-
const cleanedChars = chars == "" ? " " : chars;
|
|
239
|
-
if (!this._isSuggestion(cell) && suggestions.length == 0) {
|
|
240
|
-
command += cleanedChars;
|
|
241
|
-
wrappedCommand += cleanedChars;
|
|
242
|
-
}
|
|
243
|
-
else {
|
|
244
|
-
suggestions += cleanedChars;
|
|
245
|
-
}
|
|
246
|
-
}
|
|
247
|
-
lineY += 1;
|
|
248
|
-
line = this.#terminal.buffer.active.getLine(lineY);
|
|
249
|
-
const wrapped = line?.isWrapped || this.#terminal.buffer.active.cursorY + this.#terminal.buffer.active.baseY != lineY - 1;
|
|
250
|
-
isWrapped = isWrapped || wrapped;
|
|
251
|
-
if (!wrapped) {
|
|
252
|
-
break;
|
|
253
|
-
}
|
|
254
|
-
wrappedCommand = "";
|
|
255
|
-
}
|
|
256
|
-
const cursorAtEndOfInput = isWrapped
|
|
257
|
-
? wrappedCommand.trim().length % this.#terminal.cols <= this.#terminal.buffer.active.cursorX
|
|
258
|
-
: (this.#activeCommand.promptText.length + command.trimEnd().length) % this.#terminal.cols <= this.#terminal.buffer.active.cursorX;
|
|
259
|
-
let hasOutput = false;
|
|
260
|
-
let cell = undefined;
|
|
261
|
-
for (let i = 0; i < this.#terminal.cols; i++) {
|
|
262
|
-
cell = line?.getCell(i, cell);
|
|
263
|
-
if (cell == null)
|
|
264
|
-
continue;
|
|
265
|
-
hasOutput = cell.getChars() != "";
|
|
266
|
-
if (hasOutput) {
|
|
267
|
-
break;
|
|
268
|
-
}
|
|
269
|
-
}
|
|
270
|
-
const postfixActive = isWrapped
|
|
271
|
-
? wrappedCommand.trim().length < this.#terminal.buffer.active.cursorX
|
|
272
|
-
: this.#activeCommand.promptText.length + command.trimEnd().length < this.#terminal.buffer.active.cursorX;
|
|
273
|
-
const commandPostfix = postfixActive ? " " : "";
|
|
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);
|
|
274
275
|
this.#activeCommand.persistentOutput = this.#activeCommand.hasOutput && hasOutput;
|
|
275
276
|
this.#activeCommand.hasOutput = hasOutput;
|
|
276
|
-
this.#activeCommand.suggestionsText =
|
|
277
|
-
this.#activeCommand.commandText = command
|
|
277
|
+
this.#activeCommand.suggestionsText = suggestion;
|
|
278
|
+
this.#activeCommand.commandText = command;
|
|
278
279
|
this.#activeCommand.cursorTerminated = cursorAtEndOfInput;
|
|
279
280
|
}
|
|
280
281
|
log.debug({
|
|
@@ -282,6 +283,8 @@ export class CommandManager {
|
|
|
282
283
|
...this.#activeCommand,
|
|
283
284
|
promptEndMarker: this.#activeCommand.promptEndMarker?.line,
|
|
284
285
|
promptStartMarker: this.#activeCommand.promptStartMarker?.line,
|
|
286
|
+
cursorX: this.#terminal.buffer.active.cursorX,
|
|
287
|
+
cursorY: globalCursorPosition,
|
|
285
288
|
});
|
|
286
289
|
}
|
|
287
290
|
}
|
package/build/isterm/pty.js
CHANGED
|
@@ -6,6 +6,8 @@ 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";
|
|
10
|
+
import { Unicode11Addon } from "@xterm/addon-unicode11";
|
|
9
11
|
import pty from "@homebridge/node-pty-prebuilt-multiarch";
|
|
10
12
|
import { Shell, userZdotdir, zdotdir } from "../utils/shell.js";
|
|
11
13
|
import { IsTermOscPs, IstermOscPt, IstermPromptStart, IstermPromptEnd } from "../utils/ansi.js";
|
|
@@ -13,8 +15,8 @@ import xterm from "@xterm/headless";
|
|
|
13
15
|
import { CommandManager } from "./commandManager.js";
|
|
14
16
|
import log from "../utils/log.js";
|
|
15
17
|
import { gitBashPath } from "../utils/shell.js";
|
|
16
|
-
import ansi from "ansi-escapes";
|
|
17
18
|
import styles from "ansi-styles";
|
|
19
|
+
import * as ansi from "../utils/ansi.js";
|
|
18
20
|
const ISTermOnDataEvent = "data";
|
|
19
21
|
export class ISTerm {
|
|
20
22
|
pid;
|
|
@@ -43,7 +45,10 @@ export class ISTerm {
|
|
|
43
45
|
this.cols = this.#pty.cols;
|
|
44
46
|
this.rows = this.#pty.rows;
|
|
45
47
|
this.process = this.#pty.process;
|
|
48
|
+
const unicode11Addon = new Unicode11Addon();
|
|
46
49
|
this.#term = new xterm.Terminal({ allowProposedApi: true, rows, cols });
|
|
50
|
+
this.#term.loadAddon(unicode11Addon);
|
|
51
|
+
this.#term.unicode.activeVersion = "11";
|
|
47
52
|
this.#term.parser.registerOscHandler(IsTermOscPs, (data) => this._handleIsSequence(data));
|
|
48
53
|
this.#commandManager = new CommandManager(this.#term, shell);
|
|
49
54
|
this.#shell = shell;
|
|
@@ -84,6 +89,11 @@ export class ISTerm {
|
|
|
84
89
|
}
|
|
85
90
|
return cwd;
|
|
86
91
|
}
|
|
92
|
+
_sanitizedPrompt(prompt) {
|
|
93
|
+
// eslint-disable-next-line no-control-regex -- strip OSC control sequences
|
|
94
|
+
const oscStrippedPrompt = prompt.replace(/\x1b\][0-9]+;.*\x07/g, "");
|
|
95
|
+
return stripAnsi(oscStrippedPrompt);
|
|
96
|
+
}
|
|
87
97
|
_handleIsSequence(data) {
|
|
88
98
|
const argsIndex = data.indexOf(";");
|
|
89
99
|
const sequence = argsIndex === -1 ? data : data.substring(0, argsIndex);
|
|
@@ -101,6 +111,19 @@ export class ISTerm {
|
|
|
101
111
|
}
|
|
102
112
|
break;
|
|
103
113
|
}
|
|
114
|
+
case IstermOscPt.Prompt: {
|
|
115
|
+
const prompt = data.split(";").slice(1).join(";");
|
|
116
|
+
if (prompt != null) {
|
|
117
|
+
const sanitizedPrompt = this._sanitizedPrompt(this._deserializeIsMessage(prompt));
|
|
118
|
+
const lastPromptLine = sanitizedPrompt.substring(sanitizedPrompt.lastIndexOf("\n")).trim();
|
|
119
|
+
const promptTerminator = lastPromptLine.substring(lastPromptLine.lastIndexOf(" ")).trim();
|
|
120
|
+
if (promptTerminator) {
|
|
121
|
+
this.#commandManager.promptTerminator = promptTerminator;
|
|
122
|
+
log.debug({ msg: "prompt terminator", promptTerminator });
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
break;
|
|
126
|
+
}
|
|
104
127
|
default:
|
|
105
128
|
return false;
|
|
106
129
|
}
|
|
@@ -216,7 +239,7 @@ export class ISTerm {
|
|
|
216
239
|
const currentCursorPosition = this.#term.buffer.active.cursorY + this.#term.buffer.active.baseY;
|
|
217
240
|
const writeLine = (y) => {
|
|
218
241
|
const line = this.#term.buffer.active.getLine(y);
|
|
219
|
-
const ansiLine = [
|
|
242
|
+
const ansiLine = [ansi.resetColor, ansi.resetLine];
|
|
220
243
|
if (line == null)
|
|
221
244
|
return "";
|
|
222
245
|
let prevCell;
|
|
@@ -226,7 +249,7 @@ export class ISTerm {
|
|
|
226
249
|
const sameColor = this._sameColor(prevCell, cell);
|
|
227
250
|
const sameAccents = this._sameAccent(prevCell, cell);
|
|
228
251
|
if (!sameColor || !sameAccents) {
|
|
229
|
-
ansiLine.push(
|
|
252
|
+
ansiLine.push(ansi.resetColor);
|
|
230
253
|
}
|
|
231
254
|
if (!sameColor) {
|
|
232
255
|
ansiLine.push(this._getAnsiColors(cell));
|
|
@@ -234,7 +257,7 @@ export class ISTerm {
|
|
|
234
257
|
if (!sameAccents) {
|
|
235
258
|
ansiLine.push(this._getAnsiAccents(cell));
|
|
236
259
|
}
|
|
237
|
-
ansiLine.push(chars == "" ?
|
|
260
|
+
ansiLine.push(chars == "" ? ansi.cursorForward() : chars);
|
|
238
261
|
prevCell = cell;
|
|
239
262
|
}
|
|
240
263
|
return ansiLine.join("");
|
|
@@ -275,7 +298,10 @@ const convertToPtyTarget = async (shell, underTest, login) => {
|
|
|
275
298
|
shellArgs = ["-noexit", "-command", `try { . "${path.join(shellFolderPath, "shellIntegration.ps1")}" } catch {}`];
|
|
276
299
|
break;
|
|
277
300
|
case Shell.Fish:
|
|
278
|
-
shellArgs =
|
|
301
|
+
shellArgs =
|
|
302
|
+
platform == "win32"
|
|
303
|
+
? ["--init-command", `. "$(cygpath -u '${path.join(shellFolderPath, "shellIntegration.fish")}')"`]
|
|
304
|
+
: ["--init-command", `. ${path.join(shellFolderPath, "shellIntegration.fish").replace(/(\s+)/g, "\\$1")}`];
|
|
279
305
|
break;
|
|
280
306
|
case Shell.Xonsh: {
|
|
281
307
|
const sharedConfig = os.platform() == "win32" ? path.join("C:\\ProgramData", "xonsh", "xonshrc") : path.join("etc", "xonsh", "xonshrc");
|
|
@@ -17,10 +17,10 @@ var SuggestionIcons;
|
|
|
17
17
|
SuggestionIcons["Default"] = "\uD83D\uDCC0";
|
|
18
18
|
})(SuggestionIcons || (SuggestionIcons = {}));
|
|
19
19
|
const getIcon = (icon, suggestionType) => {
|
|
20
|
-
//
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
20
|
+
// eslint-disable-next-line no-control-regex
|
|
21
|
+
if (icon && /[^\u0000-\u00ff]/.test(icon)) {
|
|
22
|
+
return icon;
|
|
23
|
+
}
|
|
24
24
|
switch (suggestionType) {
|
|
25
25
|
case "arg":
|
|
26
26
|
return SuggestionIcons.Argument;
|
package/build/runtime/utils.js
CHANGED
|
@@ -6,14 +6,14 @@ import fsAsync from "node:fs/promises";
|
|
|
6
6
|
import { getPathSeperator } from "../utils/shell.js";
|
|
7
7
|
import log from "../utils/log.js";
|
|
8
8
|
export const buildExecuteShellCommand = (timeout) => async ({ command, env, args, cwd }) => {
|
|
9
|
-
const child = spawn(command, args, { cwd, env: { ...env, ISTERM: "1" } });
|
|
9
|
+
const child = spawn(command, args, { cwd, env: { ...process.env, ...env, ISTERM: "1" } });
|
|
10
10
|
setTimeout(() => child.kill("SIGKILL"), timeout);
|
|
11
11
|
let stdout = "";
|
|
12
12
|
let stderr = "";
|
|
13
13
|
child.stdout.on("data", (data) => (stdout += data));
|
|
14
14
|
child.stderr.on("data", (data) => (stderr += data));
|
|
15
15
|
child.on("error", (err) => {
|
|
16
|
-
log.debug({ msg: "shell command failed", e: err.message });
|
|
16
|
+
log.debug({ msg: "shell command failed", command, args, e: err.message });
|
|
17
17
|
});
|
|
18
18
|
return new Promise((resolve) => {
|
|
19
19
|
child.on("close", (code) => {
|
|
@@ -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;
|
|
@@ -125,12 +126,17 @@ export class SuggestionManager {
|
|
|
125
126
|
if (name == "return") {
|
|
126
127
|
this.#term.clearCommand(); // clear the current command on enter
|
|
127
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
|
+
}
|
|
128
133
|
if (!this.#suggestBlob) {
|
|
129
134
|
return false;
|
|
130
135
|
}
|
|
131
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;
|
|
132
137
|
if (name == dismissKey && shift == !!dismissShift && ctrl == !!dismissCtrl) {
|
|
133
138
|
this.#suggestBlob = undefined;
|
|
139
|
+
this.#hideSuggestions = true;
|
|
134
140
|
}
|
|
135
141
|
else if (name == prevKey && shift == !!prevShift && ctrl == !!prevCtrl) {
|
|
136
142
|
this.#activeSuggestionIdx = Math.max(0, this.#activeSuggestionIdx - 1);
|
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
|
@@ -8,6 +8,7 @@ import fs from "node:fs";
|
|
|
8
8
|
import url from "node:url";
|
|
9
9
|
import os from "node:os";
|
|
10
10
|
import fsAsync from "node:fs/promises";
|
|
11
|
+
import log from "./log.js";
|
|
11
12
|
export var Shell;
|
|
12
13
|
(function (Shell) {
|
|
13
14
|
Shell["Bash"] = "bash";
|
|
@@ -49,7 +50,32 @@ export const setupZshDotfiles = async () => {
|
|
|
49
50
|
await fsAsync.cp(path.join(shellFolderPath, "shellIntegration-env.zsh"), path.join(zdotdir, ".zshenv"));
|
|
50
51
|
await fsAsync.cp(path.join(shellFolderPath, "shellIntegration-login.zsh"), path.join(zdotdir, ".zlogin"));
|
|
51
52
|
};
|
|
53
|
+
const findPareentProcess = async () => {
|
|
54
|
+
try {
|
|
55
|
+
return (await find("pid", process.ppid)).at(0);
|
|
56
|
+
}
|
|
57
|
+
catch (e) {
|
|
58
|
+
log.debug({ msg: `error finding parent process: ${e}` });
|
|
59
|
+
}
|
|
60
|
+
};
|
|
52
61
|
export const inferShell = async () => {
|
|
62
|
+
// try getting shell from shell specific env variables
|
|
63
|
+
if (process.env.NU_VERSION != null) {
|
|
64
|
+
return Shell.Nushell;
|
|
65
|
+
}
|
|
66
|
+
else if (process.env.XONSHRC != null) {
|
|
67
|
+
return Shell.Xonsh;
|
|
68
|
+
}
|
|
69
|
+
else if (process.env.FISH_VERSION != null) {
|
|
70
|
+
return Shell.Fish;
|
|
71
|
+
}
|
|
72
|
+
else if (process.env.ZSH_VERSION != null) {
|
|
73
|
+
return Shell.Zsh;
|
|
74
|
+
}
|
|
75
|
+
else if (process.env.BASH_VERSION != null) {
|
|
76
|
+
return Shell.Bash;
|
|
77
|
+
}
|
|
78
|
+
// try getting shell from env
|
|
53
79
|
try {
|
|
54
80
|
const name = path.parse(process.env.SHELL ?? "").name;
|
|
55
81
|
const shellName = supportedShells.find((shell) => name.includes(shell));
|
|
@@ -59,7 +85,8 @@ export const inferShell = async () => {
|
|
|
59
85
|
catch {
|
|
60
86
|
/* empty */
|
|
61
87
|
}
|
|
62
|
-
|
|
88
|
+
// try getting shell from parent process
|
|
89
|
+
const processResult = await findPareentProcess();
|
|
63
90
|
const name = processResult?.name;
|
|
64
91
|
return name != null ? supportedShells.find((shell) => name.includes(shell)) : undefined;
|
|
65
92
|
};
|
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.16",
|
|
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/addon-unicode11": "^0.8.0",
|
|
44
45
|
"@xterm/headless": "^5.5.0",
|
|
45
46
|
"ajv": "^8.12.0",
|
|
46
47
|
"ansi-escapes": "^6.2.0",
|
|
@@ -49,6 +50,7 @@
|
|
|
49
50
|
"color-convert": "^2.0.1",
|
|
50
51
|
"commander": "^11.0.0",
|
|
51
52
|
"find-process": "^1.4.7",
|
|
53
|
+
"strip-ansi": "^7.1.0",
|
|
52
54
|
"toml": "^3.0.0",
|
|
53
55
|
"wcwidth": "^1.0.1",
|
|
54
56
|
"which": "^4.0.0",
|
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
if [[ -f $USER_ZDOTDIR/.zshenv ]]; then
|
|
2
|
+
IS_ZDOTDIR=$ZDOTDIR
|
|
2
3
|
ZDOTDIR=$USER_ZDOTDIR
|
|
3
4
|
|
|
4
5
|
# prevent recursion
|
|
5
|
-
if [[ $USER_ZDOTDIR != $
|
|
6
|
-
ZDOTDIR=$USER_ZDOTDIR
|
|
6
|
+
if [[ $USER_ZDOTDIR != $IS_ZDOTDIR ]]; then
|
|
7
7
|
. $USER_ZDOTDIR/.zshenv
|
|
8
8
|
fi
|
|
9
|
+
|
|
10
|
+
USER_ZDOTDIR=$ZDOTDIR
|
|
11
|
+
ZDOTDIR=$IS_ZDOTDIR
|
|
9
12
|
fi
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
autoload -U add-zsh-hook
|
|
2
|
+
|
|
1
3
|
if [[ -f $USER_ZDOTDIR/.zshrc ]]; then
|
|
2
4
|
ZDOTDIR=$USER_ZDOTDIR
|
|
3
5
|
. $USER_ZDOTDIR/.zshrc
|
|
@@ -25,6 +27,12 @@ __is_escape_value() {
|
|
|
25
27
|
token="\\\\"
|
|
26
28
|
elif [ "$byte" = ";" ]; then
|
|
27
29
|
token="\\x3b"
|
|
30
|
+
elif [ "$byte" = $'\n' ]; then
|
|
31
|
+
token="\x0a"
|
|
32
|
+
elif [ "$byte" = $'\e' ]; then
|
|
33
|
+
token="\\x1b"
|
|
34
|
+
elif [ "$byte" = $'\a' ]; then
|
|
35
|
+
token="\\x07"
|
|
28
36
|
else
|
|
29
37
|
token="$byte"
|
|
30
38
|
fi
|
|
@@ -40,6 +40,12 @@ __is_escape_value() {
|
|
|
40
40
|
token="\\\\"
|
|
41
41
|
elif [ "$byte" = ";" ]; then
|
|
42
42
|
token="\\x3b"
|
|
43
|
+
elif [ "$byte" = $'\n' ]; then
|
|
44
|
+
token="\x0a"
|
|
45
|
+
elif [ "$byte" = $'\e' ]; then
|
|
46
|
+
token="\\x1b"
|
|
47
|
+
elif [ "$byte" = $'\a' ]; then
|
|
48
|
+
token="\\x07"
|
|
43
49
|
else
|
|
44
50
|
token="$byte"
|
|
45
51
|
fi
|
|
@@ -54,6 +60,16 @@ __is_update_cwd() {
|
|
|
54
60
|
builtin printf '\e]6973;CWD;%s\a' "$(__is_escape_value "$PWD")"
|
|
55
61
|
}
|
|
56
62
|
|
|
63
|
+
__is_report_prompt() {
|
|
64
|
+
if ((BASH_VERSINFO[0] >= 4)); then
|
|
65
|
+
__is_prompt=${__is_original_PS1@P}
|
|
66
|
+
else
|
|
67
|
+
__is_prompt=${__is_original_PS1}
|
|
68
|
+
fi
|
|
69
|
+
__is_prompt="$(builtin printf "%s" "${__is_prompt//[$'\001'$'\002']}")"
|
|
70
|
+
builtin printf "\e]6973;PROMPT;%s\a" "$(__is_escape_value "${__is_prompt}")"
|
|
71
|
+
}
|
|
72
|
+
|
|
57
73
|
if [[ -n "${bash_preexec_imported:-}" ]]; then
|
|
58
74
|
precmd_functions+=(__is_precmd)
|
|
59
75
|
fi
|
|
@@ -61,6 +77,7 @@ fi
|
|
|
61
77
|
__is_precmd() {
|
|
62
78
|
__is_update_cwd
|
|
63
79
|
__is_update_prompt
|
|
80
|
+
__is_report_prompt
|
|
64
81
|
}
|
|
65
82
|
|
|
66
83
|
__is_update_prompt() {
|
|
@@ -2,15 +2,19 @@ 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 replace -a \a '\\x07' \
|
|
13
|
+
| string split \n | string join '\x0a' \
|
|
9
14
|
;
|
|
10
15
|
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
|
|
16
|
+
function __is_update_cwd --on-event fish_prompt; set __is_cwd (__is_escape_value "$PWD"); printf "\e]6973;CWD;%s\a" $__is_cwd; end
|
|
17
|
+
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
18
|
|
|
15
19
|
if [ "$ISTERM_TESTING" = "1" ]
|
|
16
20
|
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" | str replace --all "\a" "\\x07" }
|
|
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)$([char]0x07)\\\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 "\\x07" if byte == "\x07" 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}"
|
package/todo.md
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
- [x] improve wrapping handling
|
|
2
|
+
`const wrapped = line?.isWrapped || (this.#terminal.buffer.active.cursorY + this.#terminal.buffer.active.baseY) != lineY`
|
|
3
|
+
`lineY < this.#terminal.rows`
|
|
4
|
+
- [ ] handle paths with spaces in them on bash (ignore pwsh for now as it fails cd anyway)
|