1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
//! Heading outline + anchor slugs (0146): `outline(source)` extracts
//! every heading with a GitHub-compatible `anchor_id`, so readers can
//! build TOC panels and resolve `[text](#anchor)` links.
//!
//! Row positions are deliberately NOT computed here: which visual row a
//! heading starts at depends on the typeset fold (wrap width, spacing
//! policy, the level-1 underline), which is the widget layer's — see
//! `MarkdownView::outline_rows` for the width-resolved form. This
//! module owns the SOURCE-level facts: order, level, text, anchor.
//!
//! ## Slug algorithm (documented deviations)
//!
//! GitHub's anchor rule, approximated with std-only Unicode: lowercase
//! everything; keep alphanumerics (Unicode letters/digits) and `_`;
//! map spaces to `-`; keep existing `-`; DROP everything else
//! (punctuation, emoji, symbols); duplicate slugs within one document
//! get `-1`, `-2`, … suffixes in reading order. Known deviation:
//! COMBINING MARKS are dropped (std has no Unicode-category-M test),
//! so decomposed accents slug differently from GitHub ("e\u{301}" →
//! "e"); precomposed accents ("é") match GitHub exactly.
use ;
/// One outline entry, in reading order.
/// Extracts the document outline: every heading, in order, with level,
/// plain text and a deduplicated anchor id.
///
/// ```
/// use abstracttui::render::md;
///
/// let doc = "# Intro\n\n## Setup\n\n## Setup\n";
/// let toc = md::outline(doc);
/// assert_eq!(toc.len(), 3);
/// assert_eq!(toc[1].anchor_id, "setup");
/// assert_eq!(toc[2].anchor_id, "setup-1", "duplicates suffix in order");
/// ```
/// The single-heading slug (no dedup — [`outline`] applies suffixes).
/// See the module docs for the exact algorithm and deviations.
///
/// ```
/// use abstracttui::render::md::slugify;
/// assert_eq!(slugify("Hello, World!"), "hello-world");
/// assert_eq!(slugify("café società"), "café-società");
/// ```
/// Deduplicate against `used`, GitHub-style: first occurrence keeps the
/// base, later ones append `-1`, `-2`, … (probing skips suffixes that
/// are themselves taken by literal headings).