@perspective-dev/viewer 5.0.0 → 5.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cdn/perspective-viewer.js +9 -9
- package/dist/cdn/perspective-viewer.js.map +4 -4
- package/dist/css/botanical.css +1 -1
- package/dist/css/dracula.css +1 -1
- package/dist/css/gruvbox-dark.css +1 -1
- package/dist/css/gruvbox.css +1 -1
- package/dist/css/icons.css +1 -1
- package/dist/css/intl/de.css +1 -1
- package/dist/css/intl/es.css +1 -1
- package/dist/css/intl/fr.css +1 -1
- package/dist/css/intl/ja.css +1 -1
- package/dist/css/intl/pt.css +1 -1
- package/dist/css/intl/zh.css +1 -1
- package/dist/css/intl.css +1 -1
- package/dist/css/monokai.css +1 -1
- package/dist/css/phosphor.css +1 -1
- package/dist/css/pro-dark.css +1 -1
- package/dist/css/pro.css +1 -1
- package/dist/css/solarized-dark.css +1 -1
- package/dist/css/solarized.css +1 -1
- package/dist/css/themes.css +1 -1
- package/dist/css/vaporwave.css +1 -1
- package/dist/docs/perspective-docs.json +1 -0
- package/dist/esm/column-format.d.ts +19 -28
- package/dist/esm/perspective-viewer.d.ts +2 -0
- package/dist/esm/perspective-viewer.inline.js +8 -8
- package/dist/esm/perspective-viewer.inline.js.map +4 -4
- package/dist/esm/perspective-viewer.js +9 -9
- package/dist/esm/perspective-viewer.js.map +4 -4
- package/dist/esm/providers.d.ts +79 -0
- package/dist/esm/ts-rs/CompactDisplay.d.ts +5 -0
- package/dist/esm/ts-rs/CurrencyCode.d.ts +1 -0
- package/dist/esm/ts-rs/CurrencyDisplay.d.ts +1 -0
- package/dist/esm/ts-rs/CurrencyNumberFormatStyle.d.ts +8 -0
- package/dist/esm/ts-rs/CurrencySign.d.ts +1 -0
- package/dist/esm/ts-rs/CustomDatetimeFormat.d.ts +1 -0
- package/dist/esm/ts-rs/CustomDatetimeStyleConfig.d.ts +54 -0
- package/dist/esm/ts-rs/CustomNumberFormatConfig.d.ts +34 -0
- package/dist/esm/ts-rs/DatetimeColorMode.d.ts +1 -0
- package/dist/esm/ts-rs/DatetimeFormatType.d.ts +6 -0
- package/dist/esm/ts-rs/FormatMode.d.ts +1 -0
- package/dist/esm/ts-rs/FormatUnit.d.ts +1 -0
- package/dist/esm/ts-rs/Notation.d.ts +17 -0
- package/dist/esm/ts-rs/NumberFormatStyle.d.ts +18 -0
- package/dist/esm/ts-rs/PanelViewerConfig.d.ts +43 -1
- package/dist/esm/ts-rs/PluginStaticConfig.d.ts +35 -0
- package/dist/esm/ts-rs/RestoreOptions.d.ts +19 -0
- package/dist/esm/ts-rs/RoundingMode.d.ts +1 -0
- package/dist/esm/ts-rs/RoundingPriority.d.ts +1 -0
- package/dist/esm/ts-rs/SignDisplay.d.ts +1 -0
- package/dist/esm/ts-rs/SimpleDatetimeFormat.d.ts +1 -0
- package/dist/esm/ts-rs/SimpleDatetimeStyleConfig.d.ts +24 -0
- package/dist/esm/ts-rs/SplitRollupMode.d.ts +9 -0
- package/dist/esm/ts-rs/StringColorMode.d.ts +1 -0
- package/dist/esm/ts-rs/TrailingZeroDisplay.d.ts +1 -0
- package/dist/esm/ts-rs/Unit.d.ts +1 -0
- package/dist/esm/ts-rs/UnitDisplay.d.ts +1 -0
- package/dist/esm/ts-rs/UnitNumberFormatStyle.d.ts +6 -0
- package/dist/esm/ts-rs/UseGrouping.d.ts +1 -0
- package/dist/esm/ts-rs/ViewerConfig.d.ts +43 -1
- package/dist/esm/ts-rs/ViewerConfigInitial.d.ts +175 -0
- package/dist/esm/ts-rs/ViewerConfigUpdate.d.ts +72 -0
- package/dist/esm/ts-rs/WindowSort.d.ts +7 -0
- package/dist/esm/ts-rs/WindowSortDir.d.ts +6 -0
- package/dist/esm/ts-rs/WindowSpec.d.ts +47 -0
- package/dist/esm/ts-rs/Windows.d.ts +10 -0
- package/dist/esm/ts-rs/WorkspaceConfigUpdate.d.ts +7 -6
- package/dist/wasm/perspective-viewer.d.ts +222 -26
- package/dist/wasm/perspective-viewer.js +378 -125
- package/dist/wasm/perspective-viewer.wasm +0 -0
- package/dist/wasm/perspective-viewer.wasm.d.ts +18 -15
- package/package.json +2 -1
- package/src/css/_viewer_bundle.css +1 -0
- package/src/css/column-selector.css +9 -0
- package/src/css/column-settings-panel.css +67 -2
- package/src/css/column-style.css +4 -3
- package/src/css/config-selector.css +60 -4
- package/src/css/containers/context-menu.css +12 -6
- package/src/css/containers/tabs.css +2 -2
- package/src/css/form/code-editor.css +2 -3
- package/src/css/form/mirrored-textarea.css +81 -0
- package/src/css/viewer.css +336 -0
- package/src/rust/agent/client/mod.rs +19 -0
- package/src/rust/agent/client/protocol.rs +320 -0
- package/src/rust/agent/client/transport.rs +287 -0
- package/src/rust/agent/client/turn.rs +174 -0
- package/src/rust/agent/config.rs +159 -0
- package/src/rust/agent/docs.rs +293 -0
- package/src/rust/agent/markdown/block.rs +509 -0
- package/src/rust/agent/markdown/entity.rs +52 -0
- package/src/rust/agent/markdown/events.rs +71 -0
- package/src/rust/agent/markdown/inline.rs +497 -0
- package/src/rust/agent/markdown/mod.rs +466 -0
- package/src/rust/agent/mod.rs +23 -0
- package/src/rust/agent/preamble.md +16 -0
- package/src/rust/agent/runtime.rs +201 -0
- package/src/rust/agent/slot.rs +236 -0
- package/src/rust/agent/tools.rs +1110 -0
- package/src/rust/components/chat_panel.rs +289 -0
- package/src/rust/components/column_selector/active_column.rs +21 -27
- package/src/rust/components/column_selector/column_selector_column_row.rs +73 -0
- package/src/rust/components/column_selector/config_selector.rs +83 -8
- package/src/rust/components/column_selector/expr_edit_button.rs +7 -1
- package/src/rust/components/column_selector/inactive_column.rs +8 -1
- package/src/rust/components/column_selector.rs +27 -3
- package/src/rust/components/column_settings_sidebar/style_tab.rs +2 -1
- package/src/rust/components/column_settings_sidebar/window_tab.rs +34 -0
- package/src/rust/components/column_settings_sidebar.rs +163 -16
- package/src/rust/components/containers/dragdrop_list.rs +110 -3
- package/src/rust/components/containers/sidebar.rs +99 -22
- package/src/rust/components/containers/split_panel.rs +6 -0
- package/src/rust/components/editable_header.rs +2 -0
- package/src/rust/components/form/code_editor.rs +14 -23
- package/src/rust/components/form/debug.rs +8 -5
- package/src/rust/components/form/mirrored_textarea.rs +165 -0
- package/src/rust/components/form/mod.rs +1 -0
- package/src/rust/components/main_panel.rs +5 -0
- package/src/rust/components/mod.rs +3 -0
- package/src/rust/components/panel_menu.rs +7 -0
- package/src/rust/components/panel_tab.rs +5 -0
- package/src/rust/components/portal.rs +7 -0
- package/src/rust/components/settings_panel.rs +80 -2
- package/src/rust/components/viewer/msg.rs +2 -0
- package/src/rust/components/viewer/panels.rs +9 -14
- package/src/rust/components/viewer/render.rs +14 -0
- package/src/rust/components/viewer/settings.rs +33 -1
- package/src/rust/components/viewer.rs +2 -0
- package/src/rust/components/window_editor.rs +985 -0
- package/src/rust/config/datetime_column_style/custom.rs +17 -0
- package/src/rust/config/datetime_column_style/simple.rs +10 -0
- package/src/rust/config/number_string_format.rs +33 -0
- package/src/rust/config/options.rs +18 -0
- package/src/rust/config/plugin_static_config.rs +120 -5
- package/src/rust/config/viewer_config.rs +220 -1
- package/src/rust/config/workspace_config.rs +7 -6
- package/src/rust/custom_elements/viewer.rs +209 -18
- package/src/rust/custom_events.rs +29 -0
- package/src/rust/lib.rs +17 -1
- package/src/rust/presentation/column_locator.rs +16 -1
- package/src/rust/presentation.rs +8 -0
- package/src/rust/queries/columns_iter_set.rs +7 -1
- package/src/rust/queries/get_viewer_config.rs +5 -1
- package/src/rust/renderer/dispatch.rs +85 -58
- package/src/rust/renderer.rs +34 -1
- package/src/rust/session/column_defaults_update.rs +18 -0
- package/src/rust/session/drag_drop_update.rs +18 -0
- package/src/rust/session/metadata.rs +64 -9
- package/src/rust/session/replace_expression_update.rs +2 -0
- package/src/rust/session.rs +7 -5
- package/src/rust/tasks/create_panel.rs +18 -8
- package/src/rust/tasks/edit_window.rs +155 -0
- package/src/rust/tasks/mod.rs +2 -0
- package/src/rust/tasks/restore_panel.rs +34 -17
- package/src/rust/tasks/sync_update_panels.rs +7 -23
- package/src/rust/utils/browser/dragdrop.rs +12 -0
- package/src/rust/utils/browser/tests/debounce.rs +49 -0
- package/src/rust/utils/debounce.rs +76 -27
- package/src/rust/workspace.rs +112 -20
- package/src/svg/pin-icon.svg +5 -0
- package/src/themes/icons.css +1 -0
- package/src/themes/intl/de.css +23 -0
- package/src/themes/intl/es.css +23 -0
- package/src/themes/intl/fr.css +23 -0
- package/src/themes/intl/ja.css +23 -0
- package/src/themes/intl/pt.css +23 -0
- package/src/themes/intl/zh.css +23 -0
- package/src/themes/intl.css +26 -1
- package/src/ts/column-format.ts +67 -46
- package/src/ts/perspective-viewer.ts +2 -0
- package/src/ts/providers.ts +96 -0
- package/src/ts/ts-rs/CompactDisplay.ts +3 -0
- package/src/ts/ts-rs/CurrencyCode.ts +3 -0
- package/src/ts/ts-rs/CurrencyDisplay.ts +3 -0
- package/src/ts/ts-rs/CurrencyNumberFormatStyle.ts +6 -0
- package/src/ts/ts-rs/CurrencySign.ts +3 -0
- package/src/ts/ts-rs/CustomDatetimeFormat.ts +3 -0
- package/src/ts/ts-rs/CustomDatetimeStyleConfig.ts +54 -0
- package/src/ts/ts-rs/CustomNumberFormatConfig.ts +25 -0
- package/src/ts/ts-rs/DatetimeColorMode.ts +3 -0
- package/src/ts/ts-rs/DatetimeFormatType.ts +8 -0
- package/src/ts/ts-rs/FormatMode.ts +3 -0
- package/src/ts/ts-rs/FormatUnit.ts +3 -0
- package/src/ts/ts-rs/Notation.ts +11 -0
- package/src/ts/ts-rs/NumberFormatStyle.ts +12 -0
- package/src/ts/ts-rs/PanelViewerConfig.ts +48 -1
- package/src/ts/ts-rs/PluginStaticConfig.ts +36 -1
- package/src/ts/ts-rs/RestoreOptions.ts +20 -0
- package/src/ts/ts-rs/RoundingMode.ts +3 -0
- package/src/ts/ts-rs/RoundingPriority.ts +3 -0
- package/src/ts/ts-rs/SignDisplay.ts +3 -0
- package/src/ts/ts-rs/SimpleDatetimeFormat.ts +3 -0
- package/src/ts/ts-rs/SimpleDatetimeStyleConfig.ts +25 -0
- package/src/ts/ts-rs/SplitRollupMode.ts +11 -0
- package/src/ts/ts-rs/StringColorMode.ts +3 -0
- package/src/ts/ts-rs/TrailingZeroDisplay.ts +3 -0
- package/src/ts/ts-rs/Unit.ts +3 -0
- package/src/ts/ts-rs/UnitDisplay.ts +3 -0
- package/src/ts/ts-rs/UnitNumberFormatStyle.ts +5 -0
- package/src/ts/ts-rs/UseGrouping.ts +3 -0
- package/src/ts/ts-rs/ViewerConfig.ts +48 -1
- package/src/ts/ts-rs/ViewerConfigInitial.ts +164 -0
- package/src/ts/ts-rs/ViewerConfigUpdate.ts +81 -2
- package/src/ts/ts-rs/WindowSort.ts +9 -0
- package/src/ts/ts-rs/WindowSortDir.ts +8 -0
- package/src/ts/ts-rs/WindowSpec.ts +47 -0
- package/src/ts/ts-rs/Windows.ts +10 -0
- package/src/ts/ts-rs/WorkspaceConfigUpdate.ts +7 -6
- /package/dist/wasm/snippets/{perspective-viewer-daeef4ceb066e6c7 → perspective-viewer-02e461f3d7062b26}/inline0.js +0 -0
- /package/dist/wasm/snippets/{perspective-viewer-daeef4ceb066e6c7 → perspective-viewer-02e461f3d7062b26}/inline1.js +0 -0
- /package/dist/wasm/snippets/{perspective-viewer-daeef4ceb066e6c7 → perspective-viewer-02e461f3d7062b26}/inline2.js +0 -0
- /package/dist/wasm/snippets/{perspective-viewer-daeef4ceb066e6c7 → perspective-viewer-02e461f3d7062b26}/inline3.js +0 -0
- /package/dist/wasm/snippets/{perspective-viewer-daeef4ceb066e6c7 → perspective-viewer-02e461f3d7062b26}/inline4.js +0 -0
- /package/dist/wasm/snippets/{perspective-viewer-daeef4ceb066e6c7 → perspective-viewer-02e461f3d7062b26}/inline5.js +0 -0
|
@@ -0,0 +1,509 @@
|
|
|
1
|
+
// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
|
|
2
|
+
// ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
|
|
3
|
+
// ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
|
|
4
|
+
// ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
|
|
5
|
+
// ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
|
|
6
|
+
// ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
|
|
7
|
+
// ┃ Copyright (c) 2017, the Perspective Authors. ┃
|
|
8
|
+
// ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
|
|
9
|
+
// ┃ This file is part of the Perspective library, distributed under the terms ┃
|
|
10
|
+
// ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
|
|
11
|
+
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
|
|
12
|
+
|
|
13
|
+
//! Block-level markdown: a line-oriented pass over container structure
|
|
14
|
+
//! (blockquotes, list items) via prefix-stripping recursion, with nom
|
|
15
|
+
//! recognizers for the line classifiers. Deliberately NOT combinator-driven
|
|
16
|
+
//! at the top level — block structure is indentation-context state, which
|
|
17
|
+
//! fits a line loop better than a grammar.
|
|
18
|
+
|
|
19
|
+
use nom::IResult;
|
|
20
|
+
use nom::branch::alt;
|
|
21
|
+
use nom::character::complete::{digit1, one_of};
|
|
22
|
+
use nom::combinator::{map, verify};
|
|
23
|
+
use nom::sequence::terminated;
|
|
24
|
+
|
|
25
|
+
use super::events::{Align, Event, Tag};
|
|
26
|
+
use super::inline;
|
|
27
|
+
|
|
28
|
+
pub fn parse(text: &str) -> Vec<Event> {
|
|
29
|
+
let lines = text.lines().map(str::to_owned).collect::<Vec<_>>();
|
|
30
|
+
let mut events = vec![];
|
|
31
|
+
parse_blocks(&lines, &mut events);
|
|
32
|
+
events
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
fn parse_blocks(lines: &[String], events: &mut Vec<Event>) {
|
|
36
|
+
let mut i = 0;
|
|
37
|
+
while i < lines.len() {
|
|
38
|
+
let line = &lines[i];
|
|
39
|
+
let trimmed = line.trim_start_matches(' ');
|
|
40
|
+
let indent = line.len() - trimmed.len();
|
|
41
|
+
if trimmed.is_empty() {
|
|
42
|
+
i += 1;
|
|
43
|
+
} else if indent <= 3
|
|
44
|
+
&& let Some((fence, fence_len, lang)) = fence_open(trimmed)
|
|
45
|
+
{
|
|
46
|
+
i = parse_fence(lines, i, indent, fence, fence_len, lang, events);
|
|
47
|
+
} else if indent >= 4 {
|
|
48
|
+
i = parse_indented_code(lines, i, events);
|
|
49
|
+
} else if let Some((level, content)) = atx_heading(trimmed) {
|
|
50
|
+
events.push(Event::Start(Tag::Heading(level)));
|
|
51
|
+
inline::parse_into(content, events);
|
|
52
|
+
events.push(Event::End);
|
|
53
|
+
i += 1;
|
|
54
|
+
} else if thematic_break(trimmed) {
|
|
55
|
+
events.push(Event::Rule);
|
|
56
|
+
i += 1;
|
|
57
|
+
} else if trimmed.starts_with('>') {
|
|
58
|
+
i = parse_quote(lines, i, events);
|
|
59
|
+
} else if let Some(aligns) = table_start(lines, i) {
|
|
60
|
+
i = parse_table(lines, i, aligns, events);
|
|
61
|
+
} else if let Some((_, start, content_col)) = list_marker(line) {
|
|
62
|
+
i = parse_list(lines, i, start, content_col, events);
|
|
63
|
+
} else {
|
|
64
|
+
i = parse_paragraph(lines, i, events);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/// ` ```lang ` / `~~~lang` — 3+ fence chars, optional info string whose
|
|
70
|
+
/// first word is the language.
|
|
71
|
+
fn fence_open(trimmed: &str) -> Option<(char, usize, Option<String>)> {
|
|
72
|
+
let fence = trimmed.chars().next().filter(|x| *x == '`' || *x == '~')?;
|
|
73
|
+
let len = trimmed.chars().take_while(|x| *x == fence).count();
|
|
74
|
+
if len < 3 {
|
|
75
|
+
return None;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
let info = trimmed[len..].trim();
|
|
79
|
+
if fence == '`' && info.contains('`') {
|
|
80
|
+
return None;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
Some((
|
|
84
|
+
fence,
|
|
85
|
+
len,
|
|
86
|
+
info.split_whitespace().next().map(str::to_owned),
|
|
87
|
+
))
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
#[allow(clippy::too_many_arguments)]
|
|
91
|
+
fn parse_fence(
|
|
92
|
+
lines: &[String],
|
|
93
|
+
i: usize,
|
|
94
|
+
indent: usize,
|
|
95
|
+
fence: char,
|
|
96
|
+
fence_len: usize,
|
|
97
|
+
lang: Option<String>,
|
|
98
|
+
events: &mut Vec<Event>,
|
|
99
|
+
) -> usize {
|
|
100
|
+
let mut content = String::new();
|
|
101
|
+
let mut j = i + 1;
|
|
102
|
+
while j < lines.len() {
|
|
103
|
+
let line = &lines[j];
|
|
104
|
+
let trimmed = line.trim_start_matches(' ');
|
|
105
|
+
let close_len = trimmed.chars().take_while(|x| *x == fence).count();
|
|
106
|
+
if close_len >= fence_len && trimmed[close_len..].trim().is_empty() {
|
|
107
|
+
j += 1;
|
|
108
|
+
break;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Strip up to the opening fence's indentation.
|
|
112
|
+
let strip = line.chars().take_while(|x| *x == ' ').count().min(indent);
|
|
113
|
+
|
|
114
|
+
content.push_str(&line[strip..]);
|
|
115
|
+
content.push('\n');
|
|
116
|
+
j += 1;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
events.push(Event::Start(Tag::CodeBlock(lang)));
|
|
120
|
+
events.push(Event::Text(content));
|
|
121
|
+
events.push(Event::End);
|
|
122
|
+
j
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
fn parse_indented_code(lines: &[String], i: usize, events: &mut Vec<Event>) -> usize {
|
|
126
|
+
let mut collected: Vec<String> = vec![];
|
|
127
|
+
let mut j = i;
|
|
128
|
+
while j < lines.len() {
|
|
129
|
+
let line = &lines[j];
|
|
130
|
+
if line.trim().is_empty() {
|
|
131
|
+
collected.push(String::new());
|
|
132
|
+
} else if indent_of(line) >= 4 {
|
|
133
|
+
collected.push(line[4..].to_owned());
|
|
134
|
+
} else {
|
|
135
|
+
break;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
j += 1;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
while collected.last().is_some_and(|x| x.is_empty()) {
|
|
142
|
+
collected.pop();
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
events.push(Event::Start(Tag::CodeBlock(None)));
|
|
146
|
+
events.push(Event::Text(collected.join("\n") + "\n"));
|
|
147
|
+
events.push(Event::End);
|
|
148
|
+
j
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/// 1–6 `#` + space (or end of line); optional closing `#` run stripped.
|
|
152
|
+
fn atx_heading(trimmed: &str) -> Option<(u8, &str)> {
|
|
153
|
+
let level = trimmed.chars().take_while(|x| *x == '#').count();
|
|
154
|
+
if level == 0 || level > 6 {
|
|
155
|
+
return None;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
let rest = &trimmed[level..];
|
|
159
|
+
if !rest.is_empty() && !rest.starts_with(' ') {
|
|
160
|
+
return None;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
let content = rest.trim();
|
|
164
|
+
let stripped = content.trim_end_matches('#');
|
|
165
|
+
let content =
|
|
166
|
+
if stripped.len() < content.len() && (stripped.is_empty() || stripped.ends_with(' ')) {
|
|
167
|
+
stripped.trim_end()
|
|
168
|
+
} else {
|
|
169
|
+
content
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
Some((level as u8, content))
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/// 3+ of the same `-`/`_`/`*` char, optionally space-separated, alone on
|
|
176
|
+
/// the line. Checked before list markers so `---` is a rule.
|
|
177
|
+
fn thematic_break(trimmed: &str) -> bool {
|
|
178
|
+
for fence in ['-', '_', '*'] {
|
|
179
|
+
if trimmed.chars().all(|x| x == fence || x == ' ')
|
|
180
|
+
&& trimmed.chars().filter(|x| *x == fence).count() >= 3
|
|
181
|
+
{
|
|
182
|
+
return true;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
false
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/// Consecutive `>`-prefixed lines; no lazy continuation.
|
|
190
|
+
fn parse_quote(lines: &[String], i: usize, events: &mut Vec<Event>) -> usize {
|
|
191
|
+
let mut inner = vec![];
|
|
192
|
+
let mut j = i;
|
|
193
|
+
while j < lines.len() {
|
|
194
|
+
let trimmed = lines[j].trim_start_matches(' ');
|
|
195
|
+
let Some(rest) = trimmed.strip_prefix('>') else {
|
|
196
|
+
break;
|
|
197
|
+
};
|
|
198
|
+
|
|
199
|
+
inner.push(rest.strip_prefix(' ').unwrap_or(rest).to_owned());
|
|
200
|
+
j += 1;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
events.push(Event::Start(Tag::BlockQuote));
|
|
204
|
+
parse_blocks(&inner, events);
|
|
205
|
+
events.push(Event::End);
|
|
206
|
+
j
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
fn nom_bullet(input: &str) -> IResult<&str, Option<u64>> {
|
|
210
|
+
map(one_of("-*+"), |_| None)(input)
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
fn nom_ordered(input: &str) -> IResult<&str, Option<u64>> {
|
|
214
|
+
map(
|
|
215
|
+
terminated(verify(digit1, |x: &str| x.len() <= 9), one_of(".)")),
|
|
216
|
+
|x: &str| Some(x.parse().unwrap_or(1)),
|
|
217
|
+
)(input)
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/// `- ` / `* ` / `+ ` / `1. ` / `1) ` → (indent, `List` payload, column
|
|
221
|
+
/// where item content begins).
|
|
222
|
+
fn list_marker(line: &str) -> Option<(usize, Option<u64>, usize)> {
|
|
223
|
+
let indent = indent_of(line);
|
|
224
|
+
if indent > 3 {
|
|
225
|
+
return None;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
let rest = &line[indent..];
|
|
229
|
+
let (after, start) = alt((nom_bullet, nom_ordered))(rest).ok()?;
|
|
230
|
+
if !after.is_empty() && !after.starts_with(' ') {
|
|
231
|
+
return None;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
let width = rest.len() - after.len();
|
|
235
|
+
Some((indent, start, indent + width + 1))
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
fn parse_list(
|
|
239
|
+
lines: &[String],
|
|
240
|
+
i: usize,
|
|
241
|
+
start: Option<u64>,
|
|
242
|
+
content_col: usize,
|
|
243
|
+
events: &mut Vec<Event>,
|
|
244
|
+
) -> usize {
|
|
245
|
+
events.push(Event::Start(Tag::List(start)));
|
|
246
|
+
let mut j = i;
|
|
247
|
+
let mut col = content_col;
|
|
248
|
+
loop {
|
|
249
|
+
// One item: the marker line's remainder, plus blank or
|
|
250
|
+
// content-indented continuation lines, prefix-stripped.
|
|
251
|
+
let mut item = vec![line_from(&lines[j], col)];
|
|
252
|
+
j += 1;
|
|
253
|
+
while j < lines.len() {
|
|
254
|
+
let line = &lines[j];
|
|
255
|
+
if line.trim().is_empty() {
|
|
256
|
+
item.push(String::new());
|
|
257
|
+
} else if indent_of(line) >= col {
|
|
258
|
+
item.push(line_from(line, col));
|
|
259
|
+
} else {
|
|
260
|
+
break;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
j += 1;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
while item.last().is_some_and(|x| x.is_empty()) {
|
|
267
|
+
item.pop();
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
let mut item_events = vec![];
|
|
271
|
+
parse_blocks(&item, &mut item_events);
|
|
272
|
+
events.push(Event::Start(Tag::Item));
|
|
273
|
+
events.extend(strip_item_paragraphs(item_events));
|
|
274
|
+
events.push(Event::End);
|
|
275
|
+
|
|
276
|
+
// Another marker of the same list kind continues the list.
|
|
277
|
+
match lines.get(j).and_then(|x| list_marker(x)) {
|
|
278
|
+
Some((_, next_start, next_col)) if next_start.is_some() == start.is_some() => {
|
|
279
|
+
col = next_col;
|
|
280
|
+
},
|
|
281
|
+
_ => break,
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
events.push(Event::End);
|
|
286
|
+
j
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
/// All lists render tight: top-level paragraph wrappers inside an item are
|
|
290
|
+
/// stripped (adjacent stripped paragraphs separated by a hard break), so
|
|
291
|
+
/// `<li>` holds inline content directly, with nested blocks intact.
|
|
292
|
+
fn strip_item_paragraphs(item_events: Vec<Event>) -> Vec<Event> {
|
|
293
|
+
let mut out = Vec::with_capacity(item_events.len());
|
|
294
|
+
let mut depth = 0usize;
|
|
295
|
+
let mut stripped = vec![];
|
|
296
|
+
let mut pending_break = false;
|
|
297
|
+
for event in item_events {
|
|
298
|
+
match event {
|
|
299
|
+
Event::Start(Tag::Paragraph) if depth == 0 => {
|
|
300
|
+
if pending_break {
|
|
301
|
+
out.push(Event::HardBreak);
|
|
302
|
+
pending_break = false;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
stripped.push(depth);
|
|
306
|
+
depth += 1;
|
|
307
|
+
},
|
|
308
|
+
Event::Start(tag) => {
|
|
309
|
+
if depth == 0 {
|
|
310
|
+
pending_break = false;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
depth += 1;
|
|
314
|
+
out.push(Event::Start(tag));
|
|
315
|
+
},
|
|
316
|
+
Event::End => {
|
|
317
|
+
depth -= 1;
|
|
318
|
+
if stripped.last() == Some(&depth) {
|
|
319
|
+
stripped.pop();
|
|
320
|
+
pending_break = true;
|
|
321
|
+
} else {
|
|
322
|
+
out.push(Event::End);
|
|
323
|
+
}
|
|
324
|
+
},
|
|
325
|
+
event => {
|
|
326
|
+
if depth == 0 {
|
|
327
|
+
pending_break = false;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
out.push(event);
|
|
331
|
+
},
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
out
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
/// A table begins at a line containing `|` whose successor is a delimiter
|
|
339
|
+
/// row with a MATCHING column count — the count check keeps prose containing
|
|
340
|
+
/// pipes (and `---` rules) from false-positives.
|
|
341
|
+
fn table_start(lines: &[String], i: usize) -> Option<Vec<Align>> {
|
|
342
|
+
if !lines[i].contains('|') {
|
|
343
|
+
return None;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
let aligns = delimiter_row(lines.get(i + 1)?)?;
|
|
347
|
+
(split_row(&lines[i]).len() == aligns.len()).then_some(aligns)
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
fn delimiter_row(line: &str) -> Option<Vec<Align>> {
|
|
351
|
+
if !line.contains('|') {
|
|
352
|
+
return None;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
let cells = split_row(line);
|
|
356
|
+
if cells.is_empty() {
|
|
357
|
+
return None;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
cells
|
|
361
|
+
.iter()
|
|
362
|
+
.map(|cell| {
|
|
363
|
+
let cell = cell.trim();
|
|
364
|
+
let dashes = cell.trim_start_matches(':').trim_end_matches(':');
|
|
365
|
+
if dashes.is_empty() || !dashes.chars().all(|x| x == '-') {
|
|
366
|
+
return None;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
Some(match (cell.starts_with(':'), cell.ends_with(':')) {
|
|
370
|
+
(true, true) => Align::Center,
|
|
371
|
+
(true, false) => Align::Left,
|
|
372
|
+
(false, true) => Align::Right,
|
|
373
|
+
(false, false) => Align::None,
|
|
374
|
+
})
|
|
375
|
+
})
|
|
376
|
+
.collect()
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
/// Split on unescaped `|` outside backtick code spans; outer pipes dropped,
|
|
380
|
+
/// cells trimmed. Per GFM, `\|` unescapes HERE (before inline parsing) —
|
|
381
|
+
/// that's how a pipe gets into a cell's code span; other escapes are left
|
|
382
|
+
/// for the inline pass.
|
|
383
|
+
fn split_row(line: &str) -> Vec<String> {
|
|
384
|
+
let chars = line.trim().chars().collect::<Vec<_>>();
|
|
385
|
+
let mut cells = vec![];
|
|
386
|
+
let mut cell = String::new();
|
|
387
|
+
let mut code: Option<usize> = None;
|
|
388
|
+
let mut i = 0;
|
|
389
|
+
while i < chars.len() {
|
|
390
|
+
match chars[i] {
|
|
391
|
+
'\\' if i + 1 < chars.len() => {
|
|
392
|
+
if chars[i + 1] != '|' {
|
|
393
|
+
cell.push('\\');
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
cell.push(chars[i + 1]);
|
|
397
|
+
i += 2;
|
|
398
|
+
},
|
|
399
|
+
'`' => {
|
|
400
|
+
let run = chars[i..].iter().take_while(|x| **x == '`').count();
|
|
401
|
+
code = match code {
|
|
402
|
+
None => Some(run),
|
|
403
|
+
Some(open) if open == run => None,
|
|
404
|
+
open => open,
|
|
405
|
+
};
|
|
406
|
+
|
|
407
|
+
cell.extend(std::iter::repeat_n('`', run));
|
|
408
|
+
i += run;
|
|
409
|
+
},
|
|
410
|
+
'|' if code.is_none() => {
|
|
411
|
+
cells.push(cell.trim().to_owned());
|
|
412
|
+
cell.clear();
|
|
413
|
+
i += 1;
|
|
414
|
+
},
|
|
415
|
+
c => {
|
|
416
|
+
cell.push(c);
|
|
417
|
+
i += 1;
|
|
418
|
+
},
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
cells.push(cell.trim().to_owned());
|
|
423
|
+
if cells.first().is_some_and(|x| x.is_empty()) {
|
|
424
|
+
cells.remove(0);
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
if cells.last().is_some_and(|x| x.is_empty()) {
|
|
428
|
+
cells.pop();
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
cells
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
fn parse_table(lines: &[String], i: usize, aligns: Vec<Align>, events: &mut Vec<Event>) -> usize {
|
|
435
|
+
let columns = aligns.len();
|
|
436
|
+
events.push(Event::Start(Tag::Table(aligns)));
|
|
437
|
+
events.push(Event::Start(Tag::TableHead));
|
|
438
|
+
for cell in split_row(&lines[i]) {
|
|
439
|
+
events.push(Event::Start(Tag::TableCell));
|
|
440
|
+
inline::parse_into(&cell, events);
|
|
441
|
+
events.push(Event::End);
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
events.push(Event::End);
|
|
445
|
+
let mut j = i + 2;
|
|
446
|
+
while j < lines.len() && !lines[j].trim().is_empty() && lines[j].contains('|') {
|
|
447
|
+
let cells = split_row(&lines[j]);
|
|
448
|
+
events.push(Event::Start(Tag::TableRow));
|
|
449
|
+
for c in 0..columns {
|
|
450
|
+
events.push(Event::Start(Tag::TableCell));
|
|
451
|
+
inline::parse_into(cells.get(c).map(String::as_str).unwrap_or(""), events);
|
|
452
|
+
events.push(Event::End);
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
events.push(Event::End);
|
|
456
|
+
j += 1;
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
events.push(Event::End);
|
|
460
|
+
j
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
/// Non-blank lines accumulate until a blank or a construct that interrupts
|
|
464
|
+
/// a paragraph; indented lines join it (indented code cannot interrupt).
|
|
465
|
+
fn parse_paragraph(lines: &[String], i: usize, events: &mut Vec<Event>) -> usize {
|
|
466
|
+
let mut text = String::new();
|
|
467
|
+
let mut j = i;
|
|
468
|
+
while j < lines.len() {
|
|
469
|
+
let line = &lines[j];
|
|
470
|
+
let trimmed = line.trim_start_matches(' ');
|
|
471
|
+
let indent = line.len() - trimmed.len();
|
|
472
|
+
if trimmed.is_empty()
|
|
473
|
+
|| j > i
|
|
474
|
+
&& indent <= 3
|
|
475
|
+
&& (fence_open(trimmed).is_some()
|
|
476
|
+
|| atx_heading(trimmed).is_some()
|
|
477
|
+
|| thematic_break(trimmed)
|
|
478
|
+
|| trimmed.starts_with('>')
|
|
479
|
+
|| list_marker(line).is_some()
|
|
480
|
+
|| table_start(lines, j).is_some())
|
|
481
|
+
{
|
|
482
|
+
break;
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
if j > i {
|
|
486
|
+
text.push('\n');
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
// Trailing spaces are significant (hard breaks) — only CR is
|
|
490
|
+
// stripped.
|
|
491
|
+
text.push_str(line.trim_end_matches('\r'));
|
|
492
|
+
j += 1;
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
events.push(Event::Start(Tag::Paragraph));
|
|
496
|
+
inline::parse_into(text.trim_start(), events);
|
|
497
|
+
events.push(Event::End);
|
|
498
|
+
j
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
fn indent_of(line: &str) -> usize {
|
|
502
|
+
line.chars().take_while(|x| *x == ' ').count()
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
/// The tail of `line` from byte column `col` (always a char boundary — the
|
|
506
|
+
/// prefix is ASCII spaces/markers), or empty when the line is shorter.
|
|
507
|
+
fn line_from(line: &str, col: usize) -> String {
|
|
508
|
+
line.get(col..).unwrap_or("").to_owned()
|
|
509
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
|
|
2
|
+
// ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
|
|
3
|
+
// ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
|
|
4
|
+
// ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
|
|
5
|
+
// ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
|
|
6
|
+
// ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
|
|
7
|
+
// ┃ Copyright (c) 2017, the Perspective Authors. ┃
|
|
8
|
+
// ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
|
|
9
|
+
// ┃ This file is part of the Perspective library, distributed under the terms ┃
|
|
10
|
+
// ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
|
|
11
|
+
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
|
|
12
|
+
|
|
13
|
+
//! Minimal HTML entity decoding: numeric references plus the named entities
|
|
14
|
+
//! that actually occur in LLM output. An unrecognized entity stays literal
|
|
15
|
+
//! text — a cosmetic divergence from CommonMark's 2,000-entry table, never a
|
|
16
|
+
//! correctness one (this module exists so we don't link that table).
|
|
17
|
+
|
|
18
|
+
const NAMED: &[(&str, char)] = &[
|
|
19
|
+
("amp", '&'),
|
|
20
|
+
("lt", '<'),
|
|
21
|
+
("gt", '>'),
|
|
22
|
+
("quot", '"'),
|
|
23
|
+
("apos", '\''),
|
|
24
|
+
("nbsp", '\u{a0}'),
|
|
25
|
+
("mdash", '—'),
|
|
26
|
+
("ndash", '–'),
|
|
27
|
+
("hellip", '…'),
|
|
28
|
+
("copy", '©'),
|
|
29
|
+
("reg", '®'),
|
|
30
|
+
("trade", '™'),
|
|
31
|
+
("deg", '°'),
|
|
32
|
+
("times", '×'),
|
|
33
|
+
("plusmn", '±'),
|
|
34
|
+
("middot", '·'),
|
|
35
|
+
];
|
|
36
|
+
|
|
37
|
+
/// Try to decode an entity reference at the start of `text` (which begins
|
|
38
|
+
/// just AFTER the `&`). Returns the decoded char and the length consumed
|
|
39
|
+
/// (excluding the `&`).
|
|
40
|
+
pub fn decode(text: &str) -> Option<(char, usize)> {
|
|
41
|
+
let semi = text.find(';').filter(|x| *x <= 10)?;
|
|
42
|
+
let name = &text[..semi];
|
|
43
|
+
let decoded = if let Some(hex) = name.strip_prefix("#x").or_else(|| name.strip_prefix("#X")) {
|
|
44
|
+
char::from_u32(u32::from_str_radix(hex, 16).ok()?)?
|
|
45
|
+
} else if let Some(dec) = name.strip_prefix('#') {
|
|
46
|
+
char::from_u32(dec.parse().ok()?)?
|
|
47
|
+
} else {
|
|
48
|
+
NAMED.iter().find(|(n, _)| *n == name)?.1
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
Some((decoded, semi + 1))
|
|
52
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
|
|
2
|
+
// ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
|
|
3
|
+
// ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
|
|
4
|
+
// ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
|
|
5
|
+
// ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
|
|
6
|
+
// ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
|
|
7
|
+
// ┃ Copyright (c) 2017, the Perspective Authors. ┃
|
|
8
|
+
// ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
|
|
9
|
+
// ┃ This file is part of the Perspective library, distributed under the terms ┃
|
|
10
|
+
// ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
|
|
11
|
+
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
|
|
12
|
+
|
|
13
|
+
//! The parser→renderer event vocabulary — the closed set of constructs the
|
|
14
|
+
//! chat renderer emits. Anything the parser can't classify degrades to
|
|
15
|
+
//! `Text`, so content is never lost, only unstyled.
|
|
16
|
+
|
|
17
|
+
/// GFM table column alignment.
|
|
18
|
+
#[derive(Clone, Debug, PartialEq)]
|
|
19
|
+
pub enum Align {
|
|
20
|
+
None,
|
|
21
|
+
Left,
|
|
22
|
+
Center,
|
|
23
|
+
Right,
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
#[derive(Clone, Debug, PartialEq)]
|
|
27
|
+
pub enum Tag {
|
|
28
|
+
Paragraph,
|
|
29
|
+
|
|
30
|
+
/// ATX heading level, `1..=6`.
|
|
31
|
+
Heading(u8),
|
|
32
|
+
|
|
33
|
+
BlockQuote,
|
|
34
|
+
|
|
35
|
+
/// A code block; the payload is the fence's language word, if any.
|
|
36
|
+
CodeBlock(Option<String>),
|
|
37
|
+
|
|
38
|
+
/// `None` renders `<ul>`; `Some(start)` renders `<ol>`.
|
|
39
|
+
List(Option<u64>),
|
|
40
|
+
|
|
41
|
+
Item,
|
|
42
|
+
Emphasis,
|
|
43
|
+
Strong,
|
|
44
|
+
Strikethrough,
|
|
45
|
+
Link {
|
|
46
|
+
dest_url: String,
|
|
47
|
+
title: String,
|
|
48
|
+
},
|
|
49
|
+
Image {
|
|
50
|
+
dest_url: String,
|
|
51
|
+
},
|
|
52
|
+
Table(Vec<Align>),
|
|
53
|
+
TableHead,
|
|
54
|
+
TableRow,
|
|
55
|
+
TableCell,
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/// `Start`/`End` are emitted in balanced pairs by construction — both come
|
|
59
|
+
/// from the same lexical scope in the parser.
|
|
60
|
+
#[derive(Clone, Debug, PartialEq)]
|
|
61
|
+
pub enum Event {
|
|
62
|
+
Start(Tag),
|
|
63
|
+
End,
|
|
64
|
+
Text(String),
|
|
65
|
+
|
|
66
|
+
/// An inline code span (block-level code is `Tag::CodeBlock` + `Text`).
|
|
67
|
+
Code(String),
|
|
68
|
+
SoftBreak,
|
|
69
|
+
HardBreak,
|
|
70
|
+
Rule,
|
|
71
|
+
}
|