- ue bridge: Launch / Build now read live refs. They captured startBuild / launchUE from the render where plugins loaded — before the async .uproject and .sln detection had resolved — so both saw empty paths forever. hasProject used the live ref, so the menu items showed up and then did nothing. - Get Latest / Sync to revision: summarise `p4 sync` stdout (syncSummary) instead of dumping every depot path into the toast. flash() caps length and .toast caps height so no raw output can blow up the bubble again. - Team Relay: use the real theme tokens — --txt / --add / --del, not --text / --ok / --err, which don't exist and silently fell back to a near-white hardcode, invisible on the light theme. Bumped to 1.0.1. - Plugin dock panels get a status-bar button next to Terminal / Log; the tabs were otherwise only reachable from the Actions menu. - PLUGINS.md documents the theme token names. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
375 lines
20 KiB
TypeScript
375 lines
20 KiB
TypeScript
// Typed wrappers around the Rust p4 commands.
|
||
import { invoke } from "@tauri-apps/api/core";
|
||
|
||
export interface P4Info {
|
||
serverAddress?: string;
|
||
serverVersion?: string;
|
||
userName?: string;
|
||
clientName?: string;
|
||
clientRoot?: string;
|
||
serverUptime?: string;
|
||
serverDate?: string;
|
||
[k: string]: unknown;
|
||
}
|
||
|
||
export interface OpenedFile {
|
||
depotFile?: string;
|
||
clientFile?: string;
|
||
rev?: string;
|
||
haveRev?: string;
|
||
action?: string; // edit | add | delete | branch | integrate | move/add | move/delete
|
||
type?: string; // text | binary | ...
|
||
change?: string; // "default" or a number
|
||
user?: string;
|
||
client?: string;
|
||
_spec?: string; // when set, read this exact rev via `p4 print` (submitted/historical file)
|
||
[k: string]: unknown;
|
||
}
|
||
|
||
// choose the byte source: an exact submitted revision (`p4 print`) when `_spec`
|
||
// is set, otherwise the working copy (local file via `p4 where`).
|
||
export function fileBytes(f: OpenedFile): Promise<ArrayBuffer> {
|
||
return f._spec ? p4.printDepot(f._spec) : p4.readDepot(f.depotFile || "");
|
||
}
|
||
|
||
export interface Client {
|
||
client?: string;
|
||
Root?: string;
|
||
Host?: string;
|
||
Stream?: string;
|
||
[k: string]: unknown;
|
||
}
|
||
|
||
export interface Change {
|
||
change?: string;
|
||
time?: string;
|
||
user?: string;
|
||
client?: string;
|
||
status?: string;
|
||
desc?: string;
|
||
[k: string]: unknown;
|
||
}
|
||
|
||
export const p4 = {
|
||
clients: (port: string, user: string) =>
|
||
invoke<Client[]>("p4_clients", { port, user }),
|
||
login: (port: string, user: string, password: string, client: string) =>
|
||
invoke<P4Info>("p4_login", { port, user, password, client }),
|
||
restore: (port: string, user: string, client: string) =>
|
||
invoke<P4Info>("p4_restore", { port, user, client }),
|
||
info: () => invoke<P4Info>("p4_info"),
|
||
opened: (scope = "") => invoke<OpenedFile[]>("p4_opened", { scope }),
|
||
dirs: (path = "") => invoke<Dir[]>("p4_dirs", { path }),
|
||
changes: () => invoke<Change[]>("p4_changes"),
|
||
history: (path: string) => invoke<Change[]>("p4_history", { path }),
|
||
disconnect: () => invoke<void>("p4_disconnect"),
|
||
sync: (scope = "") => invoke<string>("p4_sync", { scope }),
|
||
syncTo: (scope: string, rev: string) => invoke<string>("p4_sync_to", { scope, rev }),
|
||
labels: () => invoke<{ label?: string; Update?: string; Owner?: string; Description?: string; [k: string]: unknown }[]>("p4_labels"),
|
||
labelTag: (label: string, scope: string, rev = "") => invoke<string>("p4_label_tag", { label, scope, rev }),
|
||
branchOp: (op: "merge" | "copy" | "integrate", source: string, target: string) => invoke<string>("p4_branch_op", { op, source, target }),
|
||
streams: () => invoke<{ Stream?: string; Name?: string; Type?: string; Parent?: string; [k: string]: unknown }[]>("p4_streams"),
|
||
switchStream: (stream: string) => invoke<P4Info>("p4_switch_stream", { stream }),
|
||
streamInteg: (stream: string, direction: "down" | "up") => invoke<string>("p4_stream_integ", { stream, direction }),
|
||
filelog: (depot: string) => invoke<Record<string, unknown>>("p4_filelog", { depot }),
|
||
cleanPreview: (scope = "") => invoke<OpenedFile[]>("p4_clean_preview", { scope }),
|
||
cleanApply: (scope = "") => invoke<string>("p4_clean_apply", { scope }),
|
||
depotLs: (path = "") => invoke<FsEntry[]>("depot_ls", { path }),
|
||
changeSizes: (changes: string[], scope = "") => invoke<{ change: string; size: number }[]>("p4_change_sizes", { changes, scope }),
|
||
fsLs: (path = "") => invoke<FsEntry[]>("fs_ls", { path }),
|
||
reconcilePreview: (scope = "") => invoke<OpenedFile[]>("p4_reconcile_preview", { scope }),
|
||
reconcileApply: (paths: string[]) => invoke<OpenedFile[]>("p4_reconcile_apply", { paths }),
|
||
setWritable: (paths: string[], writable: boolean) => invoke<string>("p4_set_writable", { paths, writable }),
|
||
jobs: () => invoke<{ Job?: string; Status?: string; Description?: string; User?: string; [k: string]: unknown }[]>("p4_jobs"),
|
||
fix: (job: string, change: string) => invoke<string>("p4_fix", { job, change }),
|
||
jobSpec: (job: string) => invoke<string>("p4_job_spec", { job }),
|
||
jobSave: (spec: string) => invoke<string>("p4_job_save", { spec }),
|
||
diff: (file: string) => invoke<string>("p4_diff", { file }),
|
||
revert: (files: string[]) => invoke<string>("p4_revert", { files }),
|
||
submit: (description: string, files: string[]) =>
|
||
invoke<string>("p4_submit", { description, files }),
|
||
commit: (description: string, files: string[]) =>
|
||
invoke<string>("p4_commit", { description, files }),
|
||
submitChange: (change: string) => invoke<string>("p4_submit_change", { change }),
|
||
submitShelved: (change: string) => invoke<string>("p4_submit_shelved", { change }),
|
||
submitOpts: (change: string, reopen: boolean, revertUnchanged: boolean) => invoke<string>("p4_submit_opts", { change, reopen, revertUnchanged }),
|
||
ignoreRead: () => invoke<string>("p4_ignore_read"),
|
||
ignoreWrite: (content: string) => invoke<void>("p4_ignore_write", { content }),
|
||
reopenDefault: (files: string[]) => invoke<string>("p4_reopen_default", { files }),
|
||
setDesc: (change: string, description: string) => invoke<string>("p4_set_desc", { change, description }),
|
||
add: (files: string[]) => invoke<OpenedFile[]>("p4_add", { files }),
|
||
edit: (files: string[]) => invoke<OpenedFile[]>("p4_edit", { files }),
|
||
del: (files: string[]) => invoke<OpenedFile[]>("p4_delete", { files }),
|
||
reconcile: (path: string) => invoke<OpenedFile[]>("p4_reconcile", { path }),
|
||
scan: (scope = "") => invoke<OpenedFile[]>("p4_scan", { scope }),
|
||
// streaming disk scan: emits `p4-scan` progress events, returns the file count.
|
||
// The caller re-reads opened() for the actual list.
|
||
scanStream: (scope = "") => invoke<number>("p4_scan_stream", { scope }),
|
||
scanCancel: () => invoke<void>("p4_scan_cancel"),
|
||
// ask an in-progress submit/sync transfer to stop (safe pre-commit for submit)
|
||
transferCancel: () => invoke<void>("p4_transfer_cancel"),
|
||
// --- plugins ---
|
||
pluginList: () => invoke<PluginInfo[]>("plugin_list"),
|
||
pluginReadEntry: (id: string) => invoke<string>("plugin_read_entry", { id }),
|
||
pluginSetEnabled: (id: string, enabled: boolean) => invoke<void>("plugin_set_enabled", { id, enabled }),
|
||
pluginWriteFile: (id: string, name: string, content: string) => invoke<void>("plugin_write_file", { id, name, content }),
|
||
registryHttpGet: (url: string) => invoke<string>("registry_http_get", { url }),
|
||
// generic HTTP pipe for relay-backed plugins (any http(s) URL, optional bearer)
|
||
relayHttp: (url: string, method: string, body?: string, token?: string) =>
|
||
invoke<string>("relay_http", { url, method, body, token }),
|
||
// shelve everything open in the default changelist → { change, files }
|
||
shelveOpen: (description: string) =>
|
||
invoke<{ change: string; files: number }>("p4_shelve_open", { description }),
|
||
// caller's max protection level on the server (super/admin/write/…)
|
||
protectsMax: () => invoke<string>("p4_protects_max"),
|
||
pluginInstall: (src: string) => invoke<string>("plugin_install", { src }),
|
||
pluginRemove: (id: string) => invoke<void>("plugin_remove", { id }),
|
||
pluginsDirPath: () => invoke<string>("plugins_dir_path"),
|
||
openPluginsDir: () => invoke<void>("open_plugins_dir"),
|
||
describe: (change: string) => invoke<Describe>("p4_describe", { change }),
|
||
undoChange: (change: string) => invoke<string>("p4_undo_change", { change }),
|
||
openInExplorer: (target: string) => invoke<void>("open_in_explorer", { target }),
|
||
findUproject: (scope: string) => invoke<string>("find_uproject", { scope }),
|
||
findSln: (scope: string) => invoke<string>("find_sln", { scope }),
|
||
buildSln: (path: string, config: string, uproject = "") => invoke<string>("build_sln", { path, config, uproject }),
|
||
launchFile: (path: string) => invoke<void>("launch_file", { path }),
|
||
listEditors: () => invoke<Editor[]>("list_editors"),
|
||
openInEditor: (depot: string, editor: string) => invoke<void>("open_in_editor", { depot, editor }),
|
||
readDepot: async (depot: string): Promise<ArrayBuffer> => {
|
||
const r: unknown = await invoke("read_depot", { depot });
|
||
if (r instanceof ArrayBuffer) return r;
|
||
if (ArrayBuffer.isView(r)) return (r as ArrayBufferView).buffer as ArrayBuffer;
|
||
if (Array.isArray(r)) return new Uint8Array(r as number[]).buffer;
|
||
return r as ArrayBuffer;
|
||
},
|
||
// read raw bytes at an exact revision (submitted/historical), e.g. "//depot/f.cpp#7"
|
||
printDepot: async (spec: string): Promise<ArrayBuffer> => {
|
||
const r: unknown = await invoke("print_depot", { spec });
|
||
if (r instanceof ArrayBuffer) return r;
|
||
if (ArrayBuffer.isView(r)) return (r as ArrayBufferView).buffer as ArrayBuffer;
|
||
if (Array.isArray(r)) return new Uint8Array(r as number[]).buffer;
|
||
return r as ArrayBuffer;
|
||
},
|
||
// built-in editor: write text back to a depot file's working copy
|
||
writeDepot: (depot: string, content: string) => invoke<void>("write_depot", { depot, content }),
|
||
switchClient: (client: string) => invoke<P4Info>("p4_switch_client", { client }),
|
||
clientSpec: (client: string) => invoke<string>("p4_client_spec", { client }),
|
||
clientSave: (spec: string) => invoke<string>("p4_client_save", { spec }),
|
||
users: () => invoke<User[]>("p4_users"),
|
||
groups: (user = "") => invoke<{ group?: string }[]>("p4_groups", { user }),
|
||
groupMember: (group: string, user: string, add: boolean) => invoke<string>("p4_group_member", { group, user, add }),
|
||
readAiKey: () => invoke<string>("read_ai_key"),
|
||
saveAiKey: (key: string) => invoke<void>("save_ai_key", { key }),
|
||
// run an arbitrary p4 command in the built-in terminal (combined stdout+stderr)
|
||
console: (args: string[]) => invoke<string>("p4_console", { args }),
|
||
// tail the newest Unreal log for a project (.uproject path → Saved/Logs/*.log)
|
||
ueLog: (uproject: string, tailBytes = 200_000) => invoke<string>("ue_log", { uproject, tailBytes }),
|
||
// --- collaboration / advanced Perforce ---
|
||
openedAll: (scope = "") => invoke<OpenedFile[]>("p4_opened_all", { scope }),
|
||
lock: (files: string[]) => invoke<string>("p4_lock", { files }),
|
||
unlock: (files: string[]) => invoke<string>("p4_unlock", { files }),
|
||
typemapGet: () => invoke<string[]>("p4_typemap_get"),
|
||
typemapSet: (entries: string[]) => invoke<string>("p4_typemap_set", { entries }),
|
||
reopenType: (files: string[], filetype: string) => invoke<string>("p4_reopen_type", { files, filetype }),
|
||
shelve: (change: string) => invoke<string>("p4_shelve", { change }),
|
||
unshelve: (change: string) => invoke<string>("p4_unshelve", { change }),
|
||
deleteShelf: (change: string) => invoke<string>("p4_delete_shelf", { change }),
|
||
shelvedFiles: (change: string) => invoke<Describe>("p4_shelved_files", { change }),
|
||
resolveList: () => invoke<OpenedFile[]>("p4_resolve_list"),
|
||
resolveFile: (file: string, mode: "am" | "ay" | "at") => invoke<string>("p4_resolve_file", { file, mode }),
|
||
diff2: (spec1: string, spec2: string) => invoke<string>("p4_diff2", { spec1, spec2 }),
|
||
annotate: (spec: string) => invoke<string>("p4_annotate", { spec }),
|
||
searchFiles: (query: string, scope = "") => invoke<{ depotFile?: string; rev?: string }[]>("p4_search_files", { query, scope }),
|
||
reopenTo: (change: string, files: string[]) => invoke<string>("p4_reopen_to", { change, files }),
|
||
latestChange: (scope = "") => invoke<Change | null>("p4_latest_change", { scope }),
|
||
behind: (scope = "") => invoke<{ count: number; sample: string[] }>("p4_behind", { scope }),
|
||
makeFolder: (scope: string, name: string) => invoke<string>("make_folder", { scope, name }),
|
||
deleteFolder: (path: string) => invoke<OpenedFile[]>("delete_folder", { path }),
|
||
renameFolder: (path: string, newName: string) => invoke<OpenedFile[]>("rename_folder", { path, newName }),
|
||
folderInfo: (path: string) => invoke<{ path: string; fileCount: number; size: number }>("folder_info", { path }),
|
||
// embedded Unreal .uasset thumbnail (PNG bytes), for working copy or history
|
||
uassetThumb: async (spec: string, historical: boolean): Promise<ArrayBuffer> => {
|
||
const r: unknown = await invoke("uasset_thumbnail", { spec, historical });
|
||
if (r instanceof ArrayBuffer) return r;
|
||
if (ArrayBuffer.isView(r)) return (r as ArrayBufferView).buffer as ArrayBuffer;
|
||
if (Array.isArray(r)) return new Uint8Array(r as number[]).buffer;
|
||
return r as ArrayBuffer;
|
||
},
|
||
// export a working-copy .uasset mesh to FBX via headless Unreal → real 3D
|
||
exportUasset3d: async (depot: string, uproject: string): Promise<ArrayBuffer> => {
|
||
const r: unknown = await invoke("uasset_export_3d", { depot, uproject });
|
||
if (r instanceof ArrayBuffer) return r;
|
||
if (ArrayBuffer.isView(r)) return (r as ArrayBufferView).buffer as ArrayBuffer;
|
||
if (Array.isArray(r)) return new Uint8Array(r as number[]).buffer;
|
||
return r as ArrayBuffer;
|
||
},
|
||
};
|
||
|
||
export interface User { User?: string; Email?: string; FullName?: string; Access?: string; Update?: string; [k: string]: unknown }
|
||
|
||
// a detected code editor / IDE
|
||
export interface Editor { id: string; name: string; exe: string; args: string[] }
|
||
// which editor to open code files in (persisted; "" → auto-pick)
|
||
export function getEditor(): string { try { return localStorage.getItem("exd-editor") || ""; } catch { return ""; } }
|
||
export function setEditorPref(id: string) { try { localStorage.setItem("exd-editor", id); } catch {} }
|
||
|
||
export interface Dir { dir?: string; local?: boolean; [k: string]: unknown }
|
||
export interface PluginInfo { id: string; name: string; version: string; description: string; author: string; permissions: string[]; manifest: Record<string, unknown>; enabled: boolean; dir: string }
|
||
// depot path -> short leaf name (last segment)
|
||
export function leaf(path?: string): string {
|
||
if (!path) return "";
|
||
const p = path.replace(/\/+$/, "");
|
||
return p.slice(p.lastIndexOf("/") + 1);
|
||
}
|
||
|
||
// per-file revision history parsed from p4 filelog's indexed fields
|
||
export interface FileRev { rev: string; change: string; action: string; user: string; time: string; desc: string; type: string }
|
||
/** One entry (file or folder) in the Depot/Workspace size explorer. `size` is bytes. */
|
||
export interface FsEntry { name: string; path: string; isDir: boolean; size: number; fileCount: number }
|
||
/** Human-readable byte size, e.g. 1536 → "1.5 KB". */
|
||
export function humanSize(n: number): string {
|
||
if (!n) return "0 B";
|
||
if (n < 1024) return `${n} B`;
|
||
const u = ["KB", "MB", "GB", "TB", "PB"];
|
||
let v = n, i = -1;
|
||
do { v /= 1024; i++; } while (v >= 1024 && i < u.length - 1);
|
||
return `${v >= 100 ? Math.round(v) : v.toFixed(1)} ${u[i]}`;
|
||
}
|
||
export function filelogRevs(v: Record<string, unknown>): FileRev[] {
|
||
const out: FileRev[] = [];
|
||
for (let i = 0; ; i++) {
|
||
const rev = v[`rev${i}`] as string | undefined;
|
||
if (rev == null) break;
|
||
out.push({
|
||
rev,
|
||
change: (v[`change${i}`] as string) || "",
|
||
action: (v[`action${i}`] as string) || "",
|
||
user: (v[`user${i}`] as string) || "",
|
||
time: (v[`time${i}`] as string) || "",
|
||
desc: (v[`desc${i}`] as string) || "",
|
||
type: (v[`type${i}`] as string) || "",
|
||
});
|
||
}
|
||
return out;
|
||
}
|
||
|
||
// a submitted changelist with indexed file fields (depotFile0, action0, ...)
|
||
export interface Describe extends Change { [k: string]: unknown }
|
||
export function describeFiles(d: Describe): { depotFile: string; action: string; rev: string }[] {
|
||
const out: { depotFile: string; action: string; rev: string }[] = [];
|
||
for (let i = 0; ; i++) {
|
||
const dp = d[`depotFile${i}`] as string | undefined;
|
||
if (!dp) break;
|
||
out.push({ depotFile: dp, action: (d[`action${i}`] as string) || "edit", rev: (d[`rev${i}`] as string) || "" });
|
||
}
|
||
return out;
|
||
}
|
||
|
||
// --- session persistence (server/user/workspace — никогда не пароль) ---
|
||
export interface Session { server: string; user: string; client: string }
|
||
const SESSION_KEY = "exd-session";
|
||
export function saveSession(s: Session) {
|
||
try { localStorage.setItem(SESSION_KEY, JSON.stringify(s)); } catch {}
|
||
}
|
||
export function loadSession(): Session | null {
|
||
try { const r = localStorage.getItem(SESSION_KEY); return r ? JSON.parse(r) : null; } catch { return null; }
|
||
}
|
||
export function clearSession() {
|
||
try { localStorage.removeItem(SESSION_KEY); } catch {}
|
||
}
|
||
|
||
// --- working-folder scope persistence (per workspace) ---
|
||
export function saveScope(client: string, path: string) {
|
||
try { localStorage.setItem("exd-scope-" + client, path); } catch {}
|
||
}
|
||
export function loadScope(client: string): string {
|
||
try { return client ? (localStorage.getItem("exd-scope-" + client) || "") : ""; } catch { return ""; }
|
||
}
|
||
// last 3 chosen working folders (Recently) — most-recent first, per workspace
|
||
export function recentScopes(client: string): string[] {
|
||
try { const a = JSON.parse(localStorage.getItem("exd-recent-" + client) || "[]"); return Array.isArray(a) ? a.slice(0, 3) : []; } catch { return []; }
|
||
}
|
||
export function pushRecentScope(client: string, path: string) {
|
||
try {
|
||
const cur = recentScopes(client).filter((p) => p !== path);
|
||
const next = [path, ...cur].slice(0, 3);
|
||
localStorage.setItem("exd-recent-" + client, JSON.stringify(next));
|
||
} catch {}
|
||
}
|
||
|
||
// format a unix-epoch string as a short local datetime
|
||
export function fmtTime(t?: string): string {
|
||
if (!t) return "";
|
||
const n = Number(t);
|
||
if (!n) return "";
|
||
const d = new Date(n * 1000);
|
||
return d.toLocaleString(undefined, { day: "2-digit", month: "2-digit", year: "numeric", hour: "2-digit", minute: "2-digit" });
|
||
}
|
||
|
||
/** Condense `p4 sync` stdout into one short line.
|
||
*
|
||
* A sync prints one line per file — `//depot/F.uasset#3 - updating d:\ws\F.uasset` —
|
||
* so a real Get Latest returns thousands of full paths. Dumping that into a toast
|
||
* buries the screen; the per-file detail already streamed into the transfer dialog.
|
||
* Short outputs ("File(s) up-to-date.") pass through unchanged. */
|
||
export function syncSummary(out: string): string {
|
||
const text = (out || "").trim();
|
||
if (!text) return "up to date";
|
||
const lines = text.split("\n").map((l) => l.trim()).filter(Boolean);
|
||
// p4 says its piece in one line when nothing moved ("File(s) up-to-date.") and
|
||
// so do server errors — keep those verbatim; flash() caps the length.
|
||
if (lines.length === 1) return lines[0];
|
||
let added = 0, updated = 0, deleted = 0;
|
||
for (const l of lines) {
|
||
if (/ - (added as|refreshing) /.test(l)) added++;
|
||
else if (/ - (updating|replacing) /.test(l)) updated++;
|
||
else if (/ - deleted as /.test(l)) deleted++;
|
||
}
|
||
const moved = added + updated + deleted;
|
||
// unrecognised shape (server notices, errors) — report the line count, not the text
|
||
if (!moved) return `${lines.length.toLocaleString()} lines`;
|
||
const parts: string[] = [];
|
||
if (added) parts.push(`+${added.toLocaleString()}`);
|
||
if (updated) parts.push(`~${updated.toLocaleString()}`);
|
||
if (deleted) parts.push(`−${deleted.toLocaleString()}`);
|
||
return `${moved.toLocaleString()} files (${parts.join(" ")})`;
|
||
}
|
||
|
||
// action -> single-char status + css class
|
||
export function statusOf(action?: string): { ch: string; cls: string } {
|
||
const a = (action || "").toLowerCase();
|
||
if (a.includes("add")) return { ch: "+", cls: "st-a" };
|
||
if (a.includes("delete")) return { ch: "−", cls: "st-d" };
|
||
return { ch: "±", cls: "st-e" }; // edit / integrate / branch / move
|
||
}
|
||
|
||
// depot or client path -> { name, dir }
|
||
export function splitPath(p?: string): { name: string; dir: string } {
|
||
if (!p) return { name: "(unknown)", dir: "" };
|
||
const norm = p.replace(/\\/g, "/");
|
||
const i = norm.lastIndexOf("/");
|
||
return { name: norm.slice(i + 1), dir: norm.slice(0, i + 1) };
|
||
}
|
||
|
||
// classify by extension for the preview panel
|
||
export type Kind = "model" | "uasset" | "image" | "audio" | "video" | "code";
|
||
export function kindOf(name: string): Kind {
|
||
const n = name.toLowerCase();
|
||
if (/\.(fbx|obj|gltf|glb|stl|ply|3mf|dae)$/.test(n)) return "model";
|
||
if (/\.(uasset|umap)$/.test(n)) return "uasset";
|
||
if (/\.(png|jpe?g|webp|gif|bmp|svg)$/.test(n)) return "image";
|
||
if (/\.(wav|mp3|ogg|flac|m4a|aac|opus)$/.test(n)) return "audio";
|
||
if (/\.(mp4|webm|mov|m4v|ogv)$/.test(n)) return "video";
|
||
return "code";
|
||
}
|
||
|
||
// true only for real text/source files — gates the "Summary" and "Open in IDE"
|
||
// buttons so they never appear on binaries (.pak/.dll/.exe/.bin/etc.) that
|
||
// happen to fall through kindOf() as "code".
|
||
const CODE_EXT = /\.(c|cc|cpp|cxx|h|hpp|hh|inl|cs|ts|tsx|js|jsx|mjs|cjs|py|rs|go|java|kt|kts|swift|m|mm|rb|php|lua|sh|bash|ps1|bat|cmd|sql|json|jsonc|yaml|yml|toml|ini|cfg|conf|xml|html|htm|css|scss|sass|less|md|markdown|txt|log|csv|gitignore|editorconfig|uproject|uplugin|build|target|usf|ush|hlsl|glsl|shader|cginc|props|targets|sln|vcxproj|csproj|gradle|cmake|make|mk)$/i;
|
||
export function isCodeFile(name: string): boolean {
|
||
const n = name.toLowerCase();
|
||
if (/\.(uasset|umap|pak|exe|dll|so|dylib|bin|obj|lib|a|pdb|zip|7z|rar|gz|tar|png|jpe?g|gif|bmp|webp|svg|fbx|obj|gltf|glb|stl|ply|dae|wav|mp3|ogg|mp4|mov|ttf|otf)$/i.test(n)) return false;
|
||
return CODE_EXT.test(n);
|
||
}
|