Skip to content

Commit cbc7b70

Browse files
authored
Merge pull request #9425 from quarto-dev/bugfix/9422
jupyter - don't merge streams across other output cells
2 parents a21d4c7 + 9b762d7 commit cbc7b70

File tree

5 files changed

+80
-25
lines changed

5 files changed

+80
-25
lines changed

news/changelog-1.5.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ All changes included in 1.5:
7070
- ([#8998](https://github.com/quarto-dev/quarto-cli/issues/8998)): Interpret slide separation markers `---` correctly when creating the `.ipynb` intermediate notebook from a `.qmd` file.
7171
- ([#9133](https://github.com/quarto-dev/quarto-cli/issues/9133)): Fix issue with Jupyter engine when using paths containing special characters.
7272
- ([#9255](https://github.com/quarto-dev/quarto-cli/issues/9255)): Support cell source fields of type `string`.
73+
- ([#9422](https://github.com/quarto-dev/quarto-cli/issues/9422)): Improve the stream merging algorithm in output cells to avoid merging outputs that should not be merged.
7374

7475
## Website Listings
7576

src/core/jupyter/jupyter-fixups.ts

Lines changed: 12 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -23,32 +23,26 @@ export function fixupStreams(nb: JupyterNotebook): JupyterNotebook {
2323
if (cell.cell_type !== "code" || cell.outputs === undefined) {
2424
continue;
2525
}
26-
let i = 0;
2726
if (cell.outputs.length === 0) {
2827
continue;
2928
}
29+
const newOutputs: JupyterOutput[] = [cell.outputs[0]];
30+
let i = 1;
3031
while (i < cell.outputs.length) {
32+
const prevOutput: JupyterOutput = newOutputs[newOutputs.length - 1];
3133
const thisOutput: JupyterOutput = cell.outputs[i];
32-
if (thisOutput.output_type === "stream") {
33-
// collect all the stream outputs with the same name
34-
const streams = cell.outputs.filter((output) =>
35-
output.output_type === "stream" && output.name === thisOutput.name
36-
);
37-
// join them together
38-
const joinedText = streams.map((output) => output.text ?? []).flat();
39-
const newOutput: JupyterOutput = {
40-
output_type: "stream",
41-
name: thisOutput.name,
42-
text: joinedText,
43-
};
44-
cell.outputs[i] = newOutput;
45-
cell.outputs = cell.outputs.filter((output, j) =>
46-
i === j ||
47-
(output.output_type !== "stream" || output.name !== thisOutput.name)
48-
);
34+
if (
35+
thisOutput.output_type === "stream" &&
36+
prevOutput.output_type === "stream" &&
37+
thisOutput.name === prevOutput.name
38+
) {
39+
prevOutput.text = [...prevOutput.text ?? [], ...thisOutput.text ?? []];
40+
} else {
41+
newOutputs.push(thisOutput);
4942
}
5043
i++;
5144
}
45+
cell.outputs = newOutputs;
5246
}
5347
return nb;
5448
}

src/core/jupyter/jupyter.ts

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1610,7 +1610,13 @@ async function mdFromCodeCell(
16101610
if (output.output_type === "stream") {
16111611
const stream = output as JupyterOutputStream;
16121612
if (asis && stream.name === "stdout") {
1613-
md.push(stream.text.join(""));
1613+
let text: string[] = [];
1614+
if (typeof stream.text === "string") {
1615+
text = [stream.text];
1616+
} else {
1617+
text = stream.text;
1618+
}
1619+
md.push(text.join(""));
16141620
} else {
16151621
md.push(mdOutputStream(stream));
16161622
}
@@ -1750,21 +1756,28 @@ function isMarkdown(output: JupyterOutput, options: JupyterToMarkdownOptions) {
17501756
}
17511757

17521758
function mdOutputStream(output: JupyterOutputStream) {
1759+
let text: string[] = [];
1760+
if (typeof output.text === "string") {
1761+
text = [output.text];
1762+
} else {
1763+
text = output.text;
1764+
}
1765+
17531766
// trim off warning source line for notebook
17541767
if (output.name === "stderr") {
1755-
if (output.text[0]) {
1756-
const firstLine = output.text[0].replace(
1768+
if (text[0]) {
1769+
const firstLine = text[0].replace(
17571770
/<ipython-input.*?>:\d+:\s+/,
17581771
"",
17591772
);
17601773
return mdCodeOutput(
1761-
[firstLine, ...output.text.slice(1)].map(colors.stripColor),
1774+
[firstLine, ...text.slice(1)].map(colors.stripColor),
17621775
);
17631776
}
17641777
}
17651778

17661779
// normal default handling
1767-
return mdCodeOutput(output.text.map(colors.stripColor));
1780+
return mdCodeOutput(text.map(colors.stripColor));
17681781
}
17691782

17701783
async function mdOutputError(

src/core/jupyter/types.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -166,12 +166,12 @@ export interface JupyterOutput {
166166
metadata?: Record<string, unknown>;
167167
data?: Record<string, unknown>;
168168
name?: string;
169-
text?: string[];
169+
text?: string[] | string;
170170
}
171171

172172
export interface JupyterOutputStream extends JupyterOutput {
173173
name: "stdout" | "stderr";
174-
text: string[];
174+
text: string[] | string;
175175
}
176176

177177
export interface JupyterOutputDisplayData extends JupyterOutput {
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
---
2+
title: "Generate Plots within Sub-Sections"
3+
format:
4+
html:
5+
echo: false
6+
# embed-resources: true
7+
engine: jupyter
8+
_quarto:
9+
tests:
10+
html:
11+
ensureHtmlElements:
12+
- ["section#scatter img"]
13+
---
14+
15+
```{python}
16+
#| label: setup
17+
import matplotlib.pyplot as plt
18+
```
19+
20+
```{python}
21+
#| label: define-data
22+
23+
data = {'apple': 10, 'orange': 15, 'lemon': 5, 'lime': 20}
24+
names = list(data.keys())
25+
values = list(data.values())
26+
```
27+
28+
## Plots and Subsections
29+
30+
```{python}
31+
#| output: asis
32+
33+
print("\n### Bar\n")
34+
35+
plt.bar(names, values)
36+
plt.show(block=False)
37+
38+
print("\n### Scatter\n")
39+
40+
plt.scatter(names, values)
41+
plt.show(block=False)
42+
43+
print("\n### Line\n")
44+
45+
plt.plot(names, values)
46+
plt.show(block=False)
47+
```

0 commit comments

Comments
 (0)