This repository was archived by the owner on May 20, 2026. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 2k
Expand file tree
/
Copy pathpromptPathRepresentationServiceNode.ts
More file actions
148 lines (131 loc) · 4.83 KB
/
Copy pathpromptPathRepresentationServiceNode.ts
File metadata and controls
148 lines (131 loc) · 4.83 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { execFileSync } from 'child_process';
import type { Uri } from 'vscode';
import { ILogService } from '../../../platform/log/common/logService';
import { LRUCache } from '../../../util/vs/base/common/map';
import { PromptPathRepresentationService } from '../common/promptPathRepresentationService';
/**
* Pattern to detect 8.3 short filename segments (e.g., PROGRA~1, DOCUME~2)
* Format: 1-6 characters, followed by tilde and 1+ digits
*/
const SHORT_NAME_SEGMENT_PATTERN = /^[^~]{1,6}~\d+$/i;
/**
* Checks if a path segment looks like an 8.3 short filename
*/
function isShortNameSegment(segment: string): boolean {
// Remove extension if present for checking the base name
const dotIndex = segment.lastIndexOf('.');
const baseName = dotIndex > 0 ? segment.substring(0, dotIndex) : segment;
return SHORT_NAME_SEGMENT_PATTERN.test(baseName);
}
/**
* Checks if a Windows path contains any 8.3 short name segments
*/
function containsShortNameSegments(filePath: string): boolean {
const segments = filePath.split(/[\\/]/);
return segments.some(isShortNameSegment);
}
export class PromptPathRepresentationServiceNode extends PromptPathRepresentationService {
/**
* Cache mapping short path prefixes to their resolved long form.
* For example: "C:\PROGRA~1" -> "C:\Program Files"
* This allows reuse across files in the same short-named directory.
*/
private readonly _shortPrefixToLongPath = new LRUCache<string, string>(64);
constructor(
@ILogService private readonly _logService: ILogService,
) {
super();
}
override getFilePath(uri: Uri): string {
let filePath = super.getFilePath(uri);
if (this.isWindows()) {
filePath = this._tryResolveLongPath(filePath);
}
return filePath;
}
override resolveFilePath(filepath: string, predominantScheme?: string): Uri | undefined {
if (this.isWindows()) {
filepath = this._tryResolveLongPath(filepath);
}
return super.resolveFilePath(filepath, predominantScheme);
}
/**
* Attempts to resolve 8.3 short paths to their long form by resolving each
* short segment individually. This allows caching of directory prefixes
* for reuse across multiple files.
*
* For example, given paths:
* - `C:\PROGRA~1\foo.txt`
* - `C:\PROGRA~1\bar.txt`
*
* Only one shell call is needed because `C:\PROGRA~1` is cached after the first resolution.
*/
private _tryResolveLongPath(filePath: string): string {
if (!containsShortNameSegments(filePath)) {
return filePath;
}
// Detect the separator used in the path
const separator = filePath.includes('/') ? '/' : '\\';
const segments = filePath.split(/[\\/]/);
const resolvedSegments: string[] = [];
for (let i = 0; i < segments.length; i++) {
const segment = segments[i];
resolvedSegments.push(segment);
if (!isShortNameSegment(segment)) {
continue;
}
// Build the current prefix path (with short segment)
const shortPrefix = resolvedSegments.join(separator);
const cacheKey = shortPrefix.toLowerCase();
// Check cache first
const cached = this._shortPrefixToLongPath.get(cacheKey);
if (cached !== undefined) {
// Replace resolvedSegments with the cached long path segments
resolvedSegments.length = 0;
resolvedSegments.push(...cached.split(/[\\/]/));
continue;
}
// Resolve via shell
try {
const longPrefix = this._resolveLongPathViaShell(shortPrefix);
this._shortPrefixToLongPath.set(cacheKey, longPrefix);
// Replace resolvedSegments with the resolved long path segments
resolvedSegments.length = 0;
resolvedSegments.push(...longPrefix.split(/[\\/]/));
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
this._logService.warn(`Failed to resolve 8.3 short path "${shortPrefix}": ${errorMessage}`);
// Cache the short prefix to avoid repeated failures
this._shortPrefixToLongPath.set(cacheKey, shortPrefix);
}
}
return resolvedSegments.join(separator);
}
/**
* Uses PowerShell to resolve an 8.3 short path to its long form.
* Uses environment variables to safely pass the path without shell injection risks.
* Protected to allow overriding in tests.
*/
protected _resolveLongPathViaShell(shortPath: string): string {
const result = execFileSync('powershell.exe', [
'-NoProfile',
'-NonInteractive',
'-NoLogo',
'-Command',
'(Get-Item $env:VSCODE_SHORT_PATH).FullName'
], {
encoding: 'utf8',
timeout: 5000,
env: {
...process.env,
VSCODE_SHORT_PATH: shortPath
},
windowsHide: true
});
return result.trim();
}
}