Path: blob/main/extensions/copilot/src/util/vs/base/common/resources.ts
13405 views
//!!! DO NOT modify, this file was COPIED from 'microsoft/vscode'12/*---------------------------------------------------------------------------------------------3* Copyright (c) Microsoft Corporation. All rights reserved.4* Licensed under the MIT License. See License.txt in the project root for license information.5*--------------------------------------------------------------------------------------------*/67import { CharCode } from './charCode';8import * as extpath from './extpath';9import { Schemas } from './network';10import * as paths from './path';11import { isLinux, isWindows } from './platform';12import { compare as strCompare, equalsIgnoreCase } from './strings';13import { URI, uriToFsPath } from './uri';1415export function originalFSPath(uri: URI): string {16return uriToFsPath(uri, true);17}1819//#region IExtUri2021export interface IExtUri {2223// --- identity2425/**26* Compares two uris.27*28* @param uri1 Uri29* @param uri2 Uri30* @param ignoreFragment Ignore the fragment (defaults to `false`)31*/32compare(uri1: URI, uri2: URI, ignoreFragment?: boolean): number;3334/**35* Tests whether two uris are equal36*37* @param uri1 Uri38* @param uri2 Uri39* @param ignoreFragment Ignore the fragment (defaults to `false`)40*/41isEqual(uri1: URI | undefined, uri2: URI | undefined, ignoreFragment?: boolean): boolean;4243/**44* Tests whether a `candidate` URI is a parent or equal of a given `base` URI.45*46* @param base A uri which is "longer" or at least same length as `parentCandidate`47* @param parentCandidate A uri which is "shorter" or up to same length as `base`48* @param ignoreFragment Ignore the fragment (defaults to `false`)49*/50isEqualOrParent(base: URI, parentCandidate: URI, ignoreFragment?: boolean): boolean;5152/**53* Creates a key from a resource URI to be used to resource comparison and for resource maps.54* @see {@link ResourceMap}55* @param uri Uri56* @param ignoreFragment Ignore the fragment (defaults to `false`)57*/58getComparisonKey(uri: URI, ignoreFragment?: boolean): string;5960/**61* Whether the casing of the path-component of the uri should be ignored.62*/63ignorePathCasing(uri: URI): boolean;6465// --- path math6667basenameOrAuthority(resource: URI): string;6869/**70* Returns the basename of the path component of an uri.71* @param resource72*/73basename(resource: URI): string;7475/**76* Returns the extension of the path component of an uri.77* @param resource78*/79extname(resource: URI): string;80/**81* Return a URI representing the directory of a URI path.82*83* @param resource The input URI.84* @returns The URI representing the directory of the input URI.85*/86dirname(resource: URI): URI;87/**88* Join a URI path with path fragments and normalizes the resulting path.89*90* @param resource The input URI.91* @param pathFragment The path fragment to add to the URI path.92* @returns The resulting URI.93*/94joinPath(resource: URI, ...pathFragment: string[]): URI;95/**96* Normalizes the path part of a URI: Resolves `.` and `..` elements with directory names.97*98* @param resource The URI to normalize the path.99* @returns The URI with the normalized path.100*/101normalizePath(resource: URI): URI;102/**103*104* @param from105* @param to106*/107relativePath(from: URI, to: URI): string | undefined;108/**109* Resolves an absolute or relative path against a base URI.110* The path can be relative or absolute posix or a Windows path111*/112resolvePath(base: URI, path: string): URI;113114// --- misc115116/**117* Returns true if the URI path is absolute.118*/119isAbsolutePath(resource: URI): boolean;120/**121* Tests whether the two authorities are the same122*/123isEqualAuthority(a1: string, a2: string): boolean;124/**125* Returns true if the URI path has a trailing path separator126*/127hasTrailingPathSeparator(resource: URI, sep?: string): boolean;128/**129* Removes a trailing path separator, if there's one.130* Important: Doesn't remove the first slash, it would make the URI invalid131*/132removeTrailingPathSeparator(resource: URI, sep?: string): URI;133/**134* Adds a trailing path separator to the URI if there isn't one already.135* For example, c:\ would be unchanged, but c:\users would become c:\users\136*/137addTrailingPathSeparator(resource: URI, sep?: string): URI;138}139140export class ExtUri implements IExtUri {141142constructor(private _ignorePathCasing: (uri: URI) => boolean) { }143144compare(uri1: URI, uri2: URI, ignoreFragment: boolean = false): number {145if (uri1 === uri2) {146return 0;147}148return strCompare(this.getComparisonKey(uri1, ignoreFragment), this.getComparisonKey(uri2, ignoreFragment));149}150151isEqual(uri1: URI | undefined, uri2: URI | undefined, ignoreFragment: boolean = false): boolean {152if (uri1 === uri2) {153return true;154}155if (!uri1 || !uri2) {156return false;157}158return this.getComparisonKey(uri1, ignoreFragment) === this.getComparisonKey(uri2, ignoreFragment);159}160161getComparisonKey(uri: URI, ignoreFragment: boolean = false): string {162return uri.with({163path: this._ignorePathCasing(uri) ? uri.path.toLowerCase() : undefined,164fragment: ignoreFragment ? null : undefined165}).toString();166}167168ignorePathCasing(uri: URI): boolean {169return this._ignorePathCasing(uri);170}171172isEqualOrParent(base: URI, parentCandidate: URI, ignoreFragment: boolean = false): boolean {173if (base.scheme === parentCandidate.scheme) {174if (base.scheme === Schemas.file) {175return extpath.isEqualOrParent(originalFSPath(base), originalFSPath(parentCandidate), this._ignorePathCasing(base)) && base.query === parentCandidate.query && (ignoreFragment || base.fragment === parentCandidate.fragment);176}177if (isEqualAuthority(base.authority, parentCandidate.authority)) {178return extpath.isEqualOrParent(base.path, parentCandidate.path, this._ignorePathCasing(base), '/') && base.query === parentCandidate.query && (ignoreFragment || base.fragment === parentCandidate.fragment);179}180}181return false;182}183184// --- path math185186joinPath(resource: URI, ...pathFragment: string[]): URI {187return URI.joinPath(resource, ...pathFragment);188}189190basenameOrAuthority(resource: URI): string {191return basename(resource) || resource.authority;192}193194basename(resource: URI): string {195return paths.posix.basename(resource.path);196}197198extname(resource: URI): string {199return paths.posix.extname(resource.path);200}201202dirname(resource: URI): URI {203if (resource.path.length === 0) {204return resource;205}206let dirname;207if (resource.scheme === Schemas.file) {208dirname = URI.file(paths.dirname(originalFSPath(resource))).path;209} else {210dirname = paths.posix.dirname(resource.path);211if (resource.authority && dirname.length && dirname.charCodeAt(0) !== CharCode.Slash) {212console.error(`dirname("${resource.toString})) resulted in a relative path`);213dirname = '/'; // If a URI contains an authority component, then the path component must either be empty or begin with a CharCode.Slash ("/") character214}215}216return resource.with({217path: dirname218});219}220221normalizePath(resource: URI): URI {222if (!resource.path.length) {223return resource;224}225let normalizedPath: string;226if (resource.scheme === Schemas.file) {227normalizedPath = URI.file(paths.normalize(originalFSPath(resource))).path;228} else {229normalizedPath = paths.posix.normalize(resource.path);230}231return resource.with({232path: normalizedPath233});234}235236relativePath(from: URI, to: URI): string | undefined {237if (from.scheme !== to.scheme || !isEqualAuthority(from.authority, to.authority)) {238return undefined;239}240if (from.scheme === Schemas.file) {241const relativePath = paths.relative(originalFSPath(from), originalFSPath(to));242return isWindows ? extpath.toSlashes(relativePath) : relativePath;243}244let fromPath = from.path || '/';245const toPath = to.path || '/';246if (this._ignorePathCasing(from)) {247// make casing of fromPath match toPath248let i = 0;249for (const len = Math.min(fromPath.length, toPath.length); i < len; i++) {250if (fromPath.charCodeAt(i) !== toPath.charCodeAt(i)) {251if (fromPath.charAt(i).toLowerCase() !== toPath.charAt(i).toLowerCase()) {252break;253}254}255}256fromPath = toPath.substr(0, i) + fromPath.substr(i);257}258return paths.posix.relative(fromPath, toPath);259}260261resolvePath(base: URI, path: string): URI {262if (base.scheme === Schemas.file) {263const newURI = URI.file(paths.resolve(originalFSPath(base), path));264return base.with({265authority: newURI.authority,266path: newURI.path267});268}269path = extpath.toPosixPath(path); // we allow path to be a windows path270return base.with({271path: paths.posix.resolve(base.path, path)272});273}274275// --- misc276277isAbsolutePath(resource: URI): boolean {278return !!resource.path && resource.path[0] === '/';279}280281isEqualAuthority(a1: string | undefined, a2: string | undefined) {282return a1 === a2 || (a1 !== undefined && a2 !== undefined && equalsIgnoreCase(a1, a2));283}284285hasTrailingPathSeparator(resource: URI, sep: string = paths.sep): boolean {286if (resource.scheme === Schemas.file) {287const fsp = originalFSPath(resource);288return fsp.length > extpath.getRoot(fsp).length && fsp[fsp.length - 1] === sep;289} else {290const p = resource.path;291return (p.length > 1 && p.charCodeAt(p.length - 1) === CharCode.Slash) && !(/^[a-zA-Z]:(\/$|\\$)/.test(resource.fsPath)); // ignore the slash at offset 0292}293}294295removeTrailingPathSeparator(resource: URI, sep: string = paths.sep): URI {296// Make sure that the path isn't a drive letter. A trailing separator there is not removable.297if (hasTrailingPathSeparator(resource, sep)) {298return resource.with({ path: resource.path.substr(0, resource.path.length - 1) });299}300return resource;301}302303addTrailingPathSeparator(resource: URI, sep: string = paths.sep): URI {304let isRootSep: boolean = false;305if (resource.scheme === Schemas.file) {306const fsp = originalFSPath(resource);307isRootSep = ((fsp !== undefined) && (fsp.length === extpath.getRoot(fsp).length) && (fsp[fsp.length - 1] === sep));308} else {309sep = '/';310const p = resource.path;311isRootSep = p.length === 1 && p.charCodeAt(p.length - 1) === CharCode.Slash;312}313if (!isRootSep && !hasTrailingPathSeparator(resource, sep)) {314return resource.with({ path: resource.path + '/' });315}316return resource;317}318}319320321/**322* Unbiased utility that takes uris "as they are". This means it can be interchanged with323* uri#toString() usages. The following is true324* ```325* assertEqual(aUri.toString() === bUri.toString(), exturi.isEqual(aUri, bUri))326* ```327*/328export const extUri = new ExtUri(() => false);329330/**331* BIASED utility that _mostly_ ignored the case of urs paths. ONLY use this util if you332* understand what you are doing.333*334* This utility is INCOMPATIBLE with `uri.toString()`-usages and both CANNOT be used interchanged.335*336* When dealing with uris from files or documents, `extUri` (the unbiased friend)is sufficient337* because those uris come from a "trustworthy source". When creating unknown uris it's always338* better to use `IUriIdentityService` which exposes an `IExtUri`-instance which knows when path339* casing matters.340*/341export const extUriBiasedIgnorePathCase = new ExtUri(uri => {342// A file scheme resource is in the same platform as code, so ignore case for non linux platforms343// Resource can be from another platform. Lowering the case as an hack. Should come from File system provider344return uri.scheme === Schemas.file ? !isLinux : true;345});346347348/**349* BIASED utility that always ignores the casing of uris paths. ONLY use this util if you350* understand what you are doing.351*352* This utility is INCOMPATIBLE with `uri.toString()`-usages and both CANNOT be used interchanged.353*354* When dealing with uris from files or documents, `extUri` (the unbiased friend)is sufficient355* because those uris come from a "trustworthy source". When creating unknown uris it's always356* better to use `IUriIdentityService` which exposes an `IExtUri`-instance which knows when path357* casing matters.358*/359export const extUriIgnorePathCase = new ExtUri(_ => true);360361export const isEqual = extUri.isEqual.bind(extUri);362export const isEqualOrParent = extUri.isEqualOrParent.bind(extUri);363export const getComparisonKey = extUri.getComparisonKey.bind(extUri);364export const basenameOrAuthority = extUri.basenameOrAuthority.bind(extUri);365export const basename = extUri.basename.bind(extUri);366export const extname = extUri.extname.bind(extUri);367export const dirname = extUri.dirname.bind(extUri);368export const joinPath = extUri.joinPath.bind(extUri);369export const normalizePath = extUri.normalizePath.bind(extUri);370export const relativePath = extUri.relativePath.bind(extUri);371export const resolvePath = extUri.resolvePath.bind(extUri);372export const isAbsolutePath = extUri.isAbsolutePath.bind(extUri);373export const isEqualAuthority = extUri.isEqualAuthority.bind(extUri);374export const hasTrailingPathSeparator = extUri.hasTrailingPathSeparator.bind(extUri);375export const removeTrailingPathSeparator = extUri.removeTrailingPathSeparator.bind(extUri);376export const addTrailingPathSeparator = extUri.addTrailingPathSeparator.bind(extUri);377378//#endregion379380export function distinctParents<T>(items: T[], resourceAccessor: (item: T) => URI): T[] {381const distinctParents: T[] = [];382for (let i = 0; i < items.length; i++) {383const candidateResource = resourceAccessor(items[i]);384if (items.some((otherItem, index) => {385if (index === i) {386return false;387}388389return isEqualOrParent(candidateResource, resourceAccessor(otherItem));390})) {391continue;392}393394distinctParents.push(items[i]);395}396397return distinctParents;398}399400/**401* Data URI related helpers.402*/403export namespace DataUri {404405export const META_DATA_LABEL = 'label';406export const META_DATA_DESCRIPTION = 'description';407export const META_DATA_SIZE = 'size';408export const META_DATA_MIME = 'mime';409410export function parseMetaData(dataUri: URI): Map<string, string> {411const metadata = new Map<string, string>();412413// Given a URI of: data:image/png;size:2313;label:SomeLabel;description:SomeDescription;base64,77+9UE5...414// the metadata is: size:2313;label:SomeLabel;description:SomeDescription415const meta = dataUri.path.substring(dataUri.path.indexOf(';') + 1, dataUri.path.lastIndexOf(';'));416meta.split(';').forEach(property => {417const [key, value] = property.split(':');418if (key && value) {419metadata.set(key, value);420}421});422423// Given a URI of: data:image/png;size:2313;label:SomeLabel;description:SomeDescription;base64,77+9UE5...424// the mime is: image/png425const mime = dataUri.path.substring(0, dataUri.path.indexOf(';'));426if (mime) {427metadata.set(META_DATA_MIME, mime);428}429430return metadata;431}432}433434export function toLocalResource(resource: URI, authority: string | undefined, localScheme: string): URI {435if (authority) {436let path = resource.path;437if (path && path[0] !== paths.posix.sep) {438path = paths.posix.sep + path;439}440441return resource.with({ scheme: localScheme, authority, path });442}443444return resource.with({ scheme: localScheme });445}446447448