diff --git a/desktop/frontend-next/src/styles/app.css b/desktop/frontend-next/src/styles/app.css
index ac0ffd5360..d40edc4e3a 100644
--- a/desktop/frontend-next/src/styles/app.css
+++ b/desktop/frontend-next/src/styles/app.css
@@ -3509,14 +3509,16 @@
.code-act:hover, .code-act:focus-visible { color: var(--text); background: var(--overlay) }
.code-act .studio-icon { width: 14px; height: 14px }
.code-act[data-state="done"] { color: var(--ok) }
- .code-body { display: flex; align-items: flex-start }
- .code-gutter {
- flex: none; padding: 11px 10px 12px 12px; text-align: end;
+ .code-body { display: block }
+ .code-body > pre.term { margin: 0; border-radius: 0; background: none; tab-size: 4; overflow-x: hidden; padding: 11px 0 12px }
+ .code-ln { display: flex; align-items: flex-start }
+ .code-no {
+ flex: none; box-sizing: content-box; width: var(--ln-w); padding: 0 10px 0 12px; text-align: end; font-style: normal;
font: var(--w-reg) var(--fs-ui)/var(--fs-title) var(--mono); color: var(--ghost); user-select: none;
- border-inline-end: 1px solid var(--hair);
+ border-inline-end: 1px solid var(--hair); align-self: stretch;
}
- .code-gutter > span { display: block }
- .code-body > pre.term { flex: 1; min-width: 0; margin: 0; border-radius: 0; background: none; tab-size: 4 }
+ .code-tx { flex: 1; min-width: 0; padding-inline: 14px 12px; white-space: pre-wrap; overflow-wrap: anywhere }
+ .md .code-body pre.term .code-tx code { white-space: inherit; overflow-wrap: inherit }
.code-wrap[data-folded] .code-body {
max-height: calc(19px * 12 + 11px); overflow: hidden;
mask-image: linear-gradient(to bottom, #000 72%, transparent);
diff --git a/desktop/frontend-next/src/ui/CodeBlock.test.tsx b/desktop/frontend-next/src/ui/CodeBlock.test.tsx
index e954d676b4..a17c54d351 100644
--- a/desktop/frontend-next/src/ui/CodeBlock.test.tsx
+++ b/desktop/frontend-next/src/ui/CodeBlock.test.tsx
@@ -4,6 +4,7 @@ import { cleanup, render, screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import "./testkit";
import { CodeBlock, extensionFor } from "./CodeBlock";
+import css from "../styles/app.css?raw";
afterEach(cleanup);
@@ -18,7 +19,7 @@ describe("a code block", () => {
const box = draw(lines(3));
expect(box.querySelector(".code-lang")?.textContent).toBe("go");
expect(box.querySelector(".code-lines")?.textContent).toContain("3");
- expect([...box.querySelectorAll(".code-gutter > span")].map((li) => li.textContent)).toEqual(["1", "2", "3"]);
+ expect([...box.querySelectorAll(".code-no")].map((li) => li.textContent)).toEqual(["1", "2", "3"]);
});
it("folds a long settled block and opens it on request", async () => {
@@ -40,3 +41,39 @@ describe("a code block", () => {
expect(extensionFor("brainfuck")).toBe("txt");
});
});
+
+// A long line wraps inside the block instead of scrolling it sideways, so its
+// number has to stay with the first row of the line it names.
+describe("a code block's rows", () => {
+ const rows = (box: HTMLElement) => [...box.querySelectorAll(".code-ln")];
+
+ it("is one row per source line, blank lines included, with no row for the closing newline", () => {
+ const box = draw("a\n\nb\n");
+ expect(rows(box).map((r) => r.querySelector(".code-no")?.textContent)).toEqual(["1", "2", "3"]);
+ expect(rows(box).map((r) => r.querySelector(".code-tx")?.textContent?.replace(/\n$/, ""))).toEqual(["a", "", "b"]);
+ });
+
+ it("splits a highlighted element that spans a newline into one piece per row", () => {
+ const source = "/* one\n two */ x\n";
+ const box = render(
+ {"/* one\n two */"}{" x\n"}
+
{children}
+
+ {rows.map((row, i) => (
+
+ {i + 1}
+ {row.length ? row : "\n"}
+
+ ))}
+