Repository: microsoft/vscode (TypeScript)
Task: Bug Fix

Issue:
URLs in CSV document not split by field
Issue Type: <b>Bug</b>

When editing a CSV document, automatically-detected URLs (aka option+click to open) are often conjoined with the start of the following field. I presume the URL detection heuristic for plain text documents is reused for CSV documents. I propose that URLs are automatically closed on a comma.

Here is an example:

```csv
https://apod.nasa.gov/apod/ap170720.html,IC 1396: Emission Nebula in Cepheus,https://apod.nasa.gov/apod/image/1707/MOSAIC_IC1396_HaSHO_blanco1024.jpg,https://apod.nasa.gov/apod/image/1707/MOSAIC_IC1396_HaSHO_blanco.jpg
https://apod.nasa.gov/apod/ap180402.html,"Moons, Rings, Shadows, Clouds: Saturn (Cassini)",https://apod.nasa.gov/apod/image/1804/SaturnRingsMoons_Cassini_967.jpg,https://apod.nasa.gov/apod/image/1804/SaturnRingsMoons_Cassini_967.jpg
```

These are the URLs as currently delimited (only 1 is correct):

- `https://apod.nasa.gov/apod/ap170720.html,IC`
- `https://apod.nasa.gov/apod/ap180402.html`
- `https://apod.nasa.gov/apod/image/1707/MOSAIC_IC1396_HaSHO_blanco1024.jpg,https://apod.nasa.gov/apod/image/1707/MOSAIC_IC1396_HaSHO_blanco.jpg`
- `https://apod.nasa.gov/apod/image/1804/SaturnRingsMoons_Cassini_967.jpg,https://apod.nasa.gov/apod/image/1804/SaturnRingsMoons_Cassini_967.jpg`

VS Code version: Code 1.54.3 (2b9aebd5354a3629c3aba0a5f5df49f43d6689f8, 2021-03-15T11:57:12.728Z)
OS version: Darwin x64 18.7.0


<!-- generated by issue reporter -->


Discussion:

