@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 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 located at `~/.inshellisenserc`. 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).
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
- if (inshellisenseConfig?.prompt?.bash != null) {
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.Nushell) {
85
- if (inshellisenseConfig?.prompt?.nu != null) {
86
- const extractedPrompt = this._extractPrompt(lineText, inshellisenseConfig.prompt.nu);
87
- if (extractedPrompt)
88
- return extractedPrompt;
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 dullItalic = (color ?? 0) > 235 || (dullColor && italic);
174
- if (this.#shell == Shell.Powershell) {
175
- return false;
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
- if (globalCursorPosition < this.#activeCommand.promptStartMarker.line) {
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
- let lineY = this.#activeCommand.promptEndMarker.line;
225
- let line = this.#terminal.buffer.active.getLine(this.#activeCommand.promptEndMarker.line);
226
- let command = "";
227
- let wrappedCommand = "";
228
- let suggestions = "";
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 = suggestions.trim();
277
- this.#activeCommand.commandText = command.trim() + commandPostfix;
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
  }
@@ -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 = ["\x1b[0m"];
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("\x1b[0m");
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 == "" ? " " : 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 = ["--init-command", `. ${path.join(shellFolderPath, "shellIntegration.fish").replace(/(\s+)/g, "\\$1")}`];
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
- // TODO: enable fig icons once spacing is better
21
- // if (icon && /[^\u0000-\u00ff]/.test(icon)) {
22
- // return icon;
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;
@@ -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));
@@ -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
  };
@@ -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 configFile = ".inshellisenserc";
75
+ const rcFile = ".inshellisenserc";
76
+ const xdgFile = "rc.toml";
74
77
  const cachePath = path.join(os.homedir(), ".inshellisense");
75
- const configPath = path.join(os.homedir(), configFile);
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
- if (fs.existsSync(configPath)) {
87
- let config;
88
- try {
89
- config = toml.parse((await fsAsync.readFile(configPath)).toString());
90
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
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
- catch (e) {
93
- program.error(`${configFile} is invalid toml. Parsing error on line ${e.line}, column ${e.column}: ${e.message}`);
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);
@@ -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
- const processResult = (await find("pid", process.ppid)).at(0);
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.14",
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 != $ZDOTDIR ]]; then
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 --all '\\' '\\\\' \
8
- | string replace --all ';' '\\x3b' \
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;$__is_cwd\a"; end
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 __is_original_PROMPT_COMMAND = if 'PROMPT_COMMAND' in $env { $env.PROMPT_COMMAND } else { "" }
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, '[\\\n;]', { param($match)
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
- $Result += $Global:__IsOriginalPrompt.Invoke()
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" + "\002"
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)