Repository: microsoft/vscode (TypeScript)
Date: 2023-02-03 19:21:09+00:00
Task type: Bug Fix

Issue:
`"serverReadyAction"` works incorrectly with `"console": "integratedTerminal"` in launch.json with dotnet projects
<!-- ⚠️⚠️ Do Not Delete This! bug_report_template ⚠️⚠️ -->
<!-- Please read our Rules of Conduct: https://opensource.microsoft.com/codeofconduct/ -->
<!-- 🕮 Read our guide about submitting issues: https://github.com/microsoft/vscode/wiki/Submitting-Bugs-and-Suggestions -->
<!-- 🔎 Search existing issues to avoid creating duplicates. -->
<!-- 🧪 Test using the latest Insiders build to see if your issue has already been fixed: https://code.visualstudio.com/insiders/ -->
<!-- 💡 Instead of creating your report here, use 'Report Issue' from the 'Help' menu in VS Code to pre-fill useful information. -->
<!-- 🔧 Launch with `code --disable-extensions` to check. -->
**Does this issue occur when all extensions are disabled?:** Omnisharp is the only extension required

<!-- 🪓 If you answered No above, use 'Help: Start Extension Bisect' from Command Palette to try to identify the cause. -->
<!-- 📣 Issues caused by an extension need to be reported directly to the extension publisher. The 'Help > Report Issue' dialog can assist with this. -->
- **VS Code Version:** 1.68.1
- **OS Version:** Win10 Pro x64 21H2 19044.1766
- **.NET Version:** 6.0.5
- **C# Extension Version:** v1.25.0

Bug can be reproduced with `settings.json` empty.

**Setup:**
1. `dotnet new web` to create new clean web project
2. Command palette: `.NET: Generate Assets for Build and Debug` to generate `launch.json` and `tasks.json`.

**Reproduce:**
1. Add line `"console": "integratedTerminal"` to launch configuration.
2. Launch project. First time it will be ok, a browser with `localhost:port` url will be opened.
3. Launch project second time. Now Windows Explorer opens. Each time, until VS Code is restarted.
4. If you change `"action"` to `"debugWithChrome"` in `"serverReadyAction"` section, launching project will open `about:blank` url in Chrome.
5. If you restart your VS Code, you can go back to step 2 of reproduce. In other words, you can successfully launch project only once after each restart of VS Code.

**Video (including setup):**
https://user-images.githubusercontent.com/45442278/176376728-23d9891c-6485-44a4-9554-dfcbdcfe609e.mp4




Issue discussion:
Ah ha, the key was using mingw64 (git bash works for this)

Solution patch:
diff --git a/extensions/debug-server-ready/src/extension.ts b/extensions/debug-server-ready/src/extension.ts
--- a/extensions/debug-server-ready/src/extension.ts
+++ b/extensions/debug-server-ready/src/extension.ts
@@ -21,11 +21,8 @@ interface ServerReadyAction {
 	killOnServerStop?: boolean;
 }
 
