Skip to content

Commit dc95b9d

Browse files
authored
Lite Terminal shell integration improvements (#1654)
1 parent c781460 commit dc95b9d

File tree

3 files changed

+49
-35
lines changed

3 files changed

+49
-35
lines changed

package-lock.json

Lines changed: 5 additions & 5 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@
4848
}
4949
],
5050
"engines": {
51-
"vscode": "^1.93.0"
51+
"vscode": "^1.104.0"
5252
},
5353
"enabledApiProposals": [
5454
"fileSearchProvider",
@@ -1815,7 +1815,7 @@
18151815
"test": "node ./out/test/runTest.js",
18161816
"lint": "eslint src/**",
18171817
"lint-fix": "eslint --fix src/**",
1818-
"download-api": "dts dev 1.93.0",
1818+
"download-api": "dts dev 1.104.0",
18191819
"postinstall": "npm run download-api"
18201820
},
18211821
"devDependencies": {
@@ -1826,7 +1826,7 @@
18261826
"@types/mocha": "^7.0.2",
18271827
"@types/node": "20.17.6",
18281828
"@types/semver": "7.5.4",
1829-
"@types/vscode": "1.93.0",
1829+
"@types/vscode": "1.104.0",
18301830
"@types/ws": "8.18.0",
18311831
"@types/xmldom": "^0.1.34",
18321832
"@typescript-eslint/eslint-plugin": "^8.15.0",

src/commands/webSocketTerminal.ts

Lines changed: 41 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ class WebSocketTerminal implements vscode.Pseudoterminal {
5656
/** The number of characters on the line that the user can't delete */
5757
private _margin = 0;
5858

59-
/** The text writted by the user since the last prompt/read */
59+
/** The text written by the user since the last prompt/read */
6060
private _input = "";
6161

6262
/** The position of the cursor within the line */
@@ -98,6 +98,7 @@ class WebSocketTerminal implements vscode.Pseudoterminal {
9898

9999
constructor(
100100
private readonly _targetUri: vscode.Uri,
101+
private readonly _nonce: string,
101102
private readonly _nsOverride?: string
102103
) {}
103104

@@ -224,7 +225,7 @@ class WebSocketTerminal implements vscode.Pseudoterminal {
224225
this._hideCursorWrite("\x1b]633;P;HasRichCommandDetection=True\x07");
225226
// Print the opening message
226227
this._hideCursorWrite(
227-
`\x1b[32mConnected to \x1b[0m\x1b[4m${api.config.host}:${api.config.port}${api.config.pathPrefix}\x1b[0m\x1b[32m as \x1b[0m\x1b[3m${api.config.username}\x1b[0m\r\n\r\n`
228+
`\x1b[32mConnected to \x1b[0m\x1b[4m${api.config.host}:${api.config.port}${api.config.pathPrefix}\x1b[0m\x1b[32m as \x1b[0m\x1b[3m${api.config.username}\x1b[0m\r\n`
228229
);
229230
// Add event handlers to the socket
230231
this._socket
@@ -273,9 +274,7 @@ class WebSocketTerminal implements vscode.Pseudoterminal {
273274
if (message.type == "prompt") {
274275
// Write the prompt to the terminal
275276
this._hideCursorWrite(
276-
`\x1b]633;D${this._promptExitCode}\x07${this._margin ? "\r\n" : ""}\x1b]633;A\x07${
277-
message.text
278-
}\x1b]633;B\x07`
277+
`\x1b]633;D${this._promptExitCode}\x07\r\n\x1b]633;A\x07${message.text}\x1b]633;B\x07`
279278
);
280279
this._margin = this._cursorCol = message.text.replace(this._colorsRegex, "").length;
281280
this._prompt = message.text;
@@ -366,13 +365,14 @@ class WebSocketTerminal implements vscode.Pseudoterminal {
366365
// Send the input to the server for processing
367366
this._socket.send(JSON.stringify({ type: this._state, input: this._input }));
368367
if (this._state == "prompt") {
369-
this._hideCursorWrite(`\x1b]633;E;${this._inputEscaped()}\x07\x1b]633;C\x07\r\n`);
368+
this._hideCursorWrite(`\x1b]633;E;${this._inputEscaped()};${this._nonce}\x07\r\n\x1b]633;C\x07`);
370369
if (this._input == "") {
371370
this._promptExitCode = "";
372371
}
373372
}
374373
this._input = "";
375374
this._state = "eval";
375+
this._margin = this._cursorCol = 0;
376376
return;
377377
}
378378
case keys.ctrlH:
@@ -561,7 +561,7 @@ class WebSocketTerminal implements vscode.Pseudoterminal {
561561
if (this._cursorCol == this._margin + inputArr[inputArr.length - 1].length) {
562562
// Move the cursor to the beginning of the input
563563
this._moveCursor(this._margin - this._cursorCol);
564-
// Erase everyhting to the right of the cursor
564+
// Erase everything to the right of the cursor
565565
this._hideCursorWrite("\x1b[0J");
566566
inputArr[inputArr.length - 1] = "";
567567
this._input = inputArr.join("\r\n");
@@ -588,10 +588,16 @@ class WebSocketTerminal implements vscode.Pseudoterminal {
588588
// Submit the input after processing
589589
// This should only happen due to VS Code's shell integration
590590
submit = true;
591-
char = char.slice(0, -1);
592-
}
593-
// Replace all single \r with \r\n (prompt) or space (read)
594-
char = char.replace(/\r/g, this._state == "prompt" ? "\r\n" : " ");
591+
// Need to remove any multi-line prompts that are in the command lines
592+
// Workaround for https://github.com/microsoft/vscode/issues/258457
593+
char = char
594+
.slice(0, -1)
595+
.split("\r")
596+
.map((l) => (l.startsWith(this._multiLinePrompt) ? l.slice(this._multiLinePrompt.length) : l))
597+
.join("\r");
598+
}
599+
// Replace all single \r with \r\n
600+
char = char.replace(/\r(?!\n)/g, "\r\n");
595601
const inputArr = this._input.split("\r\n");
596602
let eraseAfterCursor = "",
597603
trailingText = "";
@@ -613,8 +619,10 @@ class WebSocketTerminal implements vscode.Pseudoterminal {
613619
const originalCol = this._cursorCol;
614620
let newRow: number;
615621
if (char.includes("\r\n")) {
616-
char = char.replace(/\r\n/g, `\r\n${this._multiLinePrompt}`);
617-
this._margin = this._multiLinePrompt.length;
622+
if (this._state == "prompt") {
623+
char = char.replaceAll("\r\n", `\r\n${this._multiLinePrompt}`);
624+
this._margin = this._multiLinePrompt.length;
625+
}
618626
const charLines = char.split("\r\n");
619627
newRow =
620628
charLines.reduce(
@@ -632,21 +640,24 @@ class WebSocketTerminal implements vscode.Pseudoterminal {
632640
const colStr = colDelta ? (colDelta > 0 ? `\x1b[${colDelta}C` : `\x1b[${Math.abs(colDelta)}D`) : "";
633641
char += trailingText;
634642
const spaceOnCurrentLine = this._cols - (originalCol % this._cols);
635-
if (this._state == "read" && char.length >= spaceOnCurrentLine) {
643+
if (this._state == "read" && (char.includes("\r\n") || char.length >= spaceOnCurrentLine)) {
636644
// There's no auto-line wrapping when in read mode, so we must move the cursor manually
645+
const charLines = char.split("\r\n");
637646
// Extract all the characters that fit on the cursor's line
638-
const firstLine = char.slice(0, spaceOnCurrentLine);
639-
const otherLines = char.slice(spaceOnCurrentLine);
640-
const lines: string[] = [];
641-
if (otherLines.length) {
642-
// Split the rest into an array of lines that fit in the viewport
643-
for (let line = 0, i = 0; line < Math.ceil(otherLines.length / this._cols); line++, i += this._cols) {
644-
lines[line] = otherLines.slice(i, i + this._cols);
647+
const firstLine = charLines[0].slice(0, spaceOnCurrentLine);
648+
charLines[0] = charLines[0].slice(spaceOnCurrentLine);
649+
// Split the rest into an array of lines that fit in the viewport
650+
const lines = charLines.flatMap((line, idx) => {
651+
if (idx == charLines.length - 1 && line == "") {
652+
// Add a blank "line" to move the cursor to the next viewport row
653+
return [""];
645654
}
646-
} else {
647-
// Add a blank "line" to move the cursor to the next viewport row
648-
lines.push("");
649-
}
655+
const chunks = [];
656+
for (let i = 0; i < line.length; i += this._cols) {
657+
chunks.push(line.slice(i, i + this._cols));
658+
}
659+
return chunks;
660+
});
650661
// Join the lines with the cursor escape code
651662
lines.unshift(firstLine);
652663
char = lines.join("\r\n");
@@ -678,13 +689,14 @@ class WebSocketTerminal implements vscode.Pseudoterminal {
678689
// Send the input to the server for processing
679690
this._socket.send(JSON.stringify({ type: this._state, input: this._input }));
680691
if (this._state == "prompt") {
681-
this._hideCursorWrite(`\x1b]633;E;${this._inputEscaped()}\x07\x1b]633;C\x07\r\n`);
692+
this._hideCursorWrite(`\x1b]633;E;${this._inputEscaped()};${this._nonce}\x07\r\n\x1b]633;C\x07`);
682693
if (this._input == "") {
683694
this._promptExitCode = "";
684695
}
685696
}
686697
this._input = "";
687698
this._state = "eval";
699+
this._margin = this._cursorCol = 0;
688700
} else if (this._input != "" && this._state == "prompt" && this._syntaxColoringEnabled()) {
689701
// Syntax color input
690702
this._socket.send(JSON.stringify({ type: "color", input: this._input }));
@@ -747,6 +759,7 @@ function terminalConfigForUri(
747759
}
748760

749761
sendLiteTerminalTelemetryEvent(throwErrors ? "profile" : "command");
762+
const nonce = crypto.randomUUID();
750763
return {
751764
name: api.config.serverName && api.config.serverName != "" ? api.config.serverName : "iris",
752765
location:
@@ -756,9 +769,10 @@ function terminalConfigForUri(
756769
vscode.window.terminals.length > 0
757770
? vscode.TerminalLocation.Editor
758771
: vscode.TerminalLocation.Panel,
759-
pty: new WebSocketTerminal(targetUri, nsOverride),
772+
pty: new WebSocketTerminal(targetUri, nonce, nsOverride),
760773
isTransient: true,
761774
iconPath: iscIcon,
775+
shellIntegrationNonce: nonce,
762776
};
763777
}
764778

0 commit comments

Comments
 (0)