Buckets:
| /** | |
| * Pure helpers for the working-directory picker search, backed by the | |
| * server's `file_glob_search` tool. Queries starting from a root (`/`, | |
| * `C:\`, `\\host\share`) or `~` navigate the tree (search the parent for | |
| * the last segment); anything else glob-matches home-relative entries. | |
| */ | |
| import { lastPathSegment } from './path-display'; | |
| import { | |
| GLOB, | |
| HOME_TILDE, | |
| LEADING_SLASHES_REGEX, | |
| PATH_SEPARATOR, | |
| SEARCH, | |
| TRAILING_SLASHES_REGEX | |
| } from '$lib/constants'; | |
| import type { GlobEntry, GlobSearchArgs } from '$lib/types/glob'; | |
| export interface PathQuery { | |
| parent: string; | |
| last: string; | |
| } | |
| /** | |
| * Rewrite `\` into `/` when the query carries a Windows root. Elsewhere the | |
| * backslash is left alone: it is a legal filename character on POSIX. | |
| */ | |
| function toPosixSeparators(query: string): string { | |
| if (!GLOB.DRIVE_PREFIX_REGEX.test(query) && !query.startsWith(GLOB.WINDOWS_SEPARATOR)) | |
| return query; | |
| return query.split(GLOB.WINDOWS_SEPARATOR).join(PATH_SEPARATOR); | |
| } | |
| export function rootPrefixLength(path: string): number { | |
| const unc = path.match(GLOB.UNC_ROOT_REGEX); | |
| if (unc) return unc[0].length; | |
| const drive = path.match(GLOB.DRIVE_ROOT_REGEX); | |
| if (drive) return drive[0].length; | |
| return path.startsWith(PATH_SEPARATOR) ? PATH_SEPARATOR.length : 0; | |
| } | |
| /** A query starting from a root or from `~` is path navigation, not a home-relative glob. */ | |
| export function splitPathQuery(query: string): PathQuery | null { | |
| const normalized = toPosixSeparators(query); | |
| const rootLength = rootPrefixLength(normalized); | |
| if (rootLength === 0 && !normalized.startsWith(HOME_TILDE)) return null; | |
| // a root keeps its trailing separator so it stays absolute on its own | |
| const root = | |
| rootLength > 0 | |
| ? normalized.slice(0, rootLength).replace(TRAILING_SLASHES_REGEX, '') + PATH_SEPARATOR | |
| : HOME_TILDE; | |
| const rest = normalized | |
| .slice(rootLength > 0 ? rootLength : HOME_TILDE.length) | |
| .replace(LEADING_SLASHES_REGEX, '') | |
| .replace(TRAILING_SLASHES_REGEX, ''); | |
| const parentOf = (dirs: string) => | |
| rootLength > 0 ? root + dirs : HOME_TILDE + PATH_SEPARATOR + dirs; | |
| if (!rest) return { last: '', parent: root }; | |
| const idx = rest.lastIndexOf(PATH_SEPARATOR); | |
| if (idx === -1) return { last: rest, parent: root }; | |
| return { last: rest.slice(idx + 1), parent: parentOf(rest.slice(0, idx)) }; | |
| } | |
| export function buildCaseInsensitiveGlob(query: string): string { | |
| let out = GLOB.WILDCARD; | |
| for (const c of query) { | |
| const lo = c.toLowerCase(); | |
| const up = c.toUpperCase(); | |
| if (lo !== up) out += GLOB.RANGE_OPEN + lo + up + GLOB.RANGE_CLOSE; | |
| // glob metacharacters are escaped into a literal character class so a | |
| // query like "a*b" matches a literal '*' instead of becoming "ab" | |
| else if (GLOB.SPECIAL_CHARS.includes(c)) out += GLOB.RANGE_OPEN + c + GLOB.RANGE_CLOSE; | |
| else out += c; | |
| } | |
| return out + GLOB.WILDCARD; | |
| } | |
| export function buildGlobSearchArgs( | |
| query: string, | |
| scopePath: string, | |
| searchDepth: number | |
| ): GlobSearchArgs { | |
| const pathQuery = splitPathQuery(query); | |
| const path = pathQuery ? pathQuery.parent : scopePath; | |
| const include = pathQuery | |
| ? pathQuery.last | |
| ? buildCaseInsensitiveGlob(pathQuery.last) | |
| : GLOB.WILDCARD | |
| : buildCaseInsensitiveGlob(query); | |
| const maxDepth = pathQuery ? SEARCH.PATH_NAV_MAX_DEPTH : searchDepth; | |
| return { include, last: pathQuery?.last, maxDepth, path, rankQuery: pathQuery?.last ?? query }; | |
| } | |
| const RANK_EXACT = 0; | |
| const RANK_PREFIX = 1; | |
| const RANK_SUBSTRING = 2; | |
| const RANK_OTHER = 3; | |
| function rankScore(path: string, query: string): number { | |
| const name = lastPathSegment(path).toLowerCase(); | |
| const q = query.toLowerCase(); | |
| if (name === q) return RANK_EXACT; | |
| if (name.startsWith(q)) return RANK_PREFIX; | |
| if (name.includes(q)) return RANK_SUBSTRING; | |
| return RANK_OTHER; | |
| } | |
| export function rankEntries(entries: GlobEntry[], query: string): GlobEntry[] { | |
| return [...entries].sort( | |
| (a, b) => | |
| rankScore(a.path, query) - rankScore(b.path, query) || | |
| a.path.length - b.path.length || | |
| a.path.localeCompare(b.path) | |
| ); | |
| } | |
| export function joinPath(base: string, rel: string): string { | |
| if (!base) return rel; | |
| return base.replace(TRAILING_SLASHES_REGEX, '') + PATH_SEPARATOR + rel; | |
| } | |
| export function highlightMatch(text: string, query: string): { text: string; match: boolean }[] { | |
| if (!query) return [{ match: false, text }]; | |
| const segments: { text: string; match: boolean }[] = []; | |
| const lowerText = text.toLowerCase(); | |
| const lowerQuery = query.toLowerCase(); | |
| let i = 0; | |
| while (i < text.length) { | |
| const idx = lowerText.indexOf(lowerQuery, i); | |
| if (idx < 0) { | |
| segments.push({ match: false, text: text.slice(i) }); | |
| break; | |
| } | |
| if (idx > i) segments.push({ match: false, text: text.slice(i, idx) }); | |
| segments.push({ match: true, text: text.slice(idx, idx + query.length) }); | |
| i = idx + query.length; | |
| } | |
| return segments; | |
| } | |
Xet Storage Details
- Size:
- 4.86 kB
- Xet hash:
- 6b876d9a78641e44ce6dae2a7c4b92e022b58332a1fbe1cd7e0fc100e01fa046
·
Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.