kramdown-ansi 0.5.0 → 0.6.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.
- checksums.yaml +4 -4
- data/.utilsrc +0 -3
- data/CHANGES.md +68 -0
- data/README.md +10 -5
- data/Rakefile +19 -0
- data/kramdown-ansi.gemspec +7 -6
- data/lib/kramdown/ansi/styles.rb +9 -4
- data/lib/kramdown/ansi/width.rb +89 -17
- data/lib/kramdown/ansi.rb +90 -6
- data/lib/kramdown/version.rb +7 -2
- data/spec/assets/html.ansi +28 -0
- data/spec/assets/html.md +28 -0
- data/spec/assets/tables.ansi +348 -0
- data/spec/assets/tables.md +236 -0
- data/spec/kramdown/ansi/width_spec.rb +90 -0
- data/spec/kramdown/ansi_golden_spec.rb +19 -0
- data/spec/kramdown/ansi_spec.rb +7 -3
- data/spec/kramdown/render_spec.rb +156 -0
- data/spec/spec_helper.rb +33 -0
- data/tmp/.gitkeep +0 -0
- metadata +27 -4
|
@@ -0,0 +1,348 @@
|
|
|
1
|
+
╭───┬───╮
|
|
2
|
+
│ A │ B │
|
|
3
|
+
├───┼───┤
|
|
4
|
+
│ 1 │ 2 │
|
|
5
|
+
╰───┴───╯
|
|
6
|
+
|
|
7
|
+
[1m[4mCase 00 — Table at Document Start[0m[0m
|
|
8
|
+
|
|
9
|
+
[1m[4mCase 01 — Column Count Mismatch[0m[0m
|
|
10
|
+
|
|
11
|
+
Header has 3 columns, data rows have 5 and 1.
|
|
12
|
+
|
|
13
|
+
╭──────────┬───┬───┬───┬───╮
|
|
14
|
+
│ A │ B │ C │ │ │
|
|
15
|
+
├──────────┼───┼───┼───┼───┤
|
|
16
|
+
│ 1 │ 2 │ 3 │ 4 │ 5 │
|
|
17
|
+
├──────────┼───┼───┼───┼───┤
|
|
18
|
+
│ only-one │ │ │ │ │
|
|
19
|
+
├──────────┼───┼───┼───┼───┤
|
|
20
|
+
│ a │ b │ c │ d │ │
|
|
21
|
+
╰──────────┴───┴───┴───┴───╯
|
|
22
|
+
|
|
23
|
+
[1m[4mCase 02 — No Leading / Trailing Pipes[0m[0m
|
|
24
|
+
|
|
25
|
+
GFM allows omitting the outer [34m|[0m. Renderer must still split on inner
|
|
26
|
+
pipes.
|
|
27
|
+
|
|
28
|
+
╭───┬───┬───╮
|
|
29
|
+
│ A │ B │ C │
|
|
30
|
+
├───┼───┼───┤
|
|
31
|
+
│ 1 │ 2 │ 3 │
|
|
32
|
+
├───┼───┼───┤
|
|
33
|
+
│ 4 │ 5 │ 6 │
|
|
34
|
+
╰───┴───┴───╯
|
|
35
|
+
|
|
36
|
+
[1m[4mCase 03 — Escaped Pipe in Cell[0m[0m
|
|
37
|
+
|
|
38
|
+
[34m\|[0m should render as a literal [34m|[0m, not split the column.
|
|
39
|
+
|
|
40
|
+
╭───────────────────────┬───────╮
|
|
41
|
+
│ Col │ Value │
|
|
42
|
+
├───────────────────────┼───────┤
|
|
43
|
+
│ [34ma\|b[0m │ 1 │
|
|
44
|
+
├───────────────────────┼───────┤
|
|
45
|
+
│ [34mx \| y[0m │ 2 │
|
|
46
|
+
├───────────────────────┼───────┤
|
|
47
|
+
│ no-escape-but-literal │ pipe │
|
|
48
|
+
╰───────────────────────┴───────╯
|
|
49
|
+
|
|
50
|
+
[1m[4mCase 04 — Empty Cells[0m[0m
|
|
51
|
+
|
|
52
|
+
╭──────┬─────┬───────╮
|
|
53
|
+
│ A │ B │ C │
|
|
54
|
+
├──────┼─────┼───────┤
|
|
55
|
+
│ │ │ │
|
|
56
|
+
├──────┼─────┼───────┤
|
|
57
|
+
│ left │ │ right │
|
|
58
|
+
├──────┼─────┼───────┤
|
|
59
|
+
│ │ mid │ │
|
|
60
|
+
├──────┼─────┼───────┤
|
|
61
|
+
│ │ │ │
|
|
62
|
+
╰──────┴─────┴───────╯
|
|
63
|
+
|
|
64
|
+
[1m[4mCase 05 — Single-Column Table[0m[0m
|
|
65
|
+
|
|
66
|
+
╭───────╮
|
|
67
|
+
│ Only │
|
|
68
|
+
├───────┤
|
|
69
|
+
│ one │
|
|
70
|
+
├───────┤
|
|
71
|
+
│ two │
|
|
72
|
+
├───────┤
|
|
73
|
+
│ three │
|
|
74
|
+
╰───────╯
|
|
75
|
+
|
|
76
|
+
[1m[4mCase 06 — Header + Alignment Only (No Data Rows)[0m[0m
|
|
77
|
+
|
|
78
|
+
not a table
|
|
79
|
+
|
|
80
|
+
| X | Y | Z |
|
|
81
|
+
|:-:|:-:|:-:|
|
|
82
|
+
|
|
83
|
+
[1m[4mCase 07 — Back-to-Back Tables (No Blank Line)[0m[0m
|
|
84
|
+
|
|
85
|
+
╭─────┬─────╮
|
|
86
|
+
│ T1a │ T1b │
|
|
87
|
+
├─────┼─────┤
|
|
88
|
+
│ a │ b │
|
|
89
|
+
╰─────┴─────╯
|
|
90
|
+
|
|
91
|
+
╭─────┬─────╮
|
|
92
|
+
│ T2a │ T2b │
|
|
93
|
+
├─────┼─────┤
|
|
94
|
+
│ c │ d │
|
|
95
|
+
╰─────┴─────╯
|
|
96
|
+
|
|
97
|
+
[1m[4mCase 08 — Full-Width / Variable-Width Unicode[0m[0m
|
|
98
|
+
|
|
99
|
+
CJK, emoji (double-width), combining marks — column widths will [3mlie[0m.
|
|
100
|
+
|
|
101
|
+
╭─────────┬─────────┬──────────────────────────╮
|
|
102
|
+
│ Char │ Width │ Note │
|
|
103
|
+
├─────────┼─────────┼──────────────────────────┤
|
|
104
|
+
│ [34m你[0m │ 2 cells │ CJK │
|
|
105
|
+
├─────────┼─────────┼──────────────────────────┤
|
|
106
|
+
│ [34m💜[0m │ 2 cells │ Emoji │
|
|
107
|
+
├─────────┼─────────┼──────────────────────────┤
|
|
108
|
+
│ [34me\u0301[0m │ 1+0 │ Combining acute │
|
|
109
|
+
├─────────┼─────────┼──────────────────────────┤
|
|
110
|
+
│ [34m☝[0m │ 2 cells │ DBCS symbol │
|
|
111
|
+
├─────────┼─────────┼──────────────────────────┤
|
|
112
|
+
│ [34m|[0m │ 1 cell │ Looks like a pipe, isn't │
|
|
113
|
+
╰─────────┴─────────┴──────────────────────────╯
|
|
114
|
+
|
|
115
|
+
[1m[4mCase 09 — Markdown [3mInside[0m Cells[0m[0m
|
|
116
|
+
|
|
117
|
+
Bold, italic, code, links, strikethrough, nested.
|
|
118
|
+
|
|
119
|
+
╭───────────────────┬─────────╮
|
|
120
|
+
│ Cell │ Renders │
|
|
121
|
+
├───────────────────┼─────────┤
|
|
122
|
+
│ [34m**bold**[0m │ [1mbold[0m │
|
|
123
|
+
├───────────────────┼─────────┤
|
|
124
|
+
│ [34m*it*[0m │ [3mit[0m │
|
|
125
|
+
├───────────────────┼─────────┤
|
|
126
|
+
│ [34m`code`[0m │ [34mcode[0m │
|
|
127
|
+
├───────────────────┼─────────┤
|
|
128
|
+
│ [34m[link](https://x)[0m │ ]8;;https://x\link]8;;\ │
|
|
129
|
+
├───────────────────┼─────────┤
|
|
130
|
+
│ [34m~~gone~~[0m │ [9mgone[0m │
|
|
131
|
+
├───────────────────┼─────────┤
|
|
132
|
+
│ [34m**_both_**[0m │ [1m[3mboth[0m[0m │
|
|
133
|
+
╰───────────────────┴─────────╯
|
|
134
|
+
|
|
135
|
+
[1m[4mCase 10 — Alignment Row Has Wrong Column Count[0m[0m
|
|
136
|
+
|
|
137
|
+
| A | B | C |
|
|
138
|
+
|:-:|:-:|
|
|
139
|
+
|
|
140
|
+
[1m[4mCase 11 — Table Split by Blank Line[0m[0m
|
|
141
|
+
|
|
142
|
+
╭───┬───╮
|
|
143
|
+
│ A │ B │
|
|
144
|
+
├───┼───┤
|
|
145
|
+
│ 1 │ 2 │
|
|
146
|
+
╰───┴───╯
|
|
147
|
+
|
|
148
|
+
╭───┬───╮
|
|
149
|
+
│ A │ B │
|
|
150
|
+
├───┼───┤
|
|
151
|
+
│ 3 │ 4 │
|
|
152
|
+
╰───┴───╯
|
|
153
|
+
|
|
154
|
+
[1m[4mCase 12 — Excessive Whitespace Padding[0m[0m
|
|
155
|
+
|
|
156
|
+
╭───┬───┬───╮
|
|
157
|
+
│ A │ B │ C │
|
|
158
|
+
├───┼───┼───┤
|
|
159
|
+
│ 1 │ 2 │ 3 │
|
|
160
|
+
╰───┴───┴───╯
|
|
161
|
+
|
|
162
|
+
[1m[4mCase 13 — [34m<br>[0m and HTML in Cells[0m[0m
|
|
163
|
+
|
|
164
|
+
╭──────┬─────────────────────╮
|
|
165
|
+
│ Cell │ Content │
|
|
166
|
+
├──────┼─────────────────────┤
|
|
167
|
+
│ br │ line1line2 │
|
|
168
|
+
├──────┼─────────────────────┤
|
|
169
|
+
│ html │ [34m<span>inline</span>[0m │
|
|
170
|
+
├──────┼─────────────────────┤
|
|
171
|
+
│ both │ [1mbold[0m [3mitalic[0m │
|
|
172
|
+
╰──────┴─────────────────────╯
|
|
173
|
+
|
|
174
|
+
[1m[4mCase 14 — Pipe at Both Ends, Nothing Between[0m[0m
|
|
175
|
+
|
|
176
|
+
╭───╮
|
|
177
|
+
│ │
|
|
178
|
+
├───┤
|
|
179
|
+
│ │
|
|
180
|
+
╰───╯
|
|
181
|
+
|
|
182
|
+
[1m[4mCase 15 — One Data Row, 10 Columns[0m[0m
|
|
183
|
+
|
|
184
|
+
╭───┬───┬───┬───┬───┬───┬───┬───┬───┬────╮
|
|
185
|
+
│ 1 │ 2 │ 3 │ 4 │ 5 │ 6 │ 7 │ 8 │ 9 │ 10 │
|
|
186
|
+
├───┼───┼───┼───┼───┼───┼───┼───┼───┼────┤
|
|
187
|
+
│ a │ b │ c │ d │ e │ f │ g │ h │ i │ j │
|
|
188
|
+
╰───┴───┴───┴───┴───┴───┴───┴───┴───┴────╯
|
|
189
|
+
|
|
190
|
+
[1m[4mCase 16 — Alignment Row with Mixed/Invalid Markers[0m[0m
|
|
191
|
+
|
|
192
|
+
╭───┬───┬───╮
|
|
193
|
+
│ A │ B │ C │
|
|
194
|
+
├───┼───┼───┤
|
|
195
|
+
│ l │ r │ d │
|
|
196
|
+
╰───┴───┴───╯
|
|
197
|
+
|
|
198
|
+
[1m[4mCase 17 — Table at Document Start, Glued to Following Header[0m[0m
|
|
199
|
+
|
|
200
|
+
╭───┬───╮
|
|
201
|
+
│ a │ b │
|
|
202
|
+
├───┼───┤
|
|
203
|
+
│ 1 │ 2 │
|
|
204
|
+
╰───┴───╯
|
|
205
|
+
|
|
206
|
+
[1m[4mnext section[0m[0m
|
|
207
|
+
|
|
208
|
+
[1m[4mCase 18 — Table Glued to Preceding Header[0m[0m
|
|
209
|
+
|
|
210
|
+
[1m[4mheading[0m[0m
|
|
211
|
+
|
|
212
|
+
╭───┬───╮
|
|
213
|
+
│ a │ b │
|
|
214
|
+
├───┼───┤
|
|
215
|
+
│ 1 │ 2 │
|
|
216
|
+
╰───┴───╯
|
|
217
|
+
|
|
218
|
+
[1m[4mCase 19 — Single Column, No Trailing Pipe on Header[0m[0m
|
|
219
|
+
|
|
220
|
+
╭─────────╮
|
|
221
|
+
│ a table │
|
|
222
|
+
├─────────┤
|
|
223
|
+
│ hi │
|
|
224
|
+
╰─────────╯
|
|
225
|
+
|
|
226
|
+
[1m[4mCase 20 — Not a Table: Single Pipe Line[0m[0m
|
|
227
|
+
|
|
228
|
+
╭─────────────╮
|
|
229
|
+
│ not a table │
|
|
230
|
+
╰─────────────╯
|
|
231
|
+
|
|
232
|
+
[1m[4mCase 21 — Not a Table: Header + Sep, No Body, Missing Closing Pipe[0m[0m
|
|
233
|
+
|
|
234
|
+
|not a table
|
|
235
|
+
|-|
|
|
236
|
+
|
|
237
|
+
[1m[4mCase 22 — Table After Code Block, Glued[0m[0m
|
|
238
|
+
|
|
239
|
+
[34msome code
|
|
240
|
+
[0m
|
|
241
|
+
╭───┬───╮
|
|
242
|
+
│ a │ b │
|
|
243
|
+
├───┼───┤
|
|
244
|
+
│ 1 │ 2 │
|
|
245
|
+
╰───┴───╯
|
|
246
|
+
|
|
247
|
+
[1m[4mCase 23 — Table After List Item, Glued[0m[0m
|
|
248
|
+
|
|
249
|
+
· item one
|
|
250
|
+
|
|
251
|
+
· item two
|
|
252
|
+
|
|
253
|
+
╭───┬───╮
|
|
254
|
+
│ a │ b │
|
|
255
|
+
├───┼───┤
|
|
256
|
+
│ 1 │ 2 │
|
|
257
|
+
╰───┴───╯
|
|
258
|
+
|
|
259
|
+
[1m[4mCase 24 — No Leading Pipe on Separator[0m[0m
|
|
260
|
+
|
|
261
|
+
╭───┬───╮
|
|
262
|
+
│ a │ b │
|
|
263
|
+
╰───┴───╯
|
|
264
|
+
|
|
265
|
+
╭───┬───┬───╮
|
|
266
|
+
│ │ 1 │ 2 │
|
|
267
|
+
╰───┴───┴───╯
|
|
268
|
+
|
|
269
|
+
[1m[4mCase 25 — No Leading Pipe on Body Row[0m[0m
|
|
270
|
+
|
|
271
|
+
| a | b |
|
|
272
|
+
|---|---|
|
|
273
|
+
|
|
274
|
+
╭───┬───╮
|
|
275
|
+
│ 1 │ 2 │
|
|
276
|
+
╰───┴───╯
|
|
277
|
+
|
|
278
|
+
[1m[4mCase 26 — Indented Table Inside Ordered List[0m[0m
|
|
279
|
+
|
|
280
|
+
1. [1mFlatten it.[0m
|
|
281
|
+
|
|
282
|
+
╭─────────┬─────┬───────╮
|
|
283
|
+
│ Service │ Sub │ Value │
|
|
284
|
+
╰─────────┴─────┴───────╯
|
|
285
|
+
|
|
286
|
+
|:--------|:----|:------|
|
|
287
|
+
|
|
288
|
+
╭─────────────┬───────────────┬────╮
|
|
289
|
+
│ [34mollama_chat[0m │ [34mkramdown-ansi[0m │ 🟢 │
|
|
290
|
+
╰─────────────┴───────────────┴────╯
|
|
291
|
+
|
|
292
|
+
────────────────────────────────────────────────────────────────────────────────
|
|
293
|
+
|
|
294
|
+
╭──────┬────────────────────────────────────────────────────────────────╮
|
|
295
|
+
│ Case │ What It Stresses │
|
|
296
|
+
├──────┼────────────────────────────────────────────────────────────────┤
|
|
297
|
+
│ 01 │ Column count validation / overflow / underflow │
|
|
298
|
+
├──────┼────────────────────────────────────────────────────────────────┤
|
|
299
|
+
│ 02 │ Pipe-stripping logic (leading/trailing optional) │
|
|
300
|
+
├──────┼────────────────────────────────────────────────────────────────┤
|
|
301
|
+
│ 03 │ Escape sequence handling ([34m\|[0m) │
|
|
302
|
+
├──────┼────────────────────────────────────────────────────────────────┤
|
|
303
|
+
│ 04 │ Empty-string cell rendering, border alignment │
|
|
304
|
+
├──────┼────────────────────────────────────────────────────────────────┤
|
|
305
|
+
│ 05 │ Single-column edge (degenerate table) │
|
|
306
|
+
├──────┼────────────────────────────────────────────────────────────────┤
|
|
307
|
+
│ 06 │ Zero data rows — does it draw a header-only box? │
|
|
308
|
+
├──────┼────────────────────────────────────────────────────────────────┤
|
|
309
|
+
│ 07 │ Consecutive tables — does the second become data of the first? │
|
|
310
|
+
├──────┼────────────────────────────────────────────────────────────────┤
|
|
311
|
+
│ 08 │ Unicode width calculation for column sizing │
|
|
312
|
+
├──────┼────────────────────────────────────────────────────────────────┤
|
|
313
|
+
│ 09 │ Inline markdown rendering [3minside[0m a table cell │
|
|
314
|
+
├──────┼────────────────────────────────────────────────────────────────┤
|
|
315
|
+
│ 10 │ Alignment row with fewer columns than header │
|
|
316
|
+
├──────┼────────────────────────────────────────────────────────────────┤
|
|
317
|
+
│ 11 │ Blank line splitting one table into two │
|
|
318
|
+
├──────┼────────────────────────────────────────────────────────────────┤
|
|
319
|
+
│ 12 │ Whitespace normalization / trimming │
|
|
320
|
+
├──────┼────────────────────────────────────────────────────────────────┤
|
|
321
|
+
│ 13 │ Raw HTML + [34m<br>[0m in cell context │
|
|
322
|
+
├──────┼────────────────────────────────────────────────────────────────┤
|
|
323
|
+
│ 14 │ Cell with only whitespace — empty or not? │
|
|
324
|
+
├──────┼────────────────────────────────────────────────────────────────┤
|
|
325
|
+
│ 15 │ Very wide table — does it wrap or overflow? │
|
|
326
|
+
├──────┼────────────────────────────────────────────────────────────────┤
|
|
327
|
+
│ 16 │ Non-standard alignment markers │
|
|
328
|
+
├──────┼────────────────────────────────────────────────────────────────┤
|
|
329
|
+
│ 17 │ Table at doc start, glued to following header │
|
|
330
|
+
├──────┼────────────────────────────────────────────────────────────────┤
|
|
331
|
+
│ 18 │ Table glued to preceding header (no blank before) │
|
|
332
|
+
├──────┼────────────────────────────────────────────────────────────────┤
|
|
333
|
+
│ 19 │ Single col, missing closing pipe on header │
|
|
334
|
+
├──────┼────────────────────────────────────────────────────────────────┤
|
|
335
|
+
│ 20 │ Not a table: single pipe line │
|
|
336
|
+
├──────┼────────────────────────────────────────────────────────────────┤
|
|
337
|
+
│ 21 │ Not a table: header + sep, no body, missing pipe │
|
|
338
|
+
├──────┼────────────────────────────────────────────────────────────────┤
|
|
339
|
+
│ 22 │ Table after code block, no blank between │
|
|
340
|
+
├──────┼────────────────────────────────────────────────────────────────┤
|
|
341
|
+
│ 23 │ Table after list item, no blank between │
|
|
342
|
+
├──────┼────────────────────────────────────────────────────────────────┤
|
|
343
|
+
│ 24 │ No leading pipe on separator row │
|
|
344
|
+
├──────┼────────────────────────────────────────────────────────────────┤
|
|
345
|
+
│ 25 │ No leading pipe on body row │
|
|
346
|
+
├──────┼────────────────────────────────────────────────────────────────┤
|
|
347
|
+
│ 26 │ Indented table inside ordered list item │
|
|
348
|
+
╰──────┴────────────────────────────────────────────────────────────────╯
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
| A | B |
|
|
2
|
+
|---|---|
|
|
3
|
+
| 1 | 2 |
|
|
4
|
+
|
|
5
|
+
## Case 00 — Table at Document Start
|
|
6
|
+
|
|
7
|
+
## Case 01 — Column Count Mismatch
|
|
8
|
+
|
|
9
|
+
Header has 3 columns, data rows have 5 and 1.
|
|
10
|
+
|
|
11
|
+
| A | B | C |
|
|
12
|
+
|:-:|:-:|:-:|
|
|
13
|
+
| 1 | 2 | 3 | 4 | 5 |
|
|
14
|
+
| only-one |
|
|
15
|
+
| a | b | c | d |
|
|
16
|
+
|
|
17
|
+
## Case 02 — No Leading / Trailing Pipes
|
|
18
|
+
|
|
19
|
+
GFM allows omitting the outer `|`. Renderer must still split on inner pipes.
|
|
20
|
+
|
|
21
|
+
| A | B | C
|
|
22
|
+
|:-:|:-:|:-:
|
|
23
|
+
| 1 | 2 | 3
|
|
24
|
+
| 4 | 5 | 6
|
|
25
|
+
|
|
26
|
+
## Case 03 — Escaped Pipe in Cell
|
|
27
|
+
|
|
28
|
+
`\|` should render as a literal `|`, not split the column.
|
|
29
|
+
|
|
30
|
+
| Col | Value |
|
|
31
|
+
|:----|:------|
|
|
32
|
+
| `a\|b` | 1 |
|
|
33
|
+
| `x \| y` | 2 |
|
|
34
|
+
| no-escape-but-literal | pipe |
|
|
35
|
+
|
|
36
|
+
## Case 04 — Empty Cells
|
|
37
|
+
|
|
38
|
+
| A | B | C |
|
|
39
|
+
|:-:|:-:|:-:|
|
|
40
|
+
| | | |
|
|
41
|
+
| left | | right |
|
|
42
|
+
| | mid | |
|
|
43
|
+
| | | |
|
|
44
|
+
|
|
45
|
+
## Case 05 — Single-Column Table
|
|
46
|
+
|
|
47
|
+
| Only |
|
|
48
|
+
|:-----|
|
|
49
|
+
| one |
|
|
50
|
+
| two |
|
|
51
|
+
| three |
|
|
52
|
+
|
|
53
|
+
## Case 06 — Header + Alignment Only (No Data Rows)
|
|
54
|
+
|
|
55
|
+
not a table
|
|
56
|
+
|
|
57
|
+
| X | Y | Z |
|
|
58
|
+
|:-:|:-:|:-:|
|
|
59
|
+
|
|
60
|
+
## Case 07 — Back-to-Back Tables (No Blank Line)
|
|
61
|
+
|
|
62
|
+
| T1a | T1b |
|
|
63
|
+
|:----|:----|
|
|
64
|
+
| a | b |
|
|
65
|
+
| T2a | T2b |
|
|
66
|
+
|:----|:----|
|
|
67
|
+
| c | d |
|
|
68
|
+
|
|
69
|
+
## Case 08 — Full-Width / Variable-Width Unicode
|
|
70
|
+
|
|
71
|
+
CJK, emoji (double-width), combining marks — column widths will *lie*.
|
|
72
|
+
|
|
73
|
+
| Char | Width | Note |
|
|
74
|
+
|:-----|:------|:-----|
|
|
75
|
+
| `你` | 2 cells | CJK |
|
|
76
|
+
| `💜` | 2 cells | Emoji |
|
|
77
|
+
| `e\u0301` | 1+0 | Combining acute |
|
|
78
|
+
| `☝` | 2 cells | DBCS symbol |
|
|
79
|
+
| `|` | 1 cell | Looks like a pipe, isn't |
|
|
80
|
+
|
|
81
|
+
## Case 09 — Markdown *Inside* Cells
|
|
82
|
+
|
|
83
|
+
Bold, italic, code, links, strikethrough, nested.
|
|
84
|
+
|
|
85
|
+
| Cell | Renders |
|
|
86
|
+
|:-----|:--------|
|
|
87
|
+
| `**bold**` | **bold** |
|
|
88
|
+
| `*it*` | *it* |
|
|
89
|
+
| `` `code` `` | `code` |
|
|
90
|
+
| `[link](https://x)` | [link](https://x) |
|
|
91
|
+
| `~~gone~~` | ~~gone~~ |
|
|
92
|
+
| `**_both_**` | **_both_** |
|
|
93
|
+
|
|
94
|
+
## Case 10 — Alignment Row Has Wrong Column Count
|
|
95
|
+
|
|
96
|
+
| A | B | C |
|
|
97
|
+
|:-:|:-:|
|
|
98
|
+
|
|
99
|
+
## Case 11 — Table Split by Blank Line
|
|
100
|
+
|
|
101
|
+
| A | B |
|
|
102
|
+
|:-:|:-:|
|
|
103
|
+
| 1 | 2 |
|
|
104
|
+
|
|
105
|
+
| A | B |
|
|
106
|
+
|:-:|:-:|
|
|
107
|
+
| 3 | 4 |
|
|
108
|
+
|
|
109
|
+
## Case 12 — Excessive Whitespace Padding
|
|
110
|
+
|
|
111
|
+
| A | B | C |
|
|
112
|
+
| :---: | :---: | :---: |
|
|
113
|
+
| 1 | 2 | 3 |
|
|
114
|
+
|
|
115
|
+
## Case 13 — `<br>` and HTML in Cells
|
|
116
|
+
|
|
117
|
+
| Cell | Content |
|
|
118
|
+
|:-----|:--------|
|
|
119
|
+
| br | line1<br>line2 |
|
|
120
|
+
| html | `<span>inline</span>` |
|
|
121
|
+
| both | **bold** <br> *italic* |
|
|
122
|
+
|
|
123
|
+
## Case 14 — Pipe at Both Ends, Nothing Between
|
|
124
|
+
|
|
125
|
+
| |
|
|
126
|
+
|:-:|
|
|
127
|
+
| |
|
|
128
|
+
|
|
129
|
+
## Case 15 — One Data Row, 10 Columns
|
|
130
|
+
|
|
131
|
+
| 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
|
|
132
|
+
|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:--:|
|
|
133
|
+
| a | b | c | d | e | f | g | h | i | j |
|
|
134
|
+
|
|
135
|
+
## Case 16 — Alignment Row with Mixed/Invalid Markers
|
|
136
|
+
|
|
137
|
+
| A | B | C |
|
|
138
|
+
|:--|--:|---|
|
|
139
|
+
| l | r | d |
|
|
140
|
+
|
|
141
|
+
## Case 17 — Table at Document Start, Glued to Following Header
|
|
142
|
+
|
|
143
|
+
| a | b |
|
|
144
|
+
|---|---|
|
|
145
|
+
| 1 | 2 |
|
|
146
|
+
# next section
|
|
147
|
+
|
|
148
|
+
## Case 18 — Table Glued to Preceding Header
|
|
149
|
+
|
|
150
|
+
# heading
|
|
151
|
+
| a | b |
|
|
152
|
+
|---|---|
|
|
153
|
+
| 1 | 2 |
|
|
154
|
+
|
|
155
|
+
## Case 19 — Single Column, No Trailing Pipe on Header
|
|
156
|
+
|
|
157
|
+
| a table
|
|
158
|
+
|-|
|
|
159
|
+
|hi|
|
|
160
|
+
|
|
161
|
+
## Case 20 — Not a Table: Single Pipe Line
|
|
162
|
+
|
|
163
|
+
|not a table
|
|
164
|
+
|
|
165
|
+
## Case 21 — Not a Table: Header + Sep, No Body, Missing Closing Pipe
|
|
166
|
+
|
|
167
|
+
|not a table
|
|
168
|
+
|-|
|
|
169
|
+
|
|
170
|
+
## Case 22 — Table After Code Block, Glued
|
|
171
|
+
|
|
172
|
+
```
|
|
173
|
+
some code
|
|
174
|
+
```
|
|
175
|
+
| a | b |
|
|
176
|
+
|---|---|
|
|
177
|
+
| 1 | 2 |
|
|
178
|
+
|
|
179
|
+
## Case 23 — Table After List Item, Glued
|
|
180
|
+
|
|
181
|
+
- item one
|
|
182
|
+
- item two
|
|
183
|
+
| a | b |
|
|
184
|
+
|---|---|
|
|
185
|
+
| 1 | 2 |
|
|
186
|
+
|
|
187
|
+
## Case 24 — No Leading Pipe on Separator
|
|
188
|
+
|
|
189
|
+
| a | b |
|
|
190
|
+
:---|---:
|
|
191
|
+
| 1 | 2 |
|
|
192
|
+
|
|
193
|
+
## Case 25 — No Leading Pipe on Body Row
|
|
194
|
+
|
|
195
|
+
| a | b |
|
|
196
|
+
|---|---|
|
|
197
|
+
1 | 2
|
|
198
|
+
|
|
199
|
+
## Case 26 — Indented Table Inside Ordered List
|
|
200
|
+
|
|
201
|
+
1. **Flatten it.**
|
|
202
|
+
|
|
203
|
+
| Service | Sub | Value |
|
|
204
|
+
|:--------|:----|:------|
|
|
205
|
+
| `ollama_chat` | `kramdown-ansi` | 🟢 |
|
|
206
|
+
|
|
207
|
+
---
|
|
208
|
+
|
|
209
|
+
| Case | What It Stresses |
|
|
210
|
+
|:-----|:-----------------|
|
|
211
|
+
| 01 | Column count validation / overflow / underflow |
|
|
212
|
+
| 02 | Pipe-stripping logic (leading/trailing optional) |
|
|
213
|
+
| 03 | Escape sequence handling (`\|`) |
|
|
214
|
+
| 04 | Empty-string cell rendering, border alignment |
|
|
215
|
+
| 05 | Single-column edge (degenerate table) |
|
|
216
|
+
| 06 | Zero data rows — does it draw a header-only box? |
|
|
217
|
+
| 07 | Consecutive tables — does the second become data of the first? |
|
|
218
|
+
| 08 | Unicode width calculation for column sizing |
|
|
219
|
+
| 09 | Inline markdown rendering *inside* a table cell |
|
|
220
|
+
| 10 | Alignment row with fewer columns than header |
|
|
221
|
+
| 11 | Blank line splitting one table into two |
|
|
222
|
+
| 12 | Whitespace normalization / trimming |
|
|
223
|
+
| 13 | Raw HTML + `<br>` in cell context |
|
|
224
|
+
| 14 | Cell with only whitespace — empty or not? |
|
|
225
|
+
| 15 | Very wide table — does it wrap or overflow? |
|
|
226
|
+
| 16 | Non-standard alignment markers |
|
|
227
|
+
| 17 | Table at doc start, glued to following header |
|
|
228
|
+
| 18 | Table glued to preceding header (no blank before) |
|
|
229
|
+
| 19 | Single col, missing closing pipe on header |
|
|
230
|
+
| 20 | Not a table: single pipe line |
|
|
231
|
+
| 21 | Not a table: header + sep, no body, missing pipe |
|
|
232
|
+
| 22 | Table after code block, no blank between |
|
|
233
|
+
| 23 | Table after list item, no blank between |
|
|
234
|
+
| 24 | No leading pipe on separator row |
|
|
235
|
+
| 25 | No leading pipe on body row |
|
|
236
|
+
| 26 | Indented table inside ordered list item |
|
|
@@ -1,4 +1,8 @@
|
|
|
1
1
|
describe Kramdown::ANSI::Width do
|
|
2
|
+
let :ansi do
|
|
3
|
+
Term::ANSIColor
|
|
4
|
+
end
|
|
5
|
+
|
|
2
6
|
before do
|
|
3
7
|
allow(Tins::Terminal).to receive(:columns).and_return 80
|
|
4
8
|
end
|
|
@@ -58,12 +62,52 @@ describe Kramdown::ANSI::Width do
|
|
|
58
62
|
expect(truncated.size).to eq 64
|
|
59
63
|
end
|
|
60
64
|
|
|
65
|
+
it 'can truncate ansi escape sequences with length' do
|
|
66
|
+
truncated = described_class.truncate([ ansi.red { ?A * 10 }, ansi.green { ?A * 10 } ] * 5 * ' ', length: 64)
|
|
67
|
+
expect(truncated).to eq(
|
|
68
|
+
"\e[31mAAAAAAAAAA\e[0m \e[32mAAAAAAAAAA\e[0m \e[31mAAAAAAAAAA\e[0m \e[32mAAAAAAAAAA\e[0m \e[31mAAAAAAAAAA\e[0m \e[32mAAAAAAAA…\e[0m"
|
|
69
|
+
)
|
|
70
|
+
expect(Term::ANSIColor.uncolor {truncated }.size).to eq 64
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
it 'can truncate wide emojis with length' do
|
|
74
|
+
truncated = described_class.truncate('ABC' + ?😍 * 10 + 'DEF', length: 10)
|
|
75
|
+
expect(truncated).to eq("ABC😍😍😍…")
|
|
76
|
+
expect(truncated.size).to eq 7
|
|
77
|
+
expect(described_class.display_width(truncated)).to eq 10
|
|
78
|
+
truncated = described_class.truncate('AB' + ?😍 * 10 + 'DEF', length: 10)
|
|
79
|
+
expect(truncated).to eq("AB😍😍😍…")
|
|
80
|
+
expect(truncated.size).to eq 6
|
|
81
|
+
expect(described_class.display_width(truncated)).to eq 9
|
|
82
|
+
truncated = described_class.truncate('A' + ?😍 * 10 + 'DEF', length: 10)
|
|
83
|
+
expect(truncated).to eq("A😍😍😍😍…")
|
|
84
|
+
expect(truncated.size).to eq 6
|
|
85
|
+
expect(described_class.display_width(truncated)).to eq 10
|
|
86
|
+
truncated = described_class.truncate('A' * 5 + ?😍 + 'B' * 5, length: 10)
|
|
87
|
+
expect(truncated).to eq("AAAAA😍BB…")
|
|
88
|
+
expect(truncated.size).to eq 9
|
|
89
|
+
expect(described_class.display_width(truncated)).to eq 10
|
|
90
|
+
truncated = described_class.truncate('A' * 3 + ?😍 + 'B' + ?😁 + 'C' * 5, length: 10)
|
|
91
|
+
expect(truncated).to eq("AAA😍B😁C…")
|
|
92
|
+
expect(truncated.size).to eq 8
|
|
93
|
+
expect(described_class.display_width(truncated)).to eq 10
|
|
94
|
+
truncated = described_class.truncate('A' * 3 + ?😍 + ansi.red { 'B' } + ?😁 + 'C' * 5, length: 10)
|
|
95
|
+
expect(truncated).to eq("AAA😍\e[31mB\e[0m😁C…\e[0m")
|
|
96
|
+
expect(truncated.size).to eq 21
|
|
97
|
+
expect(described_class.display_width(truncated)).to eq 10
|
|
98
|
+
end
|
|
99
|
+
|
|
61
100
|
it 'cannot truncate if not necessary' do
|
|
62
101
|
text = [ ?A * 10 ] * 5 * ' '
|
|
63
102
|
truncated = described_class.truncate(text, length: 54)
|
|
64
103
|
expect(truncated).to eq text
|
|
65
104
|
end
|
|
66
105
|
|
|
106
|
+
it 'can truncate empty strings' do
|
|
107
|
+
truncated = described_class.truncate('', length: 23)
|
|
108
|
+
expect(truncated).to be_empty
|
|
109
|
+
end
|
|
110
|
+
|
|
67
111
|
it 'can truncate with length 0' do
|
|
68
112
|
truncated = described_class.truncate([ ?A * 10 ] * 10 * ' ', length: 0)
|
|
69
113
|
expect(truncated).to be_empty
|
|
@@ -76,5 +120,51 @@ describe Kramdown::ANSI::Width do
|
|
|
76
120
|
)
|
|
77
121
|
expect(truncated.size).to eq 64
|
|
78
122
|
end
|
|
123
|
+
|
|
124
|
+
it 'keeps pure ANSI strings (no chars)' do
|
|
125
|
+
truncated = described_class.truncate(ansi.red { '' }, length: 10)
|
|
126
|
+
expect(truncated).to eq "\e[31m\e[0m"
|
|
127
|
+
expect(truncated.size).to eq 9
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
it 'handles zero-width characters correctly' do
|
|
131
|
+
src = "A#{("\u200d"*10)}B"
|
|
132
|
+
truncated = described_class.truncate(src, length: 3)
|
|
133
|
+
expect(described_class.display_width(truncated)).to eq(2)
|
|
134
|
+
expect(truncated).to match "A.*B"
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
it 'preserves newlines in multi-line text' do
|
|
138
|
+
truncated = described_class.truncate("hello\nworld", length: 8)
|
|
139
|
+
expect(truncated).to include("\n")
|
|
140
|
+
expect(described_class.display_width(truncated)).to eq 8
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
it 'supports multi‑cell ellipsis' do
|
|
144
|
+
truncated = described_class.truncate('ABC' + ?😇 * 10, length: 8, ellipsis: '… (more)')
|
|
145
|
+
expect(truncated).to eq "… (more)"
|
|
146
|
+
expect(described_class.display_width(truncated)).to eq 8
|
|
147
|
+
expect(truncated.length).to eq 8
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
it 'appends reset if truncated after an escape that lacks reset' do
|
|
151
|
+
truncated = described_class.truncate(ansi.red { 'Hello' }, length: 4)
|
|
152
|
+
expect(truncated).to eq "\e[31mHel…\e[0m"
|
|
153
|
+
expect(described_class.remove_ansi_escapes(truncated).size).to eq 4
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
it 'appends single reset for nested styles when truncated' do
|
|
157
|
+
truncated = described_class.truncate(ansi.red { ansi.bold { 'Hi' } }, length: 2)
|
|
158
|
+
expect(truncated).to eq "\e[31m\e[1mH…\e[0m"
|
|
159
|
+
expect(described_class.display_width(truncated)).to eq 2
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
it 'omits escapes that fall after the truncation point' do
|
|
163
|
+
src = "#{ansi.red { 'A' }}B#{ansi.green { 'C' }}D"
|
|
164
|
+
truncated = described_class.truncate(src, length: 2)
|
|
165
|
+
expect(truncated).not_to include "\e[32m"
|
|
166
|
+
expect(truncated).to eq "\e[31mA…\e[0m"
|
|
167
|
+
expect(described_class.display_width(truncated)).to eq 2
|
|
168
|
+
end
|
|
79
169
|
end
|
|
80
170
|
end
|