UNPKG

equicord-companion

Version:

Equicord Companion is a vscode extension to test Equicord patches & webpack finds right from the comfort of your IDE

216 lines (182 loc) 6.25 kB
import { PathLike } from "fs"; import { stat } from "fs/promises"; import { CancellationToken, Progress, ProgressLocation, ProgressOptions, Uri, window, workspace } from "vscode"; export class ProgressBar { /** * WARNING: the user pressing cancel only makes this this promise reject. * * It does not stop the underlying action */ static forSingleFunc<T>(opts: ProgressOptions, func: () => PromiseLike<T>): PromiseLike<T> { return window.withProgress(opts, (p, c) => { return new Promise((res, rej) => { c.onCancellationRequested(() => rej(new Error("Cancelled by user"))); func() .then(res, rej); }); }); } private onCancel: () => void = () => void 0; private message: string; protected total: number; protected count = 0; private promise: any; protected resolve: any; private reject: any; protected progress?: Progress<{ increment: number; message: string; }>; private calcelationtoken?: CancellationToken; /** * @param total if true, only message will be shown. **If true, {@link finish} must be called when done** * @param message the message to show * @param onCancel called when the user presses the cancel button, the promise will already be rejected */ constructor(total: number, message: string, onCancel: () => void) { this.total = total; this.message = message; this.onCancel = onCancel; const [promise, resolve, reject] = resolvers(); this.promise = promise; this.resolve = resolve; this.reject = reject; } makeTitle() { return `${this.count}/${this.total}`; } start(): Promise<this> { const [ready, res, rej] = resolvers(); setTimeout(() => rej(new Error("Timeout")), 1000 * 5); window.withProgress({ location: ProgressLocation.Notification, title: this.message, cancellable: true, }, (p, c) => { this.progress = p; this.calcelationtoken = c; this.calcelationtoken.onCancellationRequested(() => { this.onCancel(); this.reject("Canceled"); }); res(this); return this.promise; }) .then(void 0, () => { }); return ready; } finish() { this.resolve(); } stop(e: any) { if (!this.progress) throw new Error("Progress not started"); this.reject(e); } increment() { if (!this.progress) throw new Error("Progress not started"); this.count++; this.progress.report({ increment: 100 / this.total, message: this.makeTitle(), }); if (this.count === this.total) this.resolve(); } } // needed because some things(formatting) report too fast for vscode to handle export class BufferedProgressBar extends ProgressBar { private markers: number[]; constructor(total: number, message: string, onCancel: () => void) { super(total, message, onCancel); this.markers = getPercentMarkers(total); } public override async increment() { if (!this.progress) throw new Error("Progress not started"); this.count++; if (this.markers.some((v) => this.count === v)) await new Promise((res) => setTimeout(res, 0)); this.progress.report({ increment: 100 / this.total, message: this.makeTitle(), }); if (this.count === this.total) this.resolve(); } } function resolvers<T>() { let reject; let resolve; const promise = new Promise<T>((res, rej) => { resolve = res; reject = rej; }); return [promise, resolve, reject]; } export enum SecTo { MS = 1000, SEC = 1, MIN = (1 / 60), } function getPercentMarkers(num: number): number[] { if (num < 100) return Array.from({ length: num }, (_, i) => i + 1); const interval = num / 100; // Calculate the interval between each point const points: number[] = []; for (let i = 1; i <= 100; i++) { const point = i * interval; points.push(point | 0); } return points; } export function getCurrentFolder() { return workspace.workspaceFolders?.[0]?.uri.fsPath; } export async function exists(path: PathLike) { try { return !!await stat(path); } catch { return false; } } /** * **PATH MUST EXIST** * * \@throws if the path doesnt exist * * use {@link exists} to check if it exists */ export async function isDirectory(path: PathLike) { return (await stat(path)).isDirectory(); } /** * **does not** format the modules code see {@link format} for more code formating * takes the raw contents of a module and prepends a header * @param moduleContents the module * @param moduleId the module id * @param isFind if the module is coming from a find eg: is it a partial module * @returns a string with the formatted module */ export function formatModule(moduleContents: string, moduleId: string | number | undefined = "000000", isFind?: boolean): string { if (isFind) return `// Webpack Module ${moduleId} \n${isFind ? `//OPEN FULL MODULE: ${moduleId}\n` : ""}//EXTRACED WEPBACK MODULE ${moduleId}\n 0,\n${moduleContents}`; return moduleContents; } /** * converts a string into a URI that will resolve to a file with the contents of the string * @param patched the contents of the file * @param filename the name of the file * @param filetype the file extension * @returns the Uri for the file */ export function mkStringUri(patched: any, filename = "module", filetype = "js"): Uri { const SUFFIX = `/${filename}.${filetype}`; if (filename.indexOf("/") !== -1 || filetype.indexOf("/") !== -1) throw new Error(`Filename and filetype must not contain \`/\`. Got: ${SUFFIX.substring(1)}`); const PREFIX = "equicord-companion://b64string/"; const a = Buffer.from(patched); return Uri.parse(PREFIX + a.toString("base64url") + SUFFIX); }