resources.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256
  1. /*---------------------------------------------------------------------------------------------
  2. * Copyright (c) Microsoft Corporation. All rights reserved.
  3. * Licensed under the MIT License. See License.txt in the project root for license information.
  4. *--------------------------------------------------------------------------------------------*/
  5. import * as extpath from './extpath.js';
  6. import { Schemas } from './network.js';
  7. import * as paths from './path.js';
  8. import { isLinux, isWindows } from './platform.js';
  9. import { compare as strCompare, equalsIgnoreCase } from './strings.js';
  10. import { URI, uriToFsPath } from './uri.js';
  11. export function originalFSPath(uri) {
  12. return uriToFsPath(uri, true);
  13. }
  14. export class ExtUri {
  15. constructor(_ignorePathCasing) {
  16. this._ignorePathCasing = _ignorePathCasing;
  17. }
  18. compare(uri1, uri2, ignoreFragment = false) {
  19. if (uri1 === uri2) {
  20. return 0;
  21. }
  22. return strCompare(this.getComparisonKey(uri1, ignoreFragment), this.getComparisonKey(uri2, ignoreFragment));
  23. }
  24. isEqual(uri1, uri2, ignoreFragment = false) {
  25. if (uri1 === uri2) {
  26. return true;
  27. }
  28. if (!uri1 || !uri2) {
  29. return false;
  30. }
  31. return this.getComparisonKey(uri1, ignoreFragment) === this.getComparisonKey(uri2, ignoreFragment);
  32. }
  33. getComparisonKey(uri, ignoreFragment = false) {
  34. return uri.with({
  35. path: this._ignorePathCasing(uri) ? uri.path.toLowerCase() : undefined,
  36. fragment: ignoreFragment ? null : undefined
  37. }).toString();
  38. }
  39. isEqualOrParent(base, parentCandidate, ignoreFragment = false) {
  40. if (base.scheme === parentCandidate.scheme) {
  41. if (base.scheme === Schemas.file) {
  42. return extpath.isEqualOrParent(originalFSPath(base), originalFSPath(parentCandidate), this._ignorePathCasing(base)) && base.query === parentCandidate.query && (ignoreFragment || base.fragment === parentCandidate.fragment);
  43. }
  44. if (isEqualAuthority(base.authority, parentCandidate.authority)) {
  45. return extpath.isEqualOrParent(base.path, parentCandidate.path, this._ignorePathCasing(base), '/') && base.query === parentCandidate.query && (ignoreFragment || base.fragment === parentCandidate.fragment);
  46. }
  47. }
  48. return false;
  49. }
  50. // --- path math
  51. joinPath(resource, ...pathFragment) {
  52. return URI.joinPath(resource, ...pathFragment);
  53. }
  54. basenameOrAuthority(resource) {
  55. return basename(resource) || resource.authority;
  56. }
  57. basename(resource) {
  58. return paths.posix.basename(resource.path);
  59. }
  60. extname(resource) {
  61. return paths.posix.extname(resource.path);
  62. }
  63. dirname(resource) {
  64. if (resource.path.length === 0) {
  65. return resource;
  66. }
  67. let dirname;
  68. if (resource.scheme === Schemas.file) {
  69. dirname = URI.file(paths.dirname(originalFSPath(resource))).path;
  70. }
  71. else {
  72. dirname = paths.posix.dirname(resource.path);
  73. if (resource.authority && dirname.length && dirname.charCodeAt(0) !== 47 /* CharCode.Slash */) {
  74. console.error(`dirname("${resource.toString})) resulted in a relative path`);
  75. dirname = '/'; // If a URI contains an authority component, then the path component must either be empty or begin with a CharCode.Slash ("/") character
  76. }
  77. }
  78. return resource.with({
  79. path: dirname
  80. });
  81. }
  82. normalizePath(resource) {
  83. if (!resource.path.length) {
  84. return resource;
  85. }
  86. let normalizedPath;
  87. if (resource.scheme === Schemas.file) {
  88. normalizedPath = URI.file(paths.normalize(originalFSPath(resource))).path;
  89. }
  90. else {
  91. normalizedPath = paths.posix.normalize(resource.path);
  92. }
  93. return resource.with({
  94. path: normalizedPath
  95. });
  96. }
  97. relativePath(from, to) {
  98. if (from.scheme !== to.scheme || !isEqualAuthority(from.authority, to.authority)) {
  99. return undefined;
  100. }
  101. if (from.scheme === Schemas.file) {
  102. const relativePath = paths.relative(originalFSPath(from), originalFSPath(to));
  103. return isWindows ? extpath.toSlashes(relativePath) : relativePath;
  104. }
  105. let fromPath = from.path || '/';
  106. const toPath = to.path || '/';
  107. if (this._ignorePathCasing(from)) {
  108. // make casing of fromPath match toPath
  109. let i = 0;
  110. for (const len = Math.min(fromPath.length, toPath.length); i < len; i++) {
  111. if (fromPath.charCodeAt(i) !== toPath.charCodeAt(i)) {
  112. if (fromPath.charAt(i).toLowerCase() !== toPath.charAt(i).toLowerCase()) {
  113. break;
  114. }
  115. }
  116. }
  117. fromPath = toPath.substr(0, i) + fromPath.substr(i);
  118. }
  119. return paths.posix.relative(fromPath, toPath);
  120. }
  121. resolvePath(base, path) {
  122. if (base.scheme === Schemas.file) {
  123. const newURI = URI.file(paths.resolve(originalFSPath(base), path));
  124. return base.with({
  125. authority: newURI.authority,
  126. path: newURI.path
  127. });
  128. }
  129. path = extpath.toPosixPath(path); // we allow path to be a windows path
  130. return base.with({
  131. path: paths.posix.resolve(base.path, path)
  132. });
  133. }
  134. // --- misc
  135. isAbsolutePath(resource) {
  136. return !!resource.path && resource.path[0] === '/';
  137. }
  138. isEqualAuthority(a1, a2) {
  139. return a1 === a2 || (a1 !== undefined && a2 !== undefined && equalsIgnoreCase(a1, a2));
  140. }
  141. hasTrailingPathSeparator(resource, sep = paths.sep) {
  142. if (resource.scheme === Schemas.file) {
  143. const fsp = originalFSPath(resource);
  144. return fsp.length > extpath.getRoot(fsp).length && fsp[fsp.length - 1] === sep;
  145. }
  146. else {
  147. const p = resource.path;
  148. return (p.length > 1 && p.charCodeAt(p.length - 1) === 47 /* CharCode.Slash */) && !(/^[a-zA-Z]:(\/$|\\$)/.test(resource.fsPath)); // ignore the slash at offset 0
  149. }
  150. }
  151. removeTrailingPathSeparator(resource, sep = paths.sep) {
  152. // Make sure that the path isn't a drive letter. A trailing separator there is not removable.
  153. if (hasTrailingPathSeparator(resource, sep)) {
  154. return resource.with({ path: resource.path.substr(0, resource.path.length - 1) });
  155. }
  156. return resource;
  157. }
  158. addTrailingPathSeparator(resource, sep = paths.sep) {
  159. let isRootSep = false;
  160. if (resource.scheme === Schemas.file) {
  161. const fsp = originalFSPath(resource);
  162. isRootSep = ((fsp !== undefined) && (fsp.length === extpath.getRoot(fsp).length) && (fsp[fsp.length - 1] === sep));
  163. }
  164. else {
  165. sep = '/';
  166. const p = resource.path;
  167. isRootSep = p.length === 1 && p.charCodeAt(p.length - 1) === 47 /* CharCode.Slash */;
  168. }
  169. if (!isRootSep && !hasTrailingPathSeparator(resource, sep)) {
  170. return resource.with({ path: resource.path + '/' });
  171. }
  172. return resource;
  173. }
  174. }
  175. /**
  176. * Unbiased utility that takes uris "as they are". This means it can be interchanged with
  177. * uri#toString() usages. The following is true
  178. * ```
  179. * assertEqual(aUri.toString() === bUri.toString(), exturi.isEqual(aUri, bUri))
  180. * ```
  181. */
  182. export const extUri = new ExtUri(() => false);
  183. /**
  184. * BIASED utility that _mostly_ ignored the case of urs paths. ONLY use this util if you
  185. * understand what you are doing.
  186. *
  187. * This utility is INCOMPATIBLE with `uri.toString()`-usages and both CANNOT be used interchanged.
  188. *
  189. * When dealing with uris from files or documents, `extUri` (the unbiased friend)is sufficient
  190. * because those uris come from a "trustworthy source". When creating unknown uris it's always
  191. * better to use `IUriIdentityService` which exposes an `IExtUri`-instance which knows when path
  192. * casing matters.
  193. */
  194. export const extUriBiasedIgnorePathCase = new ExtUri(uri => {
  195. // A file scheme resource is in the same platform as code, so ignore case for non linux platforms
  196. // Resource can be from another platform. Lowering the case as an hack. Should come from File system provider
  197. return uri.scheme === Schemas.file ? !isLinux : true;
  198. });
  199. /**
  200. * BIASED utility that always ignores the casing of uris paths. ONLY use this util if you
  201. * understand what you are doing.
  202. *
  203. * This utility is INCOMPATIBLE with `uri.toString()`-usages and both CANNOT be used interchanged.
  204. *
  205. * When dealing with uris from files or documents, `extUri` (the unbiased friend)is sufficient
  206. * because those uris come from a "trustworthy source". When creating unknown uris it's always
  207. * better to use `IUriIdentityService` which exposes an `IExtUri`-instance which knows when path
  208. * casing matters.
  209. */
  210. export const extUriIgnorePathCase = new ExtUri(_ => true);
  211. export const isEqual = extUri.isEqual.bind(extUri);
  212. export const isEqualOrParent = extUri.isEqualOrParent.bind(extUri);
  213. export const getComparisonKey = extUri.getComparisonKey.bind(extUri);
  214. export const basenameOrAuthority = extUri.basenameOrAuthority.bind(extUri);
  215. export const basename = extUri.basename.bind(extUri);
  216. export const extname = extUri.extname.bind(extUri);
  217. export const dirname = extUri.dirname.bind(extUri);
  218. export const joinPath = extUri.joinPath.bind(extUri);
  219. export const normalizePath = extUri.normalizePath.bind(extUri);
  220. export const relativePath = extUri.relativePath.bind(extUri);
  221. export const resolvePath = extUri.resolvePath.bind(extUri);
  222. export const isAbsolutePath = extUri.isAbsolutePath.bind(extUri);
  223. export const isEqualAuthority = extUri.isEqualAuthority.bind(extUri);
  224. export const hasTrailingPathSeparator = extUri.hasTrailingPathSeparator.bind(extUri);
  225. export const removeTrailingPathSeparator = extUri.removeTrailingPathSeparator.bind(extUri);
  226. export const addTrailingPathSeparator = extUri.addTrailingPathSeparator.bind(extUri);
  227. /**
  228. * Data URI related helpers.
  229. */
  230. export var DataUri;
  231. (function (DataUri) {
  232. DataUri.META_DATA_LABEL = 'label';
  233. DataUri.META_DATA_DESCRIPTION = 'description';
  234. DataUri.META_DATA_SIZE = 'size';
  235. DataUri.META_DATA_MIME = 'mime';
  236. function parseMetaData(dataUri) {
  237. const metadata = new Map();
  238. // Given a URI of: data:image/png;size:2313;label:SomeLabel;description:SomeDescription;base64,77+9UE5...
  239. // the metadata is: size:2313;label:SomeLabel;description:SomeDescription
  240. const meta = dataUri.path.substring(dataUri.path.indexOf(';') + 1, dataUri.path.lastIndexOf(';'));
  241. meta.split(';').forEach(property => {
  242. const [key, value] = property.split(':');
  243. if (key && value) {
  244. metadata.set(key, value);
  245. }
  246. });
  247. // Given a URI of: data:image/png;size:2313;label:SomeLabel;description:SomeDescription;base64,77+9UE5...
  248. // the mime is: image/png
  249. const mime = dataUri.path.substring(0, dataUri.path.indexOf(';'));
  250. if (mime) {
  251. metadata.set(DataUri.META_DATA_MIME, mime);
  252. }
  253. return metadata;
  254. }
  255. DataUri.parseMetaData = parseMetaData;
  256. })(DataUri || (DataUri = {}));