Skip to content

Commit ededbf8

Browse files
authored
Bugfix/issue 2195 (#4118)
* add trace viewer * fix typo in inner custom recursion * simplify combineFilters to only kick in when needed * use decorated block to accumulate listing name and caption * latex format support
1 parent f5efac9 commit ededbf8

File tree

15 files changed

+447
-54
lines changed

15 files changed

+447
-54
lines changed

news/changelog-1.3.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
- Add overflow to tables generated from SQL code cells ([#3497](https://github.com/quarto-dev/quarto-cli/issues/3497)).
2222
- Fix support for parquet files in OJS code cells ([#3630](https://github.com/quarto-dev/quarto-cli/issues/3630)).
2323
- Forward bootstrap table classes from caption to table element ([#4036](https://github.com/quarto-dev/quarto-cli/issues/4036)).
24+
- Render code listings with names and captions correctly ([#2195](https://github.com/quarto-dev/quarto-cli/issues/2195)).
2425

2526
## Article Layout
2627

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<html>
2+
<head>
3+
<script src="main.js" type="module"></script>
4+
</head>
5+
<body>
6+
<div id="output"></div>
7+
</body>
8+
</html>
Lines changed: 196 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,196 @@
1+
import jsonpatch from "https://esm.sh/fast-json-patch";
2+
import * as d3 from "https://esm.sh/d3@7";
3+
// import YML from "https://esm.sh/json-to-pretty-yaml"; timing out, obnoxious
4+
import YML from "https://cdn.skypack.dev/json-to-pretty-yaml";
5+
6+
const searchParams = new URLSearchParams(new URL(document.URL).search);
7+
8+
const data = await fetch(searchParams.get("file") ?? "quarto-filter-trace.json");
9+
const json = await data.json();
10+
11+
const postProcessStrings = (array) => {
12+
if (array.length === 0) { return []; }
13+
const result = [array[0]];
14+
for (const el of array.slice(1)) {
15+
if (typeof result[result.length - 1] !== "string" ||
16+
typeof el !== "string") {
17+
result.push(el);
18+
continue;
19+
}
20+
result[result.length - 1] = result[result.length - 1] + el;
21+
}
22+
if (result.length === 1 && typeof result[0] === "string") {
23+
return result[0];
24+
}
25+
return result;
26+
}
27+
28+
const convertListAttributes = (listAttr) => ({
29+
start: listAttr[0],
30+
style: listAttr[1].t,
31+
delimiter: listAttr[2].t,
32+
});
33+
34+
const convertAttr = (attr) => `('${attr[0]}', [${attr[1].map(s => `'${s}'`).join(", ")}], [${attr[2].map(s => `'${s}'`).join(", ")}])`
35+
const convertCitation = (c => c)
36+
37+
const convert = (data) => {
38+
if (Array.isArray(data)) {
39+
return postProcessStrings(data.map(convert).flat());
40+
}
41+
if (typeof data === "object") {
42+
const constMap = {
43+
Space: " ",
44+
Null: null,
45+
SingleQuote: "'",
46+
DoubleQuote: '"',
47+
};
48+
if (constMap[data.t]) {
49+
return constMap[data.t];
50+
}
51+
if (data.t === "Str") return data.c;
52+
if (["BlockQuote", "BulletList", "Plain", "Para", "Strong", "Emph", "Underline", "Strikeout", "Quoted", "SingleQuote"].includes(data.t)) {
53+
return {
54+
t: data.t,
55+
content: convert(data.c)
56+
}
57+
}
58+
if (data.t === "Code") {
59+
return {
60+
t: data.t,
61+
attr: convertAttr(data.c[0]),
62+
text: data.c[1]
63+
}
64+
}
65+
if (data.t === "Cite") {
66+
return {
67+
t: data.t,
68+
content: convert(data.c[1]),
69+
citations: data.c[0].map(convertCitation)
70+
}
71+
}
72+
if (data.t === "Div" || data.t === "Span") {
73+
return {
74+
t: data.t,
75+
attr: convertAttr(data.c[0]),
76+
content: convert(data.c.slice(1)),
77+
}
78+
}
79+
if (data.t === "Header") {
80+
return {
81+
t: data.t,
82+
level: data.c[0],
83+
attr: convertAttr(data.c[1]),
84+
content: convert(data.c.slice(2))
85+
}
86+
}
87+
if (data.t === "Link") {
88+
return {
89+
t: data.t,
90+
attr: convertAttr(data.c[0]),
91+
content: convert(data.c[1]),
92+
target: data.c[2][0],
93+
title: data.c[2][1]
94+
}
95+
}
96+
if (data.t === "Image") {
97+
return {
98+
t: data.t,
99+
attr: convertAttr(data.c[0]),
100+
caption: convert(data.c[1]),
101+
src: data.c[2][0],
102+
title: data.c[2][1]
103+
}
104+
}
105+
if (data.t === "CodeBlock") {
106+
return {
107+
t: data.t,
108+
attr: convertAttr(data.c[0]),
109+
text: data.c[1],
110+
}
111+
}
112+
if (data.t === "OrderedList") {
113+
return {
114+
t: data.t,
115+
listAttributes: convertListAttributes(data.c[0]),
116+
content: convert(data.c[1])
117+
}
118+
}
119+
if (data.t === "MetaInlines" || data.t === "MetaBlocks") {
120+
return postProcessStrings(data.c.map(convert));
121+
}
122+
if (data.t === "MetaBool") {
123+
return data.c;
124+
}
125+
if (data.t === "SoftBreak") {
126+
return "";
127+
}
128+
129+
if (data.t === "MetaList") {
130+
return postProcessStrings(data.c.map(convert));
131+
}
132+
if (data.t === "MetaMap") {
133+
return convertMeta(data.c);
134+
}
135+
if (data.t === "RawBlock" || data.t === "RawInline") {
136+
return {
137+
t: data.t,
138+
format: data.c[0],
139+
text: data.c[1]
140+
}
141+
}
142+
throw new Error(`Can't handle type ${data.t}`);
143+
}
144+
return {
145+
name: "<value>",
146+
children: []
147+
}
148+
}
149+
150+
const convertMeta = (meta) => {
151+
return Object.fromEntries(Object.entries(meta).map(([key, value]) => {
152+
return [key, convert(value)]
153+
}));
154+
}
155+
156+
const convertDoc = (doc) => {
157+
return {
158+
meta: convertMeta(doc.meta),
159+
"pandoc-api-version": doc["pandoc-api-version"].join(","),
160+
blocks: doc.blocks.map(convert),
161+
}
162+
}
163+
164+
const drawTree = (data, summary) => {
165+
const el = d3.select("#output").append("div");
166+
const deets = el.append("details");
167+
if (summary) {
168+
deets.append("summary").text(summary);
169+
}
170+
deets.append("pre").text(YML.stringify(data));
171+
return deets;
172+
}
173+
174+
d3.select("#output").append("h2").text("Starting doc")
175+
176+
drawTree(convertDoc(json.data[0].doc), "Doc");
177+
let isNoOp = true;
178+
179+
for (let i = 1; i < json.data.length; ++i) {
180+
const ops = jsonpatch.compare(
181+
convertDoc(json.data[i-1].doc),
182+
convertDoc(json.data[i].doc));
183+
if (ops.length === 0) {
184+
d3.select("#output").append("h2").text(`Filter: ${json.data[i].state} (no op)`);
185+
if (!isNoOp) {
186+
drawTree(convertDoc(json.data[i].doc), "Doc");
187+
isNoOp = true;
188+
}
189+
continue;
190+
}
191+
isNoOp = false;
192+
193+
d3.select("#output").append("h2").text(`Filter: ${json.data[i].state}`);
194+
drawTree(convertDoc(json.data[i].doc), "Doc");
195+
drawTree(ops, "Ops").style("margin-bottom", "0.1em").style("margin-top", "0.1em");
196+
}

src/command/render/pandoc.ts

Lines changed: 41 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import { basename, dirname, isAbsolute, join } from "path/mod.ts";
99

1010
import { info } from "log/mod.ts";
1111

12-
import { existsSync, expandGlobSync } from "fs/mod.ts";
12+
import { existsSync, expandGlobSync, moveSync } from "fs/mod.ts";
1313

1414
import { stringify } from "encoding/yaml.ts";
1515
import { encode as base64Encode } from "encoding/base64.ts";
@@ -188,11 +188,16 @@ import {
188188
import { kRevealJSPlugins } from "../../extension/extension-shared.ts";
189189
import { kCitation } from "../../format/html/format-html-shared.ts";
190190
import { cslDate } from "../../core/csl.ts";
191+
import { quartoConfig } from "../../core/quarto.ts";
191192

192193
export async function runPandoc(
193194
options: PandocOptions,
194195
sysFilters: string[],
195196
): Promise<RunPandocResult | null> {
197+
const beforePandocHooks: (() => unknown)[] = [];
198+
const afterPandocHooks: (() => unknown)[] = [];
199+
const pandocEnv: { [key: string]: string } = {};
200+
196201
// compute cwd for render
197202
const cwd = dirname(options.source);
198203

@@ -1003,17 +1008,49 @@ export async function runPandoc(
10031008
// workaround for our wonky Lua timing routines
10041009
const luaEpoch = await getLuaTiming();
10051010

1011+
pandocEnv["QUARTO_FILTER_PARAMS"] = base64Encode(paramsJson);
1012+
1013+
if (pandocMetadata?.["_quarto"]?.["trace-filters"]) {
1014+
// const metadata = pandocMetadata?.["_quarto"]?.["trace-filters"];
1015+
beforePandocHooks.push(() => {
1016+
pandocEnv["QUARTO_TRACE_FILTERS"] = "true";
1017+
});
1018+
afterPandocHooks.push(() => {
1019+
const dest = join(
1020+
quartoConfig.sharePath(),
1021+
"../../package/src/common/trace-viewer",
1022+
pandocMetadata?.["_quarto"]?.["trace-filters"],
1023+
);
1024+
const source = join(cwd, "quarto-filter-trace.json");
1025+
if (source !== dest) {
1026+
try {
1027+
Deno.removeSync(dest);
1028+
} catch { // pass
1029+
}
1030+
moveSync(join(cwd, "quarto-filter-trace.json"), dest);
1031+
}
1032+
});
1033+
}
1034+
1035+
// run beforePandoc hooks
1036+
for (const hook of beforePandocHooks) {
1037+
await hook();
1038+
}
1039+
10061040
// run pandoc
10071041
const result = await execProcess(
10081042
{
10091043
cmd,
10101044
cwd,
1011-
env: {
1012-
"QUARTO_FILTER_PARAMS": base64Encode(paramsJson),
1013-
},
1045+
env: pandocEnv,
10141046
},
10151047
);
10161048

1049+
// run afterPandoc hooks
1050+
for (const hook of afterPandocHooks) {
1051+
await hook();
1052+
}
1053+
10171054
// resolve resource files from metadata
10181055
const resources: string[] = resourcesFromMetadata(
10191056
options.format.metadata[kResources],

src/resources/editor/tools/vs-code.mjs

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11073,6 +11073,7 @@ var require_yaml_intelligence_resources = __commonJS({
1107311073
hidden: true,
1107411074
object: {
1107511075
properties: {
11076+
"trace-filters": "string",
1107611077
tests: "object"
1107711078
}
1107811079
}
@@ -21048,12 +21049,12 @@ var require_yaml_intelligence_resources = __commonJS({
2104821049
mermaid: "%%"
2104921050
},
2105021051
"handlers/mermaid/schema.yml": {
21051-
_internalId: 146891,
21052+
_internalId: 146893,
2105221053
type: "object",
2105321054
description: "be an object",
2105421055
properties: {
2105521056
"mermaid-format": {
21056-
_internalId: 146883,
21057+
_internalId: 146885,
2105721058
type: "enum",
2105821059
enum: [
2105921060
"png",
@@ -21069,7 +21070,7 @@ var require_yaml_intelligence_resources = __commonJS({
2106921070
exhaustiveCompletions: true
2107021071
},
2107121072
theme: {
21072-
_internalId: 146890,
21073+
_internalId: 146892,
2107321074
type: "anyOf",
2107421075
anyOf: [
2107521076
{
@@ -21364,10 +21365,11 @@ function detectCaseConvention(key) {
2136421365
return "capitalizationCase";
2136521366
}
2136621367
const underscoreIndex = key.indexOf("_");
21367-
if (underscoreIndex !== -1) {
21368+
if (underscoreIndex !== -1 && underscoreIndex !== 0 && underscoreIndex !== key.length - 1) {
2136821369
return "underscore_case";
2136921370
}
21370-
if (key.indexOf("-") !== -1) {
21371+
const dashIndex = key.indexOf("-");
21372+
if (dashIndex !== -1 && dashIndex !== 0 && dashIndex !== key.length - 1) {
2137121373
return "dash-case";
2137221374
}
2137321375
return void 0;
@@ -21379,9 +21381,8 @@ function resolveCaseConventionRegex(keys, conventions) {
2137921381
"Internal Error: resolveCaseConventionRegex requires nonempty `conventions`"
2138021382
);
2138121383
}
21382-
console.log({ conventions });
2138321384
return {
21384-
pattern: conventions.map((c) => `(?:^${c}$)`).join("|"),
21385+
pattern: conventions.map((c) => `(${c})`).join("|"),
2138521386
list: conventions
2138621387
};
2138721388
}

0 commit comments

Comments
 (0)