Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/extensions/copilot/src/util/vs/base/common/resources.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 * as extpath from './extpath';
10
import { Schemas } from './network';
11
import * as paths from './path';
12
import { isLinux, isWindows } from './platform';
13
import { compare as strCompare, equalsIgnoreCase } from './strings';
14
import { URI, uriToFsPath } from './uri';
15
16
export function originalFSPath(uri: URI): string {
17
return uriToFsPath(uri, true);
18
}
19
20
//#region IExtUri
21
22
export interface IExtUri {
23
24
// --- identity
25
26
/**
27
* Compares two uris.
28
*
29
* @param uri1 Uri
30
* @param uri2 Uri
31
* @param ignoreFragment Ignore the fragment (defaults to `false`)
32
*/
33
compare(uri1: URI, uri2: URI, ignoreFragment?: boolean): number;
34
35
/**
36
* Tests whether two uris are equal
37
*
38
* @param uri1 Uri
39
* @param uri2 Uri
40
* @param ignoreFragment Ignore the fragment (defaults to `false`)
41
*/
42
isEqual(uri1: URI | undefined, uri2: URI | undefined, ignoreFragment?: boolean): boolean;
43
44
/**
45
* Tests whether a `candidate` URI is a parent or equal of a given `base` URI.
46
*
47
* @param base A uri which is "longer" or at least same length as `parentCandidate`
48
* @param parentCandidate A uri which is "shorter" or up to same length as `base`
49
* @param ignoreFragment Ignore the fragment (defaults to `false`)
50
*/
51
isEqualOrParent(base: URI, parentCandidate: URI, ignoreFragment?: boolean): boolean;
52
53
/**
54
* Creates a key from a resource URI to be used to resource comparison and for resource maps.
55
* @see {@link ResourceMap}
56
* @param uri Uri
57
* @param ignoreFragment Ignore the fragment (defaults to `false`)
58
*/
59
getComparisonKey(uri: URI, ignoreFragment?: boolean): string;
60
61
/**
62
* Whether the casing of the path-component of the uri should be ignored.
63
*/
64
ignorePathCasing(uri: URI): boolean;
65
66
// --- path math
67
68
basenameOrAuthority(resource: URI): string;
69
70
/**
71
* Returns the basename of the path component of an uri.
72
* @param resource
73
*/
74
basename(resource: URI): string;
75
76
/**
77
* Returns the extension of the path component of an uri.
78
* @param resource
79
*/
80
extname(resource: URI): string;
81
/**
82
* Return a URI representing the directory of a URI path.
83
*
84
* @param resource The input URI.
85
* @returns The URI representing the directory of the input URI.
86
*/
87
dirname(resource: URI): URI;
88
/**
89
* Join a URI path with path fragments and normalizes the resulting path.
90
*
91
* @param resource The input URI.
92
* @param pathFragment The path fragment to add to the URI path.
93
* @returns The resulting URI.
94
*/
95
joinPath(resource: URI, ...pathFragment: string[]): URI;
96
/**
97
* Normalizes the path part of a URI: Resolves `.` and `..` elements with directory names.
98
*
99
* @param resource The URI to normalize the path.
100
* @returns The URI with the normalized path.
101
*/
102
normalizePath(resource: URI): URI;
103
/**
104
*
105
* @param from
106
* @param to
107
*/
108
relativePath(from: URI, to: URI): string | undefined;
109
/**
110
* Resolves an absolute or relative path against a base URI.
111
* The path can be relative or absolute posix or a Windows path
112
*/
113
resolvePath(base: URI, path: string): URI;
114
115
// --- misc
116
117
/**
118
* Returns true if the URI path is absolute.
119
*/
120
isAbsolutePath(resource: URI): boolean;
121
/**
122
* Tests whether the two authorities are the same
123
*/
124
isEqualAuthority(a1: string, a2: string): boolean;
125
/**
126
* Returns true if the URI path has a trailing path separator
127
*/
128
hasTrailingPathSeparator(resource: URI, sep?: string): boolean;
129
/**
130
* Removes a trailing path separator, if there's one.
131
* Important: Doesn't remove the first slash, it would make the URI invalid
132
*/
133
removeTrailingPathSeparator(resource: URI, sep?: string): URI;
134
/**
135
* Adds a trailing path separator to the URI if there isn't one already.
136
* For example, c:\ would be unchanged, but c:\users would become c:\users\
137
*/
138
addTrailingPathSeparator(resource: URI, sep?: string): URI;
139
}
140
141
export class ExtUri implements IExtUri {
142
143
constructor(private _ignorePathCasing: (uri: URI) => boolean) { }
144
145
compare(uri1: URI, uri2: URI, ignoreFragment: boolean = false): number {
146
if (uri1 === uri2) {
147
return 0;
148
}
149
return strCompare(this.getComparisonKey(uri1, ignoreFragment), this.getComparisonKey(uri2, ignoreFragment));
150
}
151
152
isEqual(uri1: URI | undefined, uri2: URI | undefined, ignoreFragment: boolean = false): boolean {
153
if (uri1 === uri2) {
154
return true;
155
}
156
if (!uri1 || !uri2) {
157
return false;
158
}
159
return this.getComparisonKey(uri1, ignoreFragment) === this.getComparisonKey(uri2, ignoreFragment);
160
}
161
162
getComparisonKey(uri: URI, ignoreFragment: boolean = false): string {
163
return uri.with({
164
path: this._ignorePathCasing(uri) ? uri.path.toLowerCase() : undefined,
165
fragment: ignoreFragment ? null : undefined
166
}).toString();
167
}
168
169
ignorePathCasing(uri: URI): boolean {
170
return this._ignorePathCasing(uri);
171
}
172
173
isEqualOrParent(base: URI, parentCandidate: URI, ignoreFragment: boolean = false): boolean {
174
if (base.scheme === parentCandidate.scheme) {
175
if (base.scheme === Schemas.file) {
176
return extpath.isEqualOrParent(originalFSPath(base), originalFSPath(parentCandidate), this._ignorePathCasing(base)) && base.query === parentCandidate.query && (ignoreFragment || base.fragment === parentCandidate.fragment);
177
}
178
if (isEqualAuthority(base.authority, parentCandidate.authority)) {
179
return extpath.isEqualOrParent(base.path, parentCandidate.path, this._ignorePathCasing(base), '/') && base.query === parentCandidate.query && (ignoreFragment || base.fragment === parentCandidate.fragment);
180
}
181
}
182
return false;
183
}
184
185
// --- path math
186
187
joinPath(resource: URI, ...pathFragment: string[]): URI {
188
return URI.joinPath(resource, ...pathFragment);
189
}
190
191
basenameOrAuthority(resource: URI): string {
192
return basename(resource) || resource.authority;
193
}
194
195
basename(resource: URI): string {
196
return paths.posix.basename(resource.path);
197
}
198
199
extname(resource: URI): string {
200
return paths.posix.extname(resource.path);
201
}
202
203
dirname(resource: URI): URI {
204
if (resource.path.length === 0) {
205
return resource;
206
}
207
let dirname;
208
if (resource.scheme === Schemas.file) {
209
dirname = URI.file(paths.dirname(originalFSPath(resource))).path;
210
} else {
211
dirname = paths.posix.dirname(resource.path);
212
if (resource.authority && dirname.length && dirname.charCodeAt(0) !== CharCode.Slash) {
213
console.error(`dirname("${resource.toString})) resulted in a relative path`);
214
dirname = '/'; // If a URI contains an authority component, then the path component must either be empty or begin with a CharCode.Slash ("/") character
215
}
216
}
217
return resource.with({
218
path: dirname
219
});
220
}
221
222
normalizePath(resource: URI): URI {
223
if (!resource.path.length) {
224
return resource;
225
}
226
let normalizedPath: string;
227
if (resource.scheme === Schemas.file) {
228
normalizedPath = URI.file(paths.normalize(originalFSPath(resource))).path;
229
} else {
230
normalizedPath = paths.posix.normalize(resource.path);
231
}
232
return resource.with({
233
path: normalizedPath
234
});
235
}
236
237
relativePath(from: URI, to: URI): string | undefined {
238
if (from.scheme !== to.scheme || !isEqualAuthority(from.authority, to.authority)) {
239
return undefined;
240
}
241
if (from.scheme === Schemas.file) {
242
const relativePath = paths.relative(originalFSPath(from), originalFSPath(to));
243
return isWindows ? extpath.toSlashes(relativePath) : relativePath;
244
}
245
let fromPath = from.path || '/';
246
const toPath = to.path || '/';
247
if (this._ignorePathCasing(from)) {
248
// make casing of fromPath match toPath
249
let i = 0;
250
for (const len = Math.min(fromPath.length, toPath.length); i < len; i++) {
251
if (fromPath.charCodeAt(i) !== toPath.charCodeAt(i)) {
252
if (fromPath.charAt(i).toLowerCase() !== toPath.charAt(i).toLowerCase()) {
253
break;
254
}
255
}
256
}
257
fromPath = toPath.substr(0, i) + fromPath.substr(i);
258
}
259
return paths.posix.relative(fromPath, toPath);
260
}
261
262
resolvePath(base: URI, path: string): URI {
263
if (base.scheme === Schemas.file) {
264
const newURI = URI.file(paths.resolve(originalFSPath(base), path));
265
return base.with({
266
authority: newURI.authority,
267
path: newURI.path
268
});
269
}
270
path = extpath.toPosixPath(path); // we allow path to be a windows path
271
return base.with({
272
path: paths.posix.resolve(base.path, path)
273
});
274
}
275
276
// --- misc
277
278
isAbsolutePath(resource: URI): boolean {
279
return !!resource.path && resource.path[0] === '/';
280
}
281
282
isEqualAuthority(a1: string | undefined, a2: string | undefined) {
283
return a1 === a2 || (a1 !== undefined && a2 !== undefined && equalsIgnoreCase(a1, a2));
284
}
285
286
hasTrailingPathSeparator(resource: URI, sep: string = paths.sep): boolean {
287
if (resource.scheme === Schemas.file) {
288
const fsp = originalFSPath(resource);
289
return fsp.length > extpath.getRoot(fsp).length && fsp[fsp.length - 1] === sep;
290
} else {
291
const p = resource.path;
292
return (p.length > 1 && p.charCodeAt(p.length - 1) === CharCode.Slash) && !(/^[a-zA-Z]:(\/$|\\$)/.test(resource.fsPath)); // ignore the slash at offset 0
293
}
294
}
295
296
removeTrailingPathSeparator(resource: URI, sep: string = paths.sep): URI {
297
// Make sure that the path isn't a drive letter. A trailing separator there is not removable.
298
if (hasTrailingPathSeparator(resource, sep)) {
299
return resource.with({ path: resource.path.substr(0, resource.path.length - 1) });
300
}
301
return resource;
302
}
303
304
addTrailingPathSeparator(resource: URI, sep: string = paths.sep): URI {
305
let isRootSep: boolean = false;
306
if (resource.scheme === Schemas.file) {
307
const fsp = originalFSPath(resource);
308
isRootSep = ((fsp !== undefined) && (fsp.length === extpath.getRoot(fsp).length) && (fsp[fsp.length - 1] === sep));
309
} else {
310
sep = '/';
311
const p = resource.path;
312
isRootSep = p.length === 1 && p.charCodeAt(p.length - 1) === CharCode.Slash;
313
}
314
if (!isRootSep && !hasTrailingPathSeparator(resource, sep)) {
315
return resource.with({ path: resource.path + '/' });
316
}
317
return resource;
318
}
319
}
320
321
322
/**
323
* Unbiased utility that takes uris "as they are". This means it can be interchanged with
324
* uri#toString() usages. The following is true
325
* ```
326
* assertEqual(aUri.toString() === bUri.toString(), exturi.isEqual(aUri, bUri))
327
* ```
328
*/
329
export const extUri = new ExtUri(() => false);
330
331
/**
332
* BIASED utility that _mostly_ ignored the case of urs paths. ONLY use this util if you
333
* understand what you are doing.
334
*
335
* This utility is INCOMPATIBLE with `uri.toString()`-usages and both CANNOT be used interchanged.
336
*
337
* When dealing with uris from files or documents, `extUri` (the unbiased friend)is sufficient
338
* because those uris come from a "trustworthy source". When creating unknown uris it's always
339
* better to use `IUriIdentityService` which exposes an `IExtUri`-instance which knows when path
340
* casing matters.
341
*/
342
export const extUriBiasedIgnorePathCase = new ExtUri(uri => {
343
// A file scheme resource is in the same platform as code, so ignore case for non linux platforms
344
// Resource can be from another platform. Lowering the case as an hack. Should come from File system provider
345
return uri.scheme === Schemas.file ? !isLinux : true;
346
});
347
348
349
/**
350
* BIASED utility that always ignores the casing of uris paths. ONLY use this util if you
351
* understand what you are doing.
352
*
353
* This utility is INCOMPATIBLE with `uri.toString()`-usages and both CANNOT be used interchanged.
354
*
355
* When dealing with uris from files or documents, `extUri` (the unbiased friend)is sufficient
356
* because those uris come from a "trustworthy source". When creating unknown uris it's always
357
* better to use `IUriIdentityService` which exposes an `IExtUri`-instance which knows when path
358
* casing matters.
359
*/
360
export const extUriIgnorePathCase = new ExtUri(_ => true);
361
362
export const isEqual = extUri.isEqual.bind(extUri);
363
export const isEqualOrParent = extUri.isEqualOrParent.bind(extUri);
364
export const getComparisonKey = extUri.getComparisonKey.bind(extUri);
365
export const basenameOrAuthority = extUri.basenameOrAuthority.bind(extUri);
366
export const basename = extUri.basename.bind(extUri);
367
export const extname = extUri.extname.bind(extUri);
368
export const dirname = extUri.dirname.bind(extUri);
369
export const joinPath = extUri.joinPath.bind(extUri);
370
export const normalizePath = extUri.normalizePath.bind(extUri);
371
export const relativePath = extUri.relativePath.bind(extUri);
372
export const resolvePath = extUri.resolvePath.bind(extUri);
373
export const isAbsolutePath = extUri.isAbsolutePath.bind(extUri);
374
export const isEqualAuthority = extUri.isEqualAuthority.bind(extUri);
375
export const hasTrailingPathSeparator = extUri.hasTrailingPathSeparator.bind(extUri);
376
export const removeTrailingPathSeparator = extUri.removeTrailingPathSeparator.bind(extUri);
377
export const addTrailingPathSeparator = extUri.addTrailingPathSeparator.bind(extUri);
378
379
//#endregion
380
381
export function distinctParents<T>(items: T[], resourceAccessor: (item: T) => URI): T[] {
382
const distinctParents: T[] = [];
383
for (let i = 0; i < items.length; i++) {
384
const candidateResource = resourceAccessor(items[i]);
385
if (items.some((otherItem, index) => {
386
if (index === i) {
387
return false;
388
}
389
390
return isEqualOrParent(candidateResource, resourceAccessor(otherItem));
391
})) {
392
continue;
393
}
394
395
distinctParents.push(items[i]);
396
}
397
398
return distinctParents;
399
}
400
401
/**
402
* Data URI related helpers.
403
*/
404
export namespace DataUri {
405
406
export const META_DATA_LABEL = 'label';
407
export const META_DATA_DESCRIPTION = 'description';
408
export const META_DATA_SIZE = 'size';
409
export const META_DATA_MIME = 'mime';
410
411
export function parseMetaData(dataUri: URI): Map<string, string> {
412
const metadata = new Map<string, string>();
413
414
// Given a URI of: data:image/png;size:2313;label:SomeLabel;description:SomeDescription;base64,77+9UE5...
415
// the metadata is: size:2313;label:SomeLabel;description:SomeDescription
416
const meta = dataUri.path.substring(dataUri.path.indexOf(';') + 1, dataUri.path.lastIndexOf(';'));
417
meta.split(';').forEach(property => {
418
const [key, value] = property.split(':');
419
if (key && value) {
420
metadata.set(key, value);
421
}
422
});
423
424
// Given a URI of: data:image/png;size:2313;label:SomeLabel;description:SomeDescription;base64,77+9UE5...
425
// the mime is: image/png
426
const mime = dataUri.path.substring(0, dataUri.path.indexOf(';'));
427
if (mime) {
428
metadata.set(META_DATA_MIME, mime);
429
}
430
431
return metadata;
432
}
433
}
434
435
export function toLocalResource(resource: URI, authority: string | undefined, localScheme: string): URI {
436
if (authority) {
437
let path = resource.path;
438
if (path && path[0] !== paths.posix.sep) {
439
path = paths.posix.sep + path;
440
}
441
442
return resource.with({ scheme: localScheme, authority, path });
443
}
444
445
return resource.with({ scheme: localScheme });
446
}
447
448