Skip to content

Commit 7ce7329

Browse files
diegorvclaude
andcommitted
fix(headings): match ATX headings on CRLF lines (fixes empty TOC on Windows files)
Context: HEADING_RE (wikilink/navigation.logic.ts) is the shared ATX-heading matcher used by the Table of Contents (toc.logic.extractTocHeadings), wikilink navigation (findHeadingPosition), wikilink completion, and copy-block-link. These callers split raw file content on '\n', so a CRLF-authored file leaves a trailing '\r' on each line. Problem: HEADING_RE was /^#{1,6}\s+(.+)$/. In JS regex `.` does not match '\r', and `$` (no multiline) only matches end-of-string or before a final '\n' -- not before a '\r'. So `# Heading\r` never matched. For any markdown file with CRLF line endings the TOC was completely empty ("No headings found"), and wikilink jumps to `#heading` returned null. The breakage only affected unedited files: once the user made any edit, CodeMirror rewrote the buffer to LF and headings reappeared. Solution: Add an optional trailing carriage return to the capture's terminator: /^#{1,6}\s+(.+)\r?$/. The greedy `(.+)` still stops before '\r' (so the capture is unchanged for LF input), `\r?` consumes the optional carriage return, then `$` matches end-of-line. Callers split on '\n' and compute positions as `line.length + 1`; since '\r' stays inside line.length, byte offsets remain correct for CRLF. Behavior: - CRLF files: TOC populates, wikilink/heading navigation and completion resolve. - LF files: capture group and positions byte-identical to before. Files: - src/lib/core/markdown-editor/extensions/wikilink/navigation.logic.ts:1-7 — HEADING_RE tolerates trailing '\r'; expanded doc comment. - src/tests/.../wikilink/navigation.logic.test.ts — findHeadingPosition over CRLF content (positions 0 and 28). - src/tests/lib/plugins/table-of-contents/toc.logic.test.ts — extractTocHeadings over CRLF content (levels, text, line, pos). - tasks/todo/bug-hunt-fixes.md — H7 marked done. Note: separate local heading regexes in live-preview/embed-resolver.logic.ts:2 and kanban.logic.ts:3 have the same latent CRLF gap; left untouched as they are outside this fix's scope. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent dac9bd0 commit 7ce7329

4 files changed

Lines changed: 26 additions & 3 deletions

File tree

src/lib/core/markdown-editor/extensions/wikilink/navigation.logic.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
1-
/** Regex matching any ATX heading: `# Title`, `## Title`, etc. */
2-
export const HEADING_RE = /^#{1,6}\s+(.+)$/;
1+
/**
2+
* Regex matching any ATX heading: `# Title`, `## Title`, etc.
3+
* Tolerates a trailing carriage return so headings are still matched when
4+
* callers split CRLF (Windows) content on `\n` and the line retains its `\r`.
5+
*/
6+
export const HEADING_RE = /^#{1,6}\s+(.+)\r?$/;
37

48
/** Regex matching a block-id marker at the end of a line: `text ^block-id` */
59
export const BLOCK_ID_RE = /\^([^\s]+)\s*$/;

src/tests/lib/core/markdown-editor/extensions/wikilink/navigation.logic.test.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,15 @@ describe('findHeadingPosition', () => {
5858
const content = '# Intro\n\n## Intro\n\ntext';
5959
expect(findHeadingPosition(content, 'Intro')).toBe(0);
6060
});
61+
62+
it('finds headings in a document with CRLF line endings', () => {
63+
// Windows/CRLF-authored notes leave a trailing \r on each split line;
64+
// the heading regex must tolerate it. Positions are byte offsets that
65+
// include the \r (split on \n keeps it in line.length).
66+
const content = '# Heading One\r\n\r\nSome text\r\n## Heading Two\r\n';
67+
expect(findHeadingPosition(content, 'Heading One')).toBe(0);
68+
expect(findHeadingPosition(content, 'Heading Two')).toBe(28);
69+
});
6170
});
6271

6372
describe('findBlockIdPosition', () => {

src/tests/lib/plugins/table-of-contents/toc.logic.test.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,16 @@ describe('extractTocHeadings', () => {
3838
]);
3939
});
4040

41+
it('extracts headings from a file with CRLF (Windows) line endings', () => {
42+
// Each split line keeps a trailing \r; without tolerating it the regex
43+
// matches nothing and the TOC is empty for unedited Windows notes.
44+
const content = '# Heading One\r\n\r\nSome text\r\n## Heading Two\r\n';
45+
expect(extractTocHeadings(content)).toEqual([
46+
{ level: 1, text: 'Heading One', line: 0, pos: 0 },
47+
{ level: 2, text: 'Heading Two', line: 3, pos: 28 },
48+
]);
49+
});
50+
4151
it('preserves heading order with gaps between levels', () => {
4252
const content = '# A\n### C';
4353
const result = extractTocHeadings(content);

tasks/todo/bug-hunt-fixes.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ This batch fixes the **7 HIGH** bugs (one commit each). Medium + Low are backlog
1313
- [x] H4: collection filter — single-row `not` group collapses to bare positive expression, inverting filter + persisting inverted YAML. `src/lib/features/collection/toolbar/filter.logic.ts:146-148`. Fix: shortcut only when `conjunction !== 'not'`.
1414
- [x] H5: properties panel crashes (Svelte `each_key_duplicate`) when frontmatter has alias + canonical twin (`color` + `_color`). `src/lib/features/properties/PropertiesView.svelte:92-96,221`. Fix: `dedupeCanonicalKeys` on parse path (or key=index).
1515
- [x] H6: kanban drag-drop misplaces cards (DOM filtered index spliced into full array) when a filter is active -> corrupts `.kanban`. `src/lib/plugins/kanban/KanbanView.svelte:372-393`. Fix: disable drag while filtered, or map filtered->absolute index.
16-
- [ ] H7: Table of Contents empty for CRLF files (`\r` breaks HEADING_RE). `src/lib/plugins/table-of-contents/toc.logic.ts:63-93` (root: `wikilink/navigation.logic.ts:2`). Fix: split `/\r?\n/` or strip `\r`; also fixes wikilink `#heading` jump on CRLF docs.
16+
- [x] H7: Table of Contents empty for CRLF files (`\r` breaks HEADING_RE). `src/lib/plugins/table-of-contents/toc.logic.ts:63-93` (root: `wikilink/navigation.logic.ts:2`). Fix: split `/\r?\n/` or strip `\r`; also fixes wikilink `#heading` jump on CRLF docs.
1717

1818
## Backlog — MEDIUM (22)
1919

0 commit comments

Comments
 (0)