///| Port of `vscode/src/vs/base/common/extpath.ts` (checked-in submodule) — only the members `resources.ts` consumes; the rest are
/// explicit DEFERRED rows in the plan ledger. `isPathSeparator`
/// (extpath.ts:12) is shared with path.mbt's identical private helper.
/// Regex character classes become char loops (D3).
///|
/// `[\\/]` → `sep` global replace (the regex bodies of extpath.ts:22 and
/// extpath.ts:71).
fn replace_path_separators(os_path : String, sep : String) -> String {
let sb = StringBuilder()
for unit in os_path.code_units() {
let code = unit.to_int()
if is_path_separator(code) {
sb.write_string(sep)
} else {
sb.write_char(Int32::unsafe_to_char(code))
}
}
sb.to_string()
}
///|
/// `toSlashes` (extpath.ts:21): takes a Windows OS path and changes backward
/// slashes to forward slashes. This should only be done for OS paths from
/// Windows (or user provided paths potentially from Windows) — using it on a
/// Linux or MacOS path might change it.
fn to_slashes(os_path : String) -> String {
replace_path_separators(os_path, Posix::sep())
}
///|
/// `toPosixPath` (extpath.ts:32): takes a Windows OS path (backward or
/// forward slashes) and turns it into a posix path — forward slashes, made
/// absolute when it starts with a drive letter (`^[a-zA-Z]:(\/|$)`).
fn to_posix_path(os_path : String) -> String {
let mut os_path = os_path
if !os_path.contains("/") {
os_path = to_slashes(os_path)
}
if os_path.length() >= 2 &&
is_windows_drive_letter(char_code_at(os_path, 0)) &&
char_code_at(os_path, 1) == char_colon &&
(os_path.length() == 2 || char_code_at(os_path, 2) == char_forward_slash) {
// starts with a drive letter
os_path = "/\{os_path}"
}
os_path
}
///|
/// `getRoot` (extpath.ts:47): computes the _root_ of a path, like
/// `getRoot('c:\files') === 'c:\'`, `getRoot('files:///files/path') ===
/// 'files:///'`, or `getRoot('\\server\shares\path') === '\\server\shares\'`.
fn get_root(path : String, sep? : String) -> String {
let sep = match sep {
Some(value) => value
None => Posix::sep()
}
if path == "" {
return ""
}
let len = path.length()
let first_letter = char_code_at(path, 0)
if is_path_separator(first_letter) {
if is_path_separator(char_code_at(path, 1)) {
// UNC candidate \\localhost\shares\ddd
// ^^^^^^^^^^^^^^^^^^^
if !is_path_separator(char_code_at(path, 2)) {
let start = 3
let pos = for pos in start.. Bool {
let separator = if force_posix_semantics { Posix::sep() } else { sep() }
let mut base = base
let mut parent_candidate = parent_candidate
if base == parent_candidate {
return true
}
if base == "" || parent_candidate == "" {
return false
}
if base.contains("..") || parent_candidate.contains("..") {
base = if force_posix_semantics {
Posix::normalize(base)
} else {
normalize(base)
}
parent_candidate = if force_posix_semantics {
Posix::normalize(parent_candidate)
} else {
normalize(parent_candidate)
}
}
if parent_candidate.length() > base.length() {
return false
}
if ignore_case {
let begins_with = starts_with_ignore_case(base, parent_candidate)
if !begins_with {
return false
}
if parent_candidate.length() == base.length() {
return true // same path, different casing
}
let mut sep_offset = parent_candidate.length()
if js_slice(parent_candidate, parent_candidate.length() - 1) == separator {
sep_offset -= 1 // adjust the expected sep offset in case our candidate already ends in separator character
}
return js_slice(base, sep_offset, end=sep_offset + 1) == separator
}
if js_slice(parent_candidate, parent_candidate.length() - 1) != separator {
parent_candidate += separator
}
base.has_prefix(parent_candidate)
}
///|
/// `isWindowsDriveLetter` (extpath.ts:275).
fn is_windows_drive_letter(char0 : Int32) -> Bool {
(char0 >= 'A'.to_int() && char0 <= 'Z'.to_int()) ||
(char0 >= 'a'.to_int() && char0 <= 'z'.to_int())
}