import * as colors from "fmt/colors";
type StackEntry = {
pos: string;
name: string;
line: string;
col: string;
};
const compareEntry = (prev: StackEntry, next: StackEntry): boolean => {
return prev.pos === next.pos && prev.line === next.line &&
prev.col === next.col;
};
let previousStack: StackEntry[] = [];
const compareStacks = (prev: StackEntry[], next: StackEntry[]): number => {
prev = prev.toReversed();
next = next.toReversed();
let i = 0;
while (i < prev.length && i < next.length && compareEntry(prev[i], next[i])) {
i++;
}
return i;
};
export const getStackAsArray = (
format?: "json" | "raw" | "ansi",
offset?: number,
) => {
let rawStack = (new Error().stack ?? "").split("\n").slice(offset ?? 2);
const m = rawStack[rawStack.length - 1].match(
/^.*at async (.*)src\/quarto.ts:\d+:\d+$/,
);
if (!m) {
console.log(
"Could not find quarto.ts in stack trace, is QUARTO_DENO_V8_OPTIONS set with a sufficiently-large stack size?",
);
}
rawStack = rawStack.filter((s) =>
!(s.match(/eventLoopTick/) && s.match(/core\/01_core.js/))
);
if (m && (typeof format !== "undefined") && (format !== "raw")) {
const pathPrefix = m[1];
rawStack = rawStack.map((s) => s.replace(pathPrefix, ""));
const entries: StackEntry[] = rawStack.map((s) => {
const m1 = s.match(/^.*at (async )?(src\/quarto.ts):(\d+):(\d+)$/);
if (m1) {
return {
pos: m1[2],
name: `<main>`,
line: m1[3],
col: m1[4] + (m1[1] ? 6 : 0),
};
}
const m2 = s.match(/^.*at (async )?(.*) \((src\/.+):(\d+):(\d+)\)$/) ||
s.match(/^.*at (async )?(.*)(src\/.+):(\d+):(\d+)$/);
if (m2) {
return {
pos: m2[3],
name: `${m2[2]}`,
line: m2[4],
col: m2[5] + (m2[1] ? 6 : 0),
};
}
const m3 = s.match(
/^.*at (async )?(.*) \(ext:(core\/.+):(\d+):(\d+)*\)$/,
);
if (m3) {
return {
pos: m3[3],
name: `${m3[2]}`,
line: m3[4],
col: m3[5] + (m3[1] ? 6 : 0),
};
}
const m4 = s.match(
/^.*at (async )?(.*) \((http.+):(\d+):(\d+)*\)$/,
);
if (m4) {
return {
pos: m4[3],
name: `${m4[2]}`,
line: m4[4],
col: m4[5] + (m4[1] ? 6 : 0),
};
}
const m5 = s.match(
/^.*at (.*)\(<anonymous>\)$/,
);
if (m5) {
return {
pos: "",
name: `${m5[1]}`,
line: "",
col: "",
};
}
throw new Error(`Unexpected stack entry: ${s}`);
});
if (format === "json") {
return entries;
}
const maxPosLength = Math.max(...entries.map((e) => e.pos.length));
const maxLineLength = Math.max(
...entries.map((e) => String(e.line).length),
);
const maxColLength = Math.max(...entries.map((e) => String(e.col).length));
const accentColor = colors.gray;
const commonPrefixLength = compareStacks(previousStack, entries);
rawStack = entries.map((e, i) => {
const linkedPos = `\x1b]8;;${
pathPrefix.replace("file://", "vscode://file")
}${e.pos}:${e.line}:${e.col}\x1b\\${e.pos}\x1b]8;;\x1b\\`;
const srcPadding = " ".repeat(maxPosLength - e.pos.length);
const linePadding = " ".repeat(maxLineLength - String(e.line).length);
const colPadding = " ".repeat(maxColLength - String(e.col).length);
const isFirstChange = i === entries.length - commonPrefixLength - 1;
if (!isFirstChange) {
return `${srcPadding}${
accentColor(
linkedPos + ":" + linePadding + e.line + colPadding + ":" + e.col +
": " + e.name,
)
}`;
}
return `${srcPadding}${linkedPos}${
accentColor(":")
}${linePadding}${e.line}${accentColor(":")}${colPadding}${e.col}${
accentColor(":")
} ${colors.yellow(e.name)}`;
});
previousStack = entries;
}
return rawStack;
};
export const getStack = (format?: "json" | "raw" | "ansi", offset?: number) => {
return "Stack:\n" +
getStackAsArray(format, offset ? offset + 1 : 3).join("\n");
};
export const debugPrint = (...data: any[]) => {
console.log(...data);
};
export const debugLogWithStack = async (...data: unknown[]) => {
const payload = {
payload: data,
stack: getStackAsArray(),
timestamp: new Date().toISOString(),
};
await Deno.writeTextFile(
"/tmp/stack-debug.json",
JSON.stringify(payload) + "\n",
{
append: true,
},
);
};