Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/extensions/copilot/src/util/vs/base/common/extpath.ts
13405 views
1
//!!! DO NOT modify, this file was COPIED from 'microsoft/vscode'
2
3
/*---------------------------------------------------------------------------------------------
4
* Copyright (c) Microsoft Corporation. All rights reserved.
5
* Licensed under the MIT License. See License.txt in the project root for license information.
6
*--------------------------------------------------------------------------------------------*/
7
8
import { CharCode } from './charCode';
9
import { isAbsolute, join, normalize, posix, sep } from './path';
10
import { isWindows } from './platform';
11
import { equalsIgnoreCase, rtrim, startsWithIgnoreCase } from './strings';
12
import { isNumber } from './types';
13
14
export function isPathSeparator(code: number) {
15
return code === CharCode.Slash || code === CharCode.Backslash;
16
}
17
18
/**
19
* Takes a Windows OS path and changes backward slashes to forward slashes.
20
* This should only be done for OS paths from Windows (or user provided paths potentially from Windows).
21
* Using it on a Linux or MaxOS path might change it.
22
*/
23
export function toSlashes(osPath: string) {
24
return osPath.replace(/[\\/]/g, posix.sep);
25
}
26
27
/**
28
* Takes a Windows OS path (using backward or forward slashes) and turns it into a posix path:
29
* - turns backward slashes into forward slashes
30
* - makes it absolute if it starts with a drive letter
31
* This should only be done for OS paths from Windows (or user provided paths potentially from Windows).
32
* Using it on a Linux or MaxOS path might change it.
33
*/
34
export function toPosixPath(osPath: string) {
35
if (osPath.indexOf('/') === -1) {
36
osPath = toSlashes(osPath);
37
}
38
if (/^[a-zA-Z]:(\/|$)/.test(osPath)) { // starts with a drive letter
39
osPath = '/' + osPath;
40
}
41
return osPath;
42
}
43
44
/**
45
* Computes the _root_ this path, like `getRoot('c:\files') === c:\`,
46
* `getRoot('files:///files/path') === files:///`,
47
* or `getRoot('\\server\shares\path') === \\server\shares\`
48
*/
49
export function getRoot(path: string, sep: string = posix.sep): string {
50
if (!path) {
51
return '';
52
}
53
54
const len = path.length;
55
const firstLetter = path.charCodeAt(0);
56
if (isPathSeparator(firstLetter)) {
57
if (isPathSeparator(path.charCodeAt(1))) {
58
// UNC candidate \\localhost\shares\ddd
59
// ^^^^^^^^^^^^^^^^^^^
60
if (!isPathSeparator(path.charCodeAt(2))) {
61
let pos = 3;
62
const start = pos;
63
for (; pos < len; pos++) {
64
if (isPathSeparator(path.charCodeAt(pos))) {
65
break;
66
}
67
}
68
if (start !== pos && !isPathSeparator(path.charCodeAt(pos + 1))) {
69
pos += 1;
70
for (; pos < len; pos++) {
71
if (isPathSeparator(path.charCodeAt(pos))) {
72
return path.slice(0, pos + 1) // consume this separator
73
.replace(/[\\/]/g, sep);
74
}
75
}
76
}
77
}
78
}
79
80
// /user/far
81
// ^
82
return sep;
83
84
} else if (isWindowsDriveLetter(firstLetter)) {
85
// check for windows drive letter c:\ or c:
86
87
if (path.charCodeAt(1) === CharCode.Colon) {
88
if (isPathSeparator(path.charCodeAt(2))) {
89
// C:\fff
90
// ^^^
91
return path.slice(0, 2) + sep;
92
} else {
93
// C:
94
// ^^
95
return path.slice(0, 2);
96
}
97
}
98
}
99
100
// check for URI
101
// scheme://authority/path
102
// ^^^^^^^^^^^^^^^^^^^
103
let pos = path.indexOf('://');
104
if (pos !== -1) {
105
pos += 3; // 3 -> "://".length
106
for (; pos < len; pos++) {
107
if (isPathSeparator(path.charCodeAt(pos))) {
108
return path.slice(0, pos + 1); // consume this separator
109
}
110
}
111
}
112
113
return '';
114
}
115
116
/**
117
* Check if the path follows this pattern: `\\hostname\sharename`.
118
*
119
* @see https://msdn.microsoft.com/en-us/library/gg465305.aspx
120
* @return A boolean indication if the path is a UNC path, on none-windows
121
* always false.
122
*/
123
export function isUNC(path: string): boolean {
124
if (!isWindows) {
125
// UNC is a windows concept
126
return false;
127
}
128
129
if (!path || path.length < 5) {
130
// at least \\a\b
131
return false;
132
}
133
134
let code = path.charCodeAt(0);
135
if (code !== CharCode.Backslash) {
136
return false;
137
}
138
139
code = path.charCodeAt(1);
140
141
if (code !== CharCode.Backslash) {
142
return false;
143
}
144
145
let pos = 2;
146
const start = pos;
147
for (; pos < path.length; pos++) {
148
code = path.charCodeAt(pos);
149
if (code === CharCode.Backslash) {
150
break;
151
}
152
}
153
154
if (start === pos) {
155
return false;
156
}
157
158
code = path.charCodeAt(pos + 1);
159
160
if (isNaN(code) || code === CharCode.Backslash) {
161
return false;
162
}
163
164
return true;
165
}
166
167
// Reference: https://en.wikipedia.org/wiki/Filename
168
const WINDOWS_INVALID_FILE_CHARS = /[\\/:\*\?"<>\|]/g;
169
const UNIX_INVALID_FILE_CHARS = /[/]/g;
170
const WINDOWS_FORBIDDEN_NAMES = /^(con|prn|aux|clock\$|nul|lpt[0-9]|com[0-9])(\.(.*?))?$/i;
171
export function isValidBasename(name: string | null | undefined, isWindowsOS: boolean = isWindows): boolean {
172
const invalidFileChars = isWindowsOS ? WINDOWS_INVALID_FILE_CHARS : UNIX_INVALID_FILE_CHARS;
173
174
if (!name || name.length === 0 || /^\s+$/.test(name)) {
175
return false; // require a name that is not just whitespace
176
}
177
178
invalidFileChars.lastIndex = 0; // the holy grail of software development
179
if (invalidFileChars.test(name)) {
180
return false; // check for certain invalid file characters
181
}
182
183
if (isWindowsOS && WINDOWS_FORBIDDEN_NAMES.test(name)) {
184
return false; // check for certain invalid file names
185
}
186
187
if (name === '.' || name === '..') {
188
return false; // check for reserved values
189
}
190
191
if (isWindowsOS && name[name.length - 1] === '.') {
192
return false; // Windows: file cannot end with a "."
193
}
194
195
if (isWindowsOS && name.length !== name.trim().length) {
196
return false; // Windows: file cannot end with a whitespace
197
}
198
199
if (name.length > 255) {
200
return false; // most file systems do not allow files > 255 length
201
}
202
203
return true;
204
}
205
206
/**
207
* @deprecated please use `IUriIdentityService.extUri.isEqual` instead. If you are
208
* in a context without services, consider to pass down the `extUri` from the outside
209
* or use `extUriBiasedIgnorePathCase` if you know what you are doing.
210
*/
211
export function isEqual(pathA: string, pathB: string, ignoreCase?: boolean): boolean {
212
const identityEquals = (pathA === pathB);
213
if (!ignoreCase || identityEquals) {
214
return identityEquals;
215
}
216
217
if (!pathA || !pathB) {
218
return false;
219
}
220
221
return equalsIgnoreCase(pathA, pathB);
222
}
223
224
/**
225
* @deprecated please use `IUriIdentityService.extUri.isEqualOrParent` instead. If
226
* you are in a context without services, consider to pass down the `extUri` from the
227
* outside, or use `extUriBiasedIgnorePathCase` if you know what you are doing.
228
*/
229
export function isEqualOrParent(base: string, parentCandidate: string, ignoreCase?: boolean, separator = sep): boolean {
230
if (base === parentCandidate) {
231
return true;
232
}
233
234
if (!base || !parentCandidate) {
235
return false;
236
}
237
238
if (parentCandidate.length > base.length) {
239
return false;
240
}
241
242
if (ignoreCase) {
243
const beginsWith = startsWithIgnoreCase(base, parentCandidate);
244
if (!beginsWith) {
245
return false;
246
}
247
248
if (parentCandidate.length === base.length) {
249
return true; // same path, different casing
250
}
251
252
let sepOffset = parentCandidate.length;
253
if (parentCandidate.charAt(parentCandidate.length - 1) === separator) {
254
sepOffset--; // adjust the expected sep offset in case our candidate already ends in separator character
255
}
256
257
return base.charAt(sepOffset) === separator;
258
}
259
260
if (parentCandidate.charAt(parentCandidate.length - 1) !== separator) {
261
parentCandidate += separator;
262
}
263
264
return base.indexOf(parentCandidate) === 0;
265
}
266
267
export function isWindowsDriveLetter(char0: number): boolean {
268
return char0 >= CharCode.A && char0 <= CharCode.Z || char0 >= CharCode.a && char0 <= CharCode.z;
269
}
270
271
export function sanitizeFilePath(candidate: string, cwd: string): string {
272
273
// Special case: allow to open a drive letter without trailing backslash
274
if (isWindows && candidate.endsWith(':')) {
275
candidate += sep;
276
}
277
278
// Ensure absolute
279
if (!isAbsolute(candidate)) {
280
candidate = join(cwd, candidate);
281
}
282
283
// Ensure normalized
284
candidate = normalize(candidate);
285
286
// Ensure no trailing slash/backslash
287
return removeTrailingPathSeparator(candidate);
288
}
289
290
export function removeTrailingPathSeparator(candidate: string): string {
291
if (isWindows) {
292
candidate = rtrim(candidate, sep);
293
294
// Special case: allow to open drive root ('C:\')
295
if (candidate.endsWith(':')) {
296
candidate += sep;
297
}
298
299
} else {
300
candidate = rtrim(candidate, sep);
301
302
// Special case: allow to open root ('/')
303
if (!candidate) {
304
candidate = sep;
305
}
306
}
307
308
return candidate;
309
}
310
311
export function isRootOrDriveLetter(path: string): boolean {
312
const pathNormalized = normalize(path);
313
314
if (isWindows) {
315
if (path.length > 3) {
316
return false;
317
}
318
319
return hasDriveLetter(pathNormalized) &&
320
(path.length === 2 || pathNormalized.charCodeAt(2) === CharCode.Backslash);
321
}
322
323
return pathNormalized === posix.sep;
324
}
325
326
export function hasDriveLetter(path: string, isWindowsOS: boolean = isWindows): boolean {
327
if (isWindowsOS) {
328
return isWindowsDriveLetter(path.charCodeAt(0)) && path.charCodeAt(1) === CharCode.Colon;
329
}
330
331
return false;
332
}
333
334
export function getDriveLetter(path: string, isWindowsOS: boolean = isWindows): string | undefined {
335
return hasDriveLetter(path, isWindowsOS) ? path[0] : undefined;
336
}
337
338
export function indexOfPath(path: string, candidate: string, ignoreCase?: boolean): number {
339
if (candidate.length > path.length) {
340
return -1;
341
}
342
343
if (path === candidate) {
344
return 0;
345
}
346
347
if (ignoreCase) {
348
path = path.toLowerCase();
349
candidate = candidate.toLowerCase();
350
}
351
352
return path.indexOf(candidate);
353
}
354
355
export interface IPathWithLineAndColumn {
356
path: string;
357
line?: number;
358
column?: number;
359
}
360
361
export function parseLineAndColumnAware(rawPath: string): IPathWithLineAndColumn {
362
const segments = rawPath.split(':'); // C:\file.txt:<line>:<column>
363
364
let path: string | undefined;
365
let line: number | undefined;
366
let column: number | undefined;
367
368
for (const segment of segments) {
369
const segmentAsNumber = Number(segment);
370
if (!isNumber(segmentAsNumber)) {
371
path = path ? [path, segment].join(':') : segment; // a colon can well be part of a path (e.g. C:\...)
372
} else if (line === undefined) {
373
line = segmentAsNumber;
374
} else if (column === undefined) {
375
column = segmentAsNumber;
376
}
377
}
378
379
if (!path) {
380
throw new Error('Format for `--goto` should be: `FILE:LINE(:COLUMN)`');
381
}
382
383
return {
384
path,
385
line: line !== undefined ? line : undefined,
386
column: column !== undefined ? column : line !== undefined ? 1 : undefined // if we have a line, make sure column is also set
387
};
388
}
389
390
const pathChars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
391
const windowsSafePathFirstChars = 'BDEFGHIJKMOQRSTUVWXYZbdefghijkmoqrstuvwxyz0123456789';
392
393
export function randomPath(parent?: string, prefix?: string, randomLength = 8): string {
394
let suffix = '';
395
for (let i = 0; i < randomLength; i++) {
396
let pathCharsTouse: string;
397
if (i === 0 && isWindows && !prefix && (randomLength === 3 || randomLength === 4)) {
398
399
// Windows has certain reserved file names that cannot be used, such
400
// as AUX, CON, PRN, etc. We want to avoid generating a random name
401
// that matches that pattern, so we use a different set of characters
402
// for the first character of the name that does not include any of
403
// the reserved names first characters.
404
405
pathCharsTouse = windowsSafePathFirstChars;
406
} else {
407
pathCharsTouse = pathChars;
408
}
409
410
suffix += pathCharsTouse.charAt(Math.floor(Math.random() * pathCharsTouse.length));
411
}
412
413
let randomFileName: string;
414
if (prefix) {
415
randomFileName = `${prefix}-${suffix}`;
416
} else {
417
randomFileName = suffix;
418
}
419
420
if (parent) {
421
return join(parent, randomFileName);
422
}
423
424
return randomFileName;
425
}
426
427