markdansi 0.1.2 → 0.1.3

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.
Files changed (3) hide show
  1. package/README.md +33 -0
  2. package/dist/render.js +100 -4
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -50,6 +50,39 @@ const custom = createRenderer({
50
50
  highlighter: (code, lang) => code.toUpperCase(),
51
51
  });
52
52
  console.log(custom('`inline`\n\n```\nblock code\n```'));
53
+
54
+ // Example: real syntax highlighting with Shiki (TS + Swift)
55
+ import { bundledLanguages, bundledThemes, createHighlighter } from 'shiki';
56
+
57
+ const shiki = await createHighlighter({
58
+ themes: [bundledThemes['github-dark']],
59
+ langs: [bundledLanguages.typescript, bundledLanguages.swift],
60
+ });
61
+
62
+ const highlighted = createRenderer({
63
+ highlighter: (code, lang) => {
64
+ if (!lang) return code;
65
+ const normalized = lang.toLowerCase();
66
+ if (!['ts', 'typescript', 'swift'].includes(normalized)) return code;
67
+ const { tokens } = shiki.codeToTokens(code, {
68
+ lang: normalized === 'swift' ? 'swift' : 'ts',
69
+ theme: 'github-dark',
70
+ });
71
+ return tokens
72
+ .map((line) =>
73
+ line
74
+ .map((token) =>
75
+ token.color ? `\u001b[38;2;${parseInt(token.color.slice(1, 3), 16)};${parseInt(
76
+ token.color.slice(3, 5),
77
+ 16,
78
+ )};${parseInt(token.color.slice(5, 7), 16)}m${token.content}\u001b[39m` : token.content,
79
+ )
80
+ .join(''),
81
+ )
82
+ .join('\n');
83
+ },
84
+ });
85
+ console.log(highlighted('```ts\nconst x: number = 1\n```\n```swift\nlet x = 1\n```'));
53
86
  ```
54
87
 
55
88
  ### Options
package/dist/render.js CHANGED
@@ -132,7 +132,100 @@ function normalizeNodes(tree) {
132
132
  if (node)
133
133
  normalized.push(node);
134
134
  }
135
- return { ...tree, children: normalized };
135
+ const mergedCodes = mergeAdjacentCodeBlocks(normalized);
136
+ const taggedDiffs = mergedCodes.map((child) => tagDiffBlock(child));
137
+ return { ...tree, children: taggedDiffs };
138
+ }
139
+ function flattenCodeList(list) {
140
+ if (!list.children.length ||
141
+ !list.children.every((item) => item.children.length === 1 &&
142
+ item.children[0]?.type === "code" &&
143
+ item.children[0].value !== undefined))
144
+ return null;
145
+ const codes = list.children.map((item) => item.children[0]);
146
+ const sameLang = codes.every((c) => c.lang === codes[0]?.lang);
147
+ const lang = sameLang ? (codes[0]?.lang ?? undefined) : undefined;
148
+ return {
149
+ type: "code",
150
+ lang: lang ?? undefined,
151
+ value: codes.map((c) => c.value).join("\n"),
152
+ position: list.position,
153
+ };
154
+ }
155
+ function mergeAdjacentCodeBlocks(nodes) {
156
+ const out = [];
157
+ let pending = null;
158
+ const flush = () => {
159
+ if (pending) {
160
+ out.push(pending);
161
+ pending = null;
162
+ }
163
+ };
164
+ for (const node of nodes) {
165
+ if (node?.type === "code") {
166
+ if (pending &&
167
+ (pending.lang === node.lang || (!pending.lang && !node.lang))) {
168
+ const nextValue = `${pending.value}\n${node.value}`;
169
+ pending = {
170
+ type: "code",
171
+ lang: pending.lang,
172
+ meta: pending.meta,
173
+ value: nextValue,
174
+ position: pending.position,
175
+ };
176
+ }
177
+ else {
178
+ flush();
179
+ pending = node;
180
+ }
181
+ continue;
182
+ }
183
+ if (node?.type === "list") {
184
+ const flattened = flattenCodeList(node);
185
+ if (flattened) {
186
+ if (pending &&
187
+ (pending.lang === flattened.lang ||
188
+ (!pending.lang && !flattened.lang))) {
189
+ const nextValue = `${pending.value}\n${flattened.value}`;
190
+ pending = {
191
+ type: "code",
192
+ lang: pending.lang,
193
+ meta: pending.meta,
194
+ value: nextValue,
195
+ position: pending.position,
196
+ };
197
+ }
198
+ else {
199
+ flush();
200
+ pending = flattened;
201
+ }
202
+ continue;
203
+ }
204
+ }
205
+ flush();
206
+ out.push(node);
207
+ }
208
+ flush();
209
+ return out;
210
+ }
211
+ function looksLikeDiff(text) {
212
+ const lines = text.split("\n").map((l) => l.trim());
213
+ if (lines.some((l) => l.startsWith("diff --git") ||
214
+ l.startsWith("--- a/") ||
215
+ l.startsWith("+++ b/") ||
216
+ l.startsWith("@@ ")))
217
+ return true;
218
+ const nonEmpty = lines.filter((l) => l !== "");
219
+ if (nonEmpty.length < 3)
220
+ return false;
221
+ const markers = nonEmpty.filter((l) => /^[+\-@]/.test(l)).length;
222
+ return markers >= Math.max(3, Math.ceil(nonEmpty.length * 0.6));
223
+ }
224
+ function tagDiffBlock(node) {
225
+ if (node?.type === "code" && !node.lang && looksLikeDiff(node.value)) {
226
+ return { ...node, lang: "diff" };
227
+ }
228
+ return node;
136
229
  }
137
230
  const HR_WIDTH = 40;
138
231
  const MAX_COL = 40;
@@ -320,11 +413,14 @@ function renderDefinition(node, _ctx) {
320
413
  function renderCodeBlock(node, ctx) {
321
414
  const theme = ctx.options.theme.blockCode || ctx.options.theme.inlineCode;
322
415
  const lines = (node.value ?? "").split("\n");
416
+ const isDiff = node.lang === "diff";
323
417
  const gutterWidth = ctx.options.codeGutter
324
418
  ? String(lines.length).length + 2
325
419
  : 0;
326
- const boxPadding = ctx.options.codeBox ? 4 : 0;
327
- const wrapLimit = ctx.options.codeWrap && ctx.options.wrap && ctx.options.width
420
+ const shouldWrap = isDiff ? false : ctx.options.codeWrap;
421
+ const useBox = ctx.options.codeBox && lines.length > 1;
422
+ const boxPadding = useBox ? 4 : 0;
423
+ const wrapLimit = shouldWrap && ctx.options.wrap && ctx.options.width
328
424
  ? Math.max(1, ctx.options.width - boxPadding - gutterWidth)
329
425
  : undefined; // undefined => no hard wrap limit
330
426
  const contentLines = lines.flatMap((line, idx) => {
@@ -340,7 +436,7 @@ function renderCodeBlock(node, ctx) {
340
436
  return `${ctx.style(num, { dim: true })} ${highlighted}`;
341
437
  });
342
438
  });
343
- if (!ctx.options.codeBox) {
439
+ if (!useBox) {
344
440
  return [`${contentLines.join("\n")}\n\n`];
345
441
  }
346
442
  // Boxed block
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "markdansi",
3
- "version": "0.1.2",
3
+ "version": "0.1.3",
4
4
  "description": "Tiny dependency-light markdown to ANSI converter.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",