Repository: microsoft/vscode (TypeScript)
Date: 2022-07-21 22:18:07+00:00
Task type: Bug Fix

Issue:
can't go to line and column by click terminal link if project path contains space
My project is under `/Users/yutengjing/Library/Application Support/Adobe/CEP/extensions/vscode-fe-helper`：

https://user-images.githubusercontent.com/41773861/179580458-2e804f82-5acc-4e07-b2c3-e1b246deebfa.mov



Reproduce steps:

1. using vscode open a folder which file path contains space, for example: `/Users/yutengjing/Library/Application Support/project-folder`
2. open an integrated terminal
3. echo `file/under/project.ts:line:col`
4. click the path link

Versions:

```
Version: 1.70.0-insider (Universal)
Commit: 1ccfe2bbe804a20a7c657ca42368987fd1adac58
Date: 2022-07-18T08:46:51.862Z
Electron: 18.3.5
Chromium: 100.0.4896.160
Node.js: 16.13.2
V8: 10.0.139.17-electron.0
OS: Darwin x64 21.5.0
```

@Tyriar


Issue discussion:
@tjx666 nice I can repro with the space in the name, thanks for following up 👍 

Solution patch:
diff --git a/src/vs/workbench/contrib/terminal/browser/links/terminalLinkOpeners.ts b/src/vs/workbench/contrib/terminal/browser/links/terminalLinkOpeners.ts
--- a/src/vs/workbench/contrib/terminal/browser/links/terminalLinkOpeners.ts
+++ b/src/vs/workbench/contrib/terminal/browser/links/terminalLinkOpeners.ts
@@ -23,6 +23,7 @@ import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/
 import { IHostService } from 'vs/workbench/services/host/browser/host';
 import { QueryBuilder } from 'vs/workbench/services/search/common/queryBuilder';
 import { ISearchService } from 'vs/workbench/services/search/common/search';
+import { basename } from 'vs/base/common/path';
 
 export class TerminalLocalFileLinkOpener implements ITerminalLinkOpener {
 	constructor(
@@ -35,7 +36,7 @@ export class TerminalLocalFileLinkOpener implements ITerminalLinkOpener {
 		if (!link.uri) {
 			throw new Error('Tried to open file link without a resolved URI');
 		}
-		const lineColumnInfo: ILineColumnInfo = this.extractLineColumnInfo(link.text);
+		const lineColumnInfo: ILineColumnInfo = this.extractLineColumnInfo(link.text, link.uri);
 		const selection: ITextEditorSelection = {
 			startLineNumber: lineColumnInfo.lineNumber,
 			startColumn: lineColumnInfo.columnNumber
@@ -51,12 +52,22 @@ export class TerminalLocalFileLinkOpener implements ITerminalLinkOpener {
 	 *
 	 * @param link Url link which may contain line and column number.
 	 */
-	extractLineColumnInfo(link: string): ILineColumnInfo {
+	extractLineColumnInfo(link: string, uri?: URI): ILineColumnInfo {
 		const lineColumnInfo: ILineColumnInfo = {
 			lineNumber: 1,
 			columnNumber: 1
 		};
 
+		// If a URI was passed in the exact file is known, sanitize the link text such that the
+		// folders and file name do not contain whitespace. The actual path isn't important in
+		// extracting the line and column from the regex so this is safe
+		if (uri) {
+			const fileName = basename(uri.path);
+			const index = link.indexOf(fileName);
+			const endIndex = index + fileName.length;
+			link = link.slice(0, endIndex).replace(/\s/g, '_') + link.slice(endIndex);
+		}
+
 		// The local link regex only works for non file:// links, check these for a simple
 		// `:line:col` suffix
 		if (link.startsWith('file://')) {


Test patch:
diff --git a/src/vs/workbench/contrib/terminal/test/browser/links/terminalLinkOpeners.test.ts b/src/vs/workbench/contrib/terminal/test/browser/links/terminalLinkOpeners.test.ts
--- a/src/vs/workbench/contrib/terminal/test/browser/links/terminalLinkOpeners.test.ts
+++ b/src/vs/workbench/contrib/terminal/test/browser/links/terminalLinkOpeners.test.ts
@@ -7,7 +7,7 @@ import { deepStrictEqual } from 'assert';
 import { Schemas } from 'vs/base/common/network';
 import { OperatingSystem } from 'vs/base/common/platform';
 import { URI } from 'vs/base/common/uri';
-import { ITextResourceEditorInput } from 'vs/platform/editor/common/editor';
+import { ITextEditorSelection, ITextResourceEditorInput } from 'vs/platform/editor/common/editor';
 import { IFileService, IFileStatWithPartialMetadata } from 'vs/platform/files/common/files';
 import { FileService } from 'vs/platform/files/common/fileService';
 import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock';
@@ -27,6 +27,7 @@ import { Terminal } from 'xterm';
 export interface ITerminalLinkActivationResult {
 	source: 'editor' | 'search';
 	link: string;
+	selection?: ITextEditorSelection;
 }
 
 class TestCommandDetectionCapability extends CommandDetectionCapability {
@@ -79,6 +80,10 @@ suite('Workbench - TerminalLinkOpeners', () => {
 					source: 'editor',
 					link: editor.resource?.toString()
 				};
+				// Only assert on selection if it's not the default value
+				if (editor.options?.selection && (editor.options.selection.startColumn !== 1 || editor.options.selection.startLineNumber !== 1)) {
+					activationResult.selection = editor.options.selection;
+				}
 			}
 		} as Partial<IEditorService>);
 		// /*editorServiceSpy = */instantiationService.spy(IEditorService, 'openEditor');
@@ -165,6 +170,28 @@ suite('Workbench - TerminalLinkOpeners', () => {
 			});
 		});
 
+		test('should extract line and column from links in a workspace containing spaces', async () => {
+			localFileOpener = instantiationService.createInstance(TerminalLocalFileLinkOpener, OperatingSystem.Linux);
+			const localFolderOpener = instantiationService.createInstance(TerminalLocalFolderInWorkspaceLinkOpener);
+			opener = instantiationService.createInstance(TerminalSearchLinkOpener, capabilities, Promise.resolve('/space folder'), localFileOpener, localFolderOpener, OperatingSystem.Linux);
+			fileService.setFiles([
+				URI.from({ scheme: Schemas.file, path: '/space folder/foo/bar.txt' })
+			]);
+			await opener.open({
+				text: './foo/bar.txt:10:5',
+				bufferRange: { start: { x: 1, y: 1 }, end: { x: 8, y: 1 } },
+				type: TerminalBuiltinLinkType.Search
+			});
+			deepStrictEqual(activationResult, {
+				link: 'file:///space%20folder/foo/bar.txt',
+				source: 'editor',
+				selection: {
+					startColumn: 5,
+					startLineNumber: 10
+				},
+			});
+		});
+
 		suite('macOS/Linux', () => {
 			setup(() => {
 				localFileOpener = instantiationService.createInstance(TerminalLocalFileLinkOpener, OperatingSystem.Linux);