-// Escape codes
-// http://en.wikipedia.org/wiki/ANSI_escape_code
-const EL = /\x1B\x5B[12]?K/g; // Erase in line
-const COLOR_START = /\x1b\[\d+m/g; // Color
-const COLOR_END = /\x1b\[0?m/g; // Color
+// Escape codes, compiled from https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h3-Functions-using-CSI-_-ordered-by-the-final-character_s_
+const CSI_SEQUENCE = /(:?\x1b\[|\x9B)[=?>!]?[\d;:]*["$#'* ]?[a-zA-Z@^`{}|~]/g;
 
 /**
  * Froms vs/base/common/strings.ts in core
@@ -33,9 +30,7 @@ const COLOR_END = /\x1b\[0?m/g; // Color
  */
 function removeAnsiEscapeCodes(str: string): string {
 	if (str) {
-		str = str.replace(EL, '');
-		str = str.replace(COLOR_START, '');
-		str = str.replace(COLOR_END, '');
+		str = str.replace(CSI_SEQUENCE, '');
 	}
 
 	return str;
diff --git a/src/vs/base/common/strings.ts b/src/vs/base/common/strings.ts
--- a/src/vs/base/common/strings.ts
+++ b/src/vs/base/common/strings.ts
@@ -727,17 +727,12 @@ export function lcut(text: string, n: number) {
 	return text.substring(i).replace(/^\s/, '');
 }
 
-// Escape codes
-// http://en.wikipedia.org/wiki/ANSI_escape_code
-const EL = /\x1B\x5B[12]?K/g; // Erase in line
-const COLOR_START = /\x1b\[\d+m/g; // Color
-const COLOR_END = /\x1b\[0?m/g; // Color
+// Escape codes, compiled from https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h3-Functions-using-CSI-_-ordered-by-the-final-character_s_
+const CSI_SEQUENCE = /(:?\x1b\[|\x9B)[=?>!]?[\d;:]*["$#'* ]?[a-zA-Z@^`{}|~]/g;
 
 export function removeAnsiEscapeCodes(str: string): string {
 	if (str) {
-		str = str.replace(EL, '');
-		str = str.replace(COLOR_START, '');
-		str = str.replace(COLOR_END, '');
+		str = str.replace(CSI_SEQUENCE, '');
 	}
 
 	return str;


Test patch:
diff --git a/src/vs/base/test/common/strings.test.ts b/src/vs/base/test/common/strings.test.ts
--- a/src/vs/base/test/common/strings.test.ts
+++ b/src/vs/base/test/common/strings.test.ts
@@ -395,4 +395,126 @@ suite('Strings', () => {
 			return `${i++}${after}`;
 		}), 'a0ca1ca2ca3c');
 	});
+
+	test('removeAnsiEscapeCodes', () => {
+		const CSI = '\x1b\[';
+		const sequences = [
+			// Base cases from https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h3-Functions-using-CSI-_-ordered-by-the-final-character_s_
+			`${CSI}42@`,
+			`${CSI}42 @`,
+			`${CSI}42A`,
+			`${CSI}42 A`,
+			`${CSI}42B`,
+			`${CSI}42C`,
+			`${CSI}42D`,
+			`${CSI}42E`,
+			`${CSI}42F`,
+			`${CSI}42G`,
+			`${CSI}42;42H`,
+			`${CSI}42I`,
+			`${CSI}42J`,
+			`${CSI}?42J`,
+			`${CSI}42K`,
+			`${CSI}?42K`,
+			`${CSI}42L`,
+			`${CSI}42M`,
+			`${CSI}42P`,
+			`${CSI}#P`,
+			`${CSI}3#P`,
+			`${CSI}#Q`,
+			`${CSI}3#Q`,
+			`${CSI}#R`,
+			`${CSI}42S`,
+			`${CSI}?1;2;3S`,
+			`${CSI}42T`,
+			`${CSI}42;42;42;42;42T`,
+			`${CSI}>3T`,
+			`${CSI}42X`,
+			`${CSI}42Z`,
+			`${CSI}42^`,
+			`${CSI}42\``,
+			`${CSI}42a`,
+			`${CSI}42b`,
+			`${CSI}42c`,
+			`${CSI}=42c`,
+			`${CSI}>42c`,
+			`${CSI}42d`,
+			`${CSI}42e`,
+			`${CSI}42;42f`,
+			`${CSI}42g`,
+			`${CSI}3h`,
+			`${CSI}?3h`,
+			`${CSI}42i`,
+			`${CSI}?42i`,
+			`${CSI}3l`,
+			`${CSI}?3l`,
+			`${CSI}3m`,
+			`${CSI}>0;0m`,
+			`${CSI}>0m`,
+			`${CSI}?0m`,
+			`${CSI}42n`,
+			`${CSI}>42n`,
+			`${CSI}?42n`,
+			`${CSI}>42p`,
+			`${CSI}!p`,
+			`${CSI}0;0"p`,
+			`${CSI}42$p`,
+			`${CSI}?42$p`,
+			`${CSI}#p`,
+			`${CSI}3#p`,
+			`${CSI}>42q`,
+			`${CSI}42q`,
+			`${CSI}42 q`,
+			`${CSI}42"q`,
+			`${CSI}#q`,
+			`${CSI}42;42r`,
+			`${CSI}?3r`,
+			`${CSI}0;0;0;0;3$r`,
+			`${CSI}s`,
+			`${CSI}0;0s`,
+			`${CSI}>42s`,
+			`${CSI}?3s`,
+			`${CSI}42;42;42t`,
+			`${CSI}>3t`,
+			`${CSI}42 t`,
+			`${CSI}0;0;0;0;3$t`,
+			`${CSI}u`,
+			`${CSI}42 u`,
+			`${CSI}0;0;0;0;0;0;0;0$v`,
+			`${CSI}42$w`,
+			`${CSI}0;0;0;0'w`,
+			`${CSI}42x`,
+			`${CSI}42*x`,
+			`${CSI}0;0;0;0;0$x`,
+			`${CSI}42#y`,
+			`${CSI}0;0;0;0;0;0*y`,
+			`${CSI}42;0'z`,
+			`${CSI}0;1;2;4$z`,
+			`${CSI}3'{`,
+			`${CSI}#{`,
+			`${CSI}3#{`,
+			`${CSI}0;0;0;0\${`,
+			`${CSI}0;0;0;0#|`,
+			`${CSI}42$|`,
+			`${CSI}42'|`,
+			`${CSI}42*|`,
+			`${CSI}#}`,
+			`${CSI}42'}`,
+			`${CSI}42$}`,
+			`${CSI}42'~`,
+			`${CSI}42$~`,
+
+			// Common SGR cases:
+			`${CSI}1;31m`, // multiple attrs
+			`${CSI}105m`, // bright background
+			`${CSI}48:5:128m`, // 256 indexed color
+			`${CSI}48;5;128m`, // 256 indexed color alt
+			`${CSI}38:2:0:255:255:255m`, // truecolor
+			`${CSI}38;2;255;255;255m`, // truecolor alt
+		];
+
+		for (const sequence of sequences) {
+			assert.strictEqual(strings.removeAnsiEscapeCodes(`hello${sequence}world`), 'helloworld', `expect to remove ${JSON.stringify(sequence)}`);
+		}
+	});
 });