Skip to content

Commit 1302e33

Browse files
Fix heading search highlight rendering
1 parent 21ea833 commit 1302e33

3 files changed

Lines changed: 33 additions & 8 deletions

File tree

app/src/features/editor/extensions/markdown-decorations/builders/headings.ts

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,13 @@ const HEADING_LEVEL: Record<string, number> = {
1919

2020
const headingMarkCache = new Map<string, Decoration>();
2121

22-
function getHeadingMark(level: number): Decoration {
23-
const key = `h${level}`;
22+
function getHeadingMark(level: number, inline = false): Decoration {
23+
const key = `${inline ? "inline-" : ""}h${level}`;
2424
let deco = headingMarkCache.get(key);
2525
if (!deco) {
26-
deco = Decoration.mark({ class: `cm-md-heading cm-md-h${level}` });
26+
deco = Decoration.mark({
27+
class: `cm-md-heading cm-md-h${level}${inline ? " cm-md-heading-inline" : ""}`,
28+
});
2729
headingMarkCache.set(key, deco);
2830
}
2931
return deco;
@@ -47,6 +49,7 @@ function handleATXHeading(
4749
resolved: SyntaxNode,
4850
level: number,
4951
onCursor: boolean,
52+
inlineHeading: boolean,
5053
out: DecorationEntry[],
5154
): void {
5255
const marks = resolved.getChildren("HeaderMark");
@@ -76,7 +79,7 @@ function handleATXHeading(
7679
out.push({
7780
from: node.from,
7881
to: node.to,
79-
decoration: getHeadingMark(level),
82+
decoration: getHeadingMark(level, inlineHeading),
8083
});
8184
}
8285

@@ -92,8 +95,8 @@ export function handleHeading(
9295

9396
const resolved = node.node;
9497
const onCursor = overlapsAny(node.from, node.to, ctx.cursorLines);
95-
const revealSyntax =
96-
onCursor || overlapsAny(node.from, node.to, ctx.searchMatches);
98+
const revealedBySearch = overlapsAny(node.from, node.to, ctx.searchMatches);
99+
const revealSyntax = onCursor || revealedBySearch;
97100

98101
if (!node.name.startsWith("ATX")) {
99102
return;
@@ -107,5 +110,5 @@ export function handleHeading(
107110
return;
108111
}
109112

110-
handleATXHeading(node, resolved, level, revealSyntax, out);
113+
handleATXHeading(node, resolved, level, revealSyntax, revealedBySearch, out);
111114
}

app/src/features/editor/extensions/markdown-decorations/index.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,15 @@ const markdownDecorationsTheme = EditorView.baseTheme({
4646
".cm-md-h6": {
4747
paddingBottom: "0rem",
4848
},
49+
".cm-md-heading-inline": {
50+
display: "inline",
51+
verticalAlign: "baseline",
52+
width: "auto",
53+
},
54+
".cm-md-heading-inline.cm-md-h1, .cm-md-heading-inline.cm-md-h2, .cm-md-heading-inline.cm-md-h3, .cm-md-heading-inline.cm-md-h4, .cm-md-heading-inline.cm-md-h5, .cm-md-heading-inline.cm-md-h6":
55+
{
56+
paddingBottom: "0",
57+
},
4958
".cm-md-strong": {
5059
fontWeight: "700",
5160
},

app/src/features/editor/extensions/search-rendering.test.ts

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,20 @@ describe("CodeMirror search rendering", () => {
125125

126126
expect(view.dom.querySelector(".cm-md-heading")).not.toBeNull();
127127
expect(view.dom.querySelector(".cm-md-h1")).not.toBeNull();
128-
expect(view.dom.querySelector(".cm-searchMatch")).not.toBeNull();
128+
expect(view.dom.querySelector(".cm-searchMatch")?.textContent).toBe("l");
129+
expect(view.dom.textContent).toContain("# lorem ipsum");
130+
131+
view.destroy();
132+
});
133+
134+
it("keeps heading prefix matches inline instead of wrapping the heading fragment", async () => {
135+
const { view } = createView("# lorem ipsum", "#");
136+
137+
await flush();
138+
139+
expect(view.dom.querySelector(".cm-md-heading")).not.toBeNull();
140+
expect(view.dom.querySelector(".cm-md-heading-inline")).not.toBeNull();
141+
expect(view.dom.querySelector(".cm-searchMatch")?.textContent).toBe("#");
129142
expect(view.dom.textContent).toContain("# lorem ipsum");
130143

131144
view.destroy();

0 commit comments

Comments
 (0)