Path: blob/main/extensions/copilot/src/util/vs/base/common/extpath.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 { isAbsolute, join, normalize, posix, sep } from './path';9import { isWindows } from './platform';10import { equalsIgnoreCase, rtrim, startsWithIgnoreCase } from './strings';11import { isNumber } from './types';1213export function isPathSeparator(code: number) {14return code === CharCode.Slash || code === CharCode.Backslash;15}1617/**18* Takes a Windows OS path and changes backward slashes to forward slashes.19* This should only be done for OS paths from Windows (or user provided paths potentially from Windows).20* Using it on a Linux or MaxOS path might change it.21*/22export function toSlashes(osPath: string) {23return osPath.replace(/[\\/]/g, posix.sep);24}2526/**27* Takes a Windows OS path (using backward or forward slashes) and turns it into a posix path:28* - turns backward slashes into forward slashes29* - makes it absolute if it starts with a drive letter30* This should only be done for OS paths from Windows (or user provided paths potentially from Windows).31* Using it on a Linux or MaxOS path might change it.32*/33export function toPosixPath(osPath: string) {34if (osPath.indexOf('/') === -1) {35osPath = toSlashes(osPath);36}37if (/^[a-zA-Z]:(\/|$)/.test(osPath)) { // starts with a drive letter38osPath = '/' + osPath;39}40return osPath;41}4243/**44* Computes the _root_ this path, like `getRoot('c:\files') === c:\`,45* `getRoot('files:///files/path') === files:///`,46* or `getRoot('\\server\shares\path') === \\server\shares\`47*/48export function getRoot(path: string, sep: string = posix.sep): string {49if (!path) {50return '';51}5253const len = path.length;54const firstLetter = path.charCodeAt(0);55if (isPathSeparator(firstLetter)) {56if (isPathSeparator(path.charCodeAt(1))) {57// UNC candidate \\localhost\shares\ddd58// ^^^^^^^^^^^^^^^^^^^59if (!isPathSeparator(path.charCodeAt(2))) {60let pos = 3;61const start = pos;62for (; pos < len; pos++) {63if (isPathSeparator(path.charCodeAt(pos))) {64break;65}66}67if (start !== pos && !isPathSeparator(path.charCodeAt(pos + 1))) {68pos += 1;69for (; pos < len; pos++) {70if (isPathSeparator(path.charCodeAt(pos))) {71return path.slice(0, pos + 1) // consume this separator72.replace(/[\\/]/g, sep);73}74}75}76}77}7879// /user/far80// ^81return sep;8283} else if (isWindowsDriveLetter(firstLetter)) {84// check for windows drive letter c:\ or c:8586if (path.charCodeAt(1) === CharCode.Colon) {87if (isPathSeparator(path.charCodeAt(2))) {88// C:\fff89// ^^^90return path.slice(0, 2) + sep;91} else {92// C:93// ^^94return path.slice(0, 2);95}96}97}9899// check for URI100// scheme://authority/path101// ^^^^^^^^^^^^^^^^^^^102let pos = path.indexOf('://');103if (pos !== -1) {104pos += 3; // 3 -> "://".length105for (; pos < len; pos++) {106if (isPathSeparator(path.charCodeAt(pos))) {107return path.slice(0, pos + 1); // consume this separator108}109}110}111112return '';113}114115/**116* Check if the path follows this pattern: `\\hostname\sharename`.117*118* @see https://msdn.microsoft.com/en-us/library/gg465305.aspx119* @return A boolean indication if the path is a UNC path, on none-windows120* always false.121*/122export function isUNC(path: string): boolean {123if (!isWindows) {124// UNC is a windows concept125return false;126}127128if (!path || path.length < 5) {129// at least \\a\b130return false;131}132133let code = path.charCodeAt(0);134if (code !== CharCode.Backslash) {135return false;136}137138code = path.charCodeAt(1);139140if (code !== CharCode.Backslash) {141return false;142}143144let pos = 2;145const start = pos;146for (; pos < path.length; pos++) {147code = path.charCodeAt(pos);148if (code === CharCode.Backslash) {149break;150}151}152153if (start === pos) {154return false;155}156157code = path.charCodeAt(pos + 1);158159if (isNaN(code) || code === CharCode.Backslash) {160return false;161}162163return true;164}165166// Reference: https://en.wikipedia.org/wiki/Filename167const WINDOWS_INVALID_FILE_CHARS = /[\\/:\*\?"<>\|]/g;168const UNIX_INVALID_FILE_CHARS = /[/]/g;169const WINDOWS_FORBIDDEN_NAMES = /^(con|prn|aux|clock\$|nul|lpt[0-9]|com[0-9])(\.(.*?))?$/i;170export function isValidBasename(name: string | null | undefined, isWindowsOS: boolean = isWindows): boolean {171const invalidFileChars = isWindowsOS ? WINDOWS_INVALID_FILE_CHARS : UNIX_INVALID_FILE_CHARS;172173if (!name || name.length === 0 || /^\s+$/.test(name)) {174return false; // require a name that is not just whitespace175}176177invalidFileChars.lastIndex = 0; // the holy grail of software development178if (invalidFileChars.test(name)) {179return false; // check for certain invalid file characters180}181182if (isWindowsOS && WINDOWS_FORBIDDEN_NAMES.test(name)) {183return false; // check for certain invalid file names184}185186if (name === '.' || name === '..') {187return false; // check for reserved values188}189190if (isWindowsOS && name[name.length - 1] === '.') {191return false; // Windows: file cannot end with a "."192}193194if (isWindowsOS && name.length !== name.trim().length) {195return false; // Windows: file cannot end with a whitespace196}197198if (name.length > 255) {199return false; // most file systems do not allow files > 255 length200}201202return true;203}204205/**206* @deprecated please use `IUriIdentityService.extUri.isEqual` instead. If you are207* in a context without services, consider to pass down the `extUri` from the outside208* or use `extUriBiasedIgnorePathCase` if you know what you are doing.209*/210export function isEqual(pathA: string, pathB: string, ignoreCase?: boolean): boolean {211const identityEquals = (pathA === pathB);212if (!ignoreCase || identityEquals) {213return identityEquals;214}215216if (!pathA || !pathB) {217return false;218}219220return equalsIgnoreCase(pathA, pathB);221}222223/**224* @deprecated please use `IUriIdentityService.extUri.isEqualOrParent` instead. If225* you are in a context without services, consider to pass down the `extUri` from the226* outside, or use `extUriBiasedIgnorePathCase` if you know what you are doing.227*/228export function isEqualOrParent(base: string, parentCandidate: string, ignoreCase?: boolean, separator = sep): boolean {229if (base === parentCandidate) {230return true;231}232233if (!base || !parentCandidate) {234return false;235}236237if (parentCandidate.length > base.length) {238return false;239}240241if (ignoreCase) {242const beginsWith = startsWithIgnoreCase(base, parentCandidate);243if (!beginsWith) {244return false;245}246247if (parentCandidate.length === base.length) {248return true; // same path, different casing249}250251let sepOffset = parentCandidate.length;252if (parentCandidate.charAt(parentCandidate.length - 1) === separator) {253sepOffset--; // adjust the expected sep offset in case our candidate already ends in separator character254}255256return base.charAt(sepOffset) === separator;257}258259if (parentCandidate.charAt(parentCandidate.length - 1) !== separator) {260parentCandidate += separator;261}262263return base.indexOf(parentCandidate) === 0;264}265266export function isWindowsDriveLetter(char0: number): boolean {267return char0 >= CharCode.A && char0 <= CharCode.Z || char0 >= CharCode.a && char0 <= CharCode.z;268}269270export function sanitizeFilePath(candidate: string, cwd: string): string {271272// Special case: allow to open a drive letter without trailing backslash273if (isWindows && candidate.endsWith(':')) {274candidate += sep;275}276277// Ensure absolute278if (!isAbsolute(candidate)) {279candidate = join(cwd, candidate);280}281282// Ensure normalized283candidate = normalize(candidate);284285// Ensure no trailing slash/backslash286return removeTrailingPathSeparator(candidate);287}288289export function removeTrailingPathSeparator(candidate: string): string {290if (isWindows) {291candidate = rtrim(candidate, sep);292293// Special case: allow to open drive root ('C:\')294if (candidate.endsWith(':')) {295candidate += sep;296}297298} else {299candidate = rtrim(candidate, sep);300301// Special case: allow to open root ('/')302if (!candidate) {303candidate = sep;304}305}306307return candidate;308}309310export function isRootOrDriveLetter(path: string): boolean {311const pathNormalized = normalize(path);312313if (isWindows) {314if (path.length > 3) {315return false;316}317318return hasDriveLetter(pathNormalized) &&319(path.length === 2 || pathNormalized.charCodeAt(2) === CharCode.Backslash);320}321322return pathNormalized === posix.sep;323}324325export function hasDriveLetter(path: string, isWindowsOS: boolean = isWindows): boolean {326if (isWindowsOS) {327return isWindowsDriveLetter(path.charCodeAt(0)) && path.charCodeAt(1) === CharCode.Colon;328}329330return false;331}332333export function getDriveLetter(path: string, isWindowsOS: boolean = isWindows): string | undefined {334return hasDriveLetter(path, isWindowsOS) ? path[0] : undefined;335}336337export function indexOfPath(path: string, candidate: string, ignoreCase?: boolean): number {338if (candidate.length > path.length) {339return -1;340}341342if (path === candidate) {343return 0;344}345346if (ignoreCase) {347path = path.toLowerCase();348candidate = candidate.toLowerCase();349}350351return path.indexOf(candidate);352}353354export interface IPathWithLineAndColumn {355path: string;356line?: number;357column?: number;358}359360export function parseLineAndColumnAware(rawPath: string): IPathWithLineAndColumn {361const segments = rawPath.split(':'); // C:\file.txt:<line>:<column>362363let path: string | undefined;364let line: number | undefined;365let column: number | undefined;366367for (const segment of segments) {368const segmentAsNumber = Number(segment);369if (!isNumber(segmentAsNumber)) {370path = path ? [path, segment].join(':') : segment; // a colon can well be part of a path (e.g. C:\...)371} else if (line === undefined) {372line = segmentAsNumber;373} else if (column === undefined) {374column = segmentAsNumber;375}376}377378if (!path) {379throw new Error('Format for `--goto` should be: `FILE:LINE(:COLUMN)`');380}381382return {383path,384line: line !== undefined ? line : undefined,385column: column !== undefined ? column : line !== undefined ? 1 : undefined // if we have a line, make sure column is also set386};387}388389const pathChars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';390const windowsSafePathFirstChars = 'BDEFGHIJKMOQRSTUVWXYZbdefghijkmoqrstuvwxyz0123456789';391392export function randomPath(parent?: string, prefix?: string, randomLength = 8): string {393let suffix = '';394for (let i = 0; i < randomLength; i++) {395let pathCharsTouse: string;396if (i === 0 && isWindows && !prefix && (randomLength === 3 || randomLength === 4)) {397398// Windows has certain reserved file names that cannot be used, such399// as AUX, CON, PRN, etc. We want to avoid generating a random name400// that matches that pattern, so we use a different set of characters401// for the first character of the name that does not include any of402// the reserved names first characters.403404pathCharsTouse = windowsSafePathFirstChars;405} else {406pathCharsTouse = pathChars;407}408409suffix += pathCharsTouse.charAt(Math.floor(Math.random() * pathCharsTouse.length));410}411412let randomFileName: string;413if (prefix) {414randomFileName = `${prefix}-${suffix}`;415} else {416randomFileName = suffix;417}418419if (parent) {420return join(parent, randomFileName);421}422423return randomFileName;424}425426427