Patch:
diff --git a/src/vs/editor/common/languages/linkComputer.ts b/src/vs/editor/common/languages/linkComputer.ts
--- a/src/vs/editor/common/languages/linkComputer.ts
+++ b/src/vs/editor/common/languages/linkComputer.ts
@@ -155,12 +155,12 @@ function getClassifier(): CharacterClassifier<CharacterClass> {
 		_classifier = new CharacterClassifier<CharacterClass>(CharacterClass.None);
 
 		// allow-any-unicode-next-line
-		const FORCE_TERMINATION_CHARACTERS = ' \t<>\'\"、。｡､，．：；‘〈「『〔（［｛｢｣｝］）〕』」〉’｀～…';
+		const FORCE_TERMINATION_CHARACTERS = ', \t<>\'\"、。｡､，．：；‘〈「『〔（［｛｢｣｝］）〕』」〉’｀～…';
 		for (let i = 0; i < FORCE_TERMINATION_CHARACTERS.length; i++) {
 			_classifier.set(FORCE_TERMINATION_CHARACTERS.charCodeAt(i), CharacterClass.ForceTermination);
 		}
 
-		const CANNOT_END_WITH_CHARACTERS = '.,;:';
+		const CANNOT_END_WITH_CHARACTERS = '.;:';
 		for (let i = 0; i < CANNOT_END_WITH_CHARACTERS.length; i++) {
 			_classifier.set(CANNOT_END_WITH_CHARACTERS.charCodeAt(i), CharacterClass.CannotEndIn);
 		}


Test patch:
diff --git a/src/vs/editor/test/common/modes/linkComputer.test.ts b/src/vs/editor/test/common/modes/linkComputer.test.ts
--- a/src/vs/editor/test/common/modes/linkComputer.test.ts
+++ b/src/vs/editor/test/common/modes/linkComputer.test.ts
@@ -26,38 +26,29 @@ function myComputeLinks(lines: string[]): ILink[] {
 	return computeLinks(target);
 }
 
-function assertLink(text: string, extractedLink: string): void {
-	let startColumn = 0,
-		endColumn = 0,
-		chr: string,
-		i = 0;
-
-	for (i = 0; i < extractedLink.length; i++) {
-		chr = extractedLink.charAt(i);
-		if (chr !== ' ' && chr !== '\t') {
-			startColumn = i + 1;
-			break;
+function extractLinks(text: string): string {
+	const keep: boolean[] = [];
+	const links = myComputeLinks([text]);
+	for (const link of links) {
+		const startChar = link.range.startColumn - 1;
+		const endChar = link.range.endColumn - 1;
+		for (let char = startChar; char < endChar; char++) {
+			keep[char] = true;
 		}
 	}
-
-	for (i = extractedLink.length - 1; i >= 0; i--) {
-		chr = extractedLink.charAt(i);
-		if (chr !== ' ' && chr !== '\t') {
-			endColumn = i + 2;
-			break;
+	const result: string[] = [];
+	for (let i = 0; i < text.length; i++) {
+		if (keep[i]) {
+			result.push(text.charAt(i));
+		} else {
+			result.push(' ');
 		}
 	}
+	return result.join('');
+}
 
-	const r = myComputeLinks([text]);
-	assert.deepStrictEqual(r, [{
-		range: {
-			startLineNumber: 1,
-			startColumn: startColumn,
-			endLineNumber: 1,
-			endColumn: endColumn
-		},
-		url: extractedLink.substring(startColumn - 1, endColumn - 1)
-	}]);
+function assertLink(text: string, expectedLinks: string): void {
+	assert.deepStrictEqual(extractLinks(text), expectedLinks);
 }
 
 suite('Editor Modes - Link Computer', () => {
@@ -106,19 +97,19 @@ suite('Editor Modes - Link Computer', () => {
 
 		assertLink(
 			'(see http://foo.bar)',
-			'     http://foo.bar  '
+			'     http://foo.bar '
 		);
 		assertLink(
 			'[see http://foo.bar]',
-			'     http://foo.bar  '
+			'     http://foo.bar '
 		);
 		assertLink(
 			'{see http://foo.bar}',
-			'     http://foo.bar  '
+			'     http://foo.bar '
 		);
 		assertLink(
 			'<see http://foo.bar>',
-			'     http://foo.bar  '
+			'     http://foo.bar '
 		);
 		assertLink(
 			'<url>http://mylink.com</url>',
@@ -199,7 +190,7 @@ suite('Editor Modes - Link Computer', () => {
 	test('issue #62278: "Ctrl + click to follow link" for IPv6 URLs', () => {
 		assertLink(
 			'let x = "http://[::1]:5000/connect/token"',
-			'         http://[::1]:5000/connect/token  '
+			'         http://[::1]:5000/connect/token '
 		);
 	});
 
@@ -273,4 +264,15 @@ suite('Editor Modes - Link Computer', () => {
 			`                                   https://github.com/jeff-hykin/better-c-syntax/blob/master/autogenerated/c.tmLanguage.json  `,
 		);
 	});
+
+	test('issue #119696: Links shouldn\'t include commas', () => {
+		assertLink(
+			`https://apod.nasa.gov/apod/ap170720.html,IC 1396: Emission Nebula in Cepheus,https://apod.nasa.gov/apod/image/1707/MOSAIC_IC1396_HaSHO_blanco1024.jpg,https://apod.nasa.gov/apod/image/1707/MOSAIC_IC1396_HaSHO_blanco.jpg`,
+			`https://apod.nasa.gov/apod/ap170720.html                                     https://apod.nasa.gov/apod/image/1707/MOSAIC_IC1396_HaSHO_blanco1024.jpg https://apod.nasa.gov/apod/image/1707/MOSAIC_IC1396_HaSHO_blanco.jpg`
+		);
+		assertLink(
+			`https://apod.nasa.gov/apod/ap180402.html,"Moons, Rings, Shadows, Clouds: Saturn (Cassini)",https://apod.nasa.gov/apod/image/1804/SaturnRingsMoons_Cassini_967.jpg,https://apod.nasa.gov/apod/image/1804/SaturnRingsMoons_Cassini_967.jpg`,
+			`https://apod.nasa.gov/apod/ap180402.html                                                   https://apod.nasa.gov/apod/image/1804/SaturnRingsMoons_Cassini_967.jpg https://apod.nasa.gov/apod/image/1804/SaturnRingsMoons_Cassini_967.jpg`,
+		);
+	});
